fix(tradein): Mera-audit fix-2 — _is_plausible_deal floor edge + area/price guards
Ужесточает валидацию ДКП-сделок в _is_plausible_deal: - floor < 1 или floor=0 → drop (floor=-5/0/999 из битого парсера) - area_m2 задана и <= 0 → drop (нулевая/отрицательная площадь из битого парсера) - price_rub задана и <= 0 → drop (нерыночная/техническая сделка) - floor=None → допустимо (graceful, нечем судить) Все новые параметры area_m2/price_rub keyword-only с дефолтом None → backward-compatible для существующих вызовов. Caller _fetch_deals обновлён для передачи area_m2/price_rub из данных сделки. Тесты: 16 новых тест-кейсов в test_deals_sanitize.py (floor=-5, floor=999, floor=None ok, area_m2=0 drop, price_rub=-1 drop и пр.).
This commit is contained in:
parent
50a72f030a
commit
81ee864d80
2 changed files with 95 additions and 6 deletions
|
|
@ -4000,12 +4000,21 @@ def _fetch_analogs(
|
|||
|
||||
|
||||
def _is_plausible_deal(
|
||||
price_per_m2: float | None, floor: int | None, total_floors: int | None
|
||||
price_per_m2: float | None,
|
||||
floor: int | None,
|
||||
total_floors: int | None,
|
||||
area_m2: float | None = None,
|
||||
price_rub: float | None = None,
|
||||
) -> bool:
|
||||
"""#699: True если ДКП-сделка правдоподобна (не выброс по ppm²/этажу).
|
||||
"""#699 + Mera-audit fix-2: True если ДКП-сделка правдоподобна (не выброс).
|
||||
|
||||
Абсолютные guard-bands (см. DEAL_* константы). None-поля не судим (keep —
|
||||
нечем сравнивать). Этаж > total_floors физически невозможен → drop.
|
||||
нечем сравнивать). Проверки:
|
||||
- price_per_m2 вне [DEAL_MIN_PPM2, DEAL_MAX_PPM2] → drop
|
||||
- floor < 1 или floor > DEAL_MAX_FLOOR → drop (битый парсер: floor=-5/999)
|
||||
- floor > total_floors физически невозможен → drop
|
||||
- area_m2 задана и <= 0 → drop (битый парсер)
|
||||
- price_rub задана и <= 0 → drop (нерыночная/технческая сделка)
|
||||
"""
|
||||
if price_per_m2 is not None and not (DEAL_MIN_PPM2 <= price_per_m2 <= DEAL_MAX_PPM2):
|
||||
return False
|
||||
|
|
@ -4014,6 +4023,10 @@ def _is_plausible_deal(
|
|||
return False
|
||||
if total_floors is not None and total_floors > 0 and floor > total_floors:
|
||||
return False
|
||||
if area_m2 is not None and area_m2 <= 0:
|
||||
return False
|
||||
if price_rub is not None and price_rub <= 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
|
@ -4055,13 +4068,19 @@ def _fetch_deals(
|
|||
.all()
|
||||
)
|
||||
|
||||
# #699: отсекаем ДКП-выбросы (битый этаж / нерыночный ppm²) до выдачи в
|
||||
# actual_deals и expected_sold — иначе floor:100 / 39.7К-м² шумят демо.
|
||||
# #699 + Mera-audit fix-2: отсекаем ДКП-выбросы (битый этаж / нерыночный ppm²
|
||||
# / нулевая площадь / нулевая цена) до выдачи в actual_deals и expected_sold.
|
||||
deals = [dict(r) for r in rows]
|
||||
clean = [
|
||||
d
|
||||
for d in deals
|
||||
if _is_plausible_deal(d.get("price_per_m2"), d.get("floor"), d.get("total_floors"))
|
||||
if _is_plausible_deal(
|
||||
d.get("price_per_m2"),
|
||||
d.get("floor"),
|
||||
d.get("total_floors"),
|
||||
d.get("area_m2"),
|
||||
d.get("price_rub"),
|
||||
)
|
||||
]
|
||||
if len(clean) < len(deals):
|
||||
logger.info("deals sanitize #699: %d → %d (dropped outliers)", len(deals), len(clean))
|
||||
|
|
|
|||
|
|
@ -50,3 +50,73 @@ def test_null_fields_not_judged() -> None:
|
|||
assert _is_plausible_deal(None, None, None) is True
|
||||
assert _is_plausible_deal(None, 100, None) is False # floor всё равно судим
|
||||
assert _is_plausible_deal(39_700, None, None) is False # ppm² всё равно судим
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mera-audit fix-2: floor edge cases + area_m2/price_rub guards
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_drops_floor_negative() -> None:
|
||||
# floor=-5 из битого парсера → drop.
|
||||
assert _is_plausible_deal(150_000, -5, 9) is False
|
||||
|
||||
|
||||
def test_drops_floor_zero() -> None:
|
||||
# floor=0 — не существует (1-indexed) → drop.
|
||||
assert _is_plausible_deal(150_000, 0, 9) is False
|
||||
|
||||
|
||||
def test_drops_floor_999() -> None:
|
||||
# floor=999 из битого парсера → drop (> DEAL_MAX_FLOOR=60).
|
||||
assert _is_plausible_deal(150_000, 999, None) is False
|
||||
|
||||
|
||||
def test_keeps_floor_none_graceful() -> None:
|
||||
# floor=None → допустимо, не судим.
|
||||
assert _is_plausible_deal(150_000, None, None) is True
|
||||
|
||||
|
||||
def test_keeps_floor_1_valid() -> None:
|
||||
assert _is_plausible_deal(150_000, 1, 9) is True
|
||||
|
||||
|
||||
def test_keeps_floor_at_max() -> None:
|
||||
assert _is_plausible_deal(150_000, DEAL_MAX_FLOOR, None) is True
|
||||
|
||||
|
||||
def test_drops_area_m2_zero() -> None:
|
||||
# area_m2=0 из битого парсера → drop.
|
||||
assert _is_plausible_deal(150_000, 5, 9, area_m2=0.0) is False
|
||||
|
||||
|
||||
def test_drops_area_m2_negative() -> None:
|
||||
# area_m2=-10 → drop.
|
||||
assert _is_plausible_deal(150_000, 5, 9, area_m2=-10.0) is False
|
||||
|
||||
|
||||
def test_keeps_area_m2_none() -> None:
|
||||
# area_m2=None → не судим (graceful).
|
||||
assert _is_plausible_deal(150_000, 5, 9, area_m2=None) is True
|
||||
|
||||
|
||||
def test_keeps_area_m2_valid() -> None:
|
||||
assert _is_plausible_deal(150_000, 5, 9, area_m2=55.0) is True
|
||||
|
||||
|
||||
def test_drops_price_rub_zero() -> None:
|
||||
# price_rub=0 → нерыночная/техническая сделка → drop.
|
||||
assert _is_plausible_deal(150_000, 5, 9, price_rub=0.0) is False
|
||||
|
||||
|
||||
def test_drops_price_rub_negative() -> None:
|
||||
assert _is_plausible_deal(150_000, 5, 9, price_rub=-1.0) is False
|
||||
|
||||
|
||||
def test_keeps_price_rub_none() -> None:
|
||||
# price_rub=None → не судим (graceful).
|
||||
assert _is_plausible_deal(150_000, 5, 9, price_rub=None) is True
|
||||
|
||||
|
||||
def test_keeps_price_rub_valid() -> None:
|
||||
assert _is_plausible_deal(150_000, 5, 9, price_rub=8_000_000.0) is True
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue