gendesign/tradein-mvp/backend/tests/test_deals_sanitize.py
bot-backend 81ee864d80 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 и пр.).
2026-06-21 09:04:54 +03:00

122 lines
4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""#699 — ДКП-выброс sanitize: _is_plausible_deal guard-bands (floor + ppm²)."""
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
from app.services.estimator import (
DEAL_MAX_FLOOR,
DEAL_MAX_PPM2,
DEAL_MIN_PPM2,
_is_plausible_deal,
)
def test_drops_implausible_floor_100() -> None:
# репорт-кейс: floor:100 — заведомо битый этаж.
assert _is_plausible_deal(150_000, 100, None) is False
def test_drops_submarket_ppm2_3970() -> None:
# репорт-кейс: 4.98М за 125 м² = ~39.7К/м² — нерыночный.
assert _is_plausible_deal(39_700, 5, 9) is False
def test_drops_floor_above_total_floors() -> None:
# 30-й этаж в 5-этажке физически невозможен.
assert _is_plausible_deal(150_000, 30, 5) is False
def test_drops_ppm2_above_band() -> None:
assert _is_plausible_deal(DEAL_MAX_PPM2 + 1, 5, 9) is False
def test_keeps_normal_deal() -> None:
assert _is_plausible_deal(150_000, 5, 9) is True
def test_keeps_high_floor_in_tall_building() -> None:
# 30-й этаж в 30-этажке — реально для ЕКБ, не выброс.
assert _is_plausible_deal(160_000, 30, 30) is True
def test_keeps_at_band_edges() -> None:
assert _is_plausible_deal(DEAL_MIN_PPM2, 1, None) is True
assert _is_plausible_deal(DEAL_MAX_PPM2, DEAL_MAX_FLOOR, None) is True
def test_null_fields_not_judged() -> None:
# ppm²/floor=None — нечем судить, оставляем (None-safe).
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