fix(estimator): low-findings аудита «Мера» — cian sanity, deal floor-edge, price_trend dedup, DEAL_MAX verify #1863

Merged
lekss361 merged 4 commits from fix/estimator-low-findings into main 2026-06-21 06:16:35 +00:00
2 changed files with 95 additions and 6 deletions
Showing only changes of commit 81ee864d80 - Show all commits

View file

@ -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))

View file

@ -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