fix(estimator): enable split-home disclosure at validated threshold 1.2 (audit #1871)
Включает wide-corridor disclosure (PR #1880, был default OFF) — но с ИСПРАВЛЕННЫМ порогом по prod-данным. Проверка на trade_in_estimates (60д): corridor_pct = (range_high-range_low)/ median_price имеет median≈0.48, p90≈0.93, p99≈1.38; **30.7% оценок > 0.6**. Старый порог 0.6 прилепил бы caveat «дом разбит на секции» к ~трети оценок — большинство НЕ split-дома (широкий коридор бывает от разных причин). Это была бы ложная атрибуция (дезинформация). - estimate_wide_corridor_threshold 0.6 → 1.2: genuine split-дома из аудита = 148-170% (1.48-1.70) → 1.2 ловит только экстремальный хвост (>p99), не трогая нормальную оценочную неопределённость. - estimate_wide_corridor_disclosure_enabled False → True (включаем после валидации). ENV-override сохранён (kill-switch). - Формулировка смягчена: «дом разбит на секции» → «вероятно, дом разбит на секции разной этажности ИЛИ разнородный фонд» — мы ИНФЕРИМ split по ширине коридора, не доказываем структурно. - Логика не тронута: фаерит только Tier A + corridor>threshold, только понижает confidence + дописывает explanation; point/median/range не трогает. Тесты: обновлены default-ассерты (флаг ON, порог 1.2) + 2 behavioral boundary- теста (фаерит при pct>threshold, НЕ фаерит при умеренном 1.045<1.2 — доказывает что raise убрал false-positive flood). 13 passed. Refs #1871
This commit is contained in:
parent
f0b689318e
commit
77a3d3f75a
3 changed files with 42 additions and 13 deletions
|
|
@ -191,17 +191,21 @@ class Settings(BaseSettings):
|
|||
# ENV: ESTIMATE_RADIUS_DEDUP_ENABLED.
|
||||
estimate_radius_dedup_enabled: bool = True
|
||||
|
||||
# ── #1871 P2: split-дома wide-corridor disclosure (default OFF) ────────────
|
||||
# ── #1871 P2: split-дома wide-corridor disclosure (default ON, порог 1.2) ──
|
||||
# Tier A (same-building) матчит по address-regex (намеренно НЕ house_id — дом
|
||||
# дробится на несколько house_id). На split-доме разной этажности comp_min..max
|
||||
# растягивается через несколько ценовых режимов → коридор range_low/high
|
||||
# 148%/170%. Коридор честно широкий, но юзер видит 170% без объяснения. True +
|
||||
# Tier A + corridor_pct > threshold → понижаем confidence на ступень и дописываем
|
||||
# disclosure в explanation. НЕ трогает point/median/range. default False
|
||||
# (no-regress, включаем осознанно). ENV: ESTIMATE_WIDE_CORRIDOR_DISCLOSURE_ENABLED.
|
||||
estimate_wide_corridor_disclosure_enabled: bool = False
|
||||
# Порог ширины коридора (range_high-range_low)/median_price для disclosure (60%).
|
||||
estimate_wide_corridor_threshold: float = 0.6
|
||||
# disclosure в explanation. НЕ трогает point/median/range.
|
||||
# ENV: ESTIMATE_WIDE_CORRIDOR_DISCLOSURE_ENABLED.
|
||||
estimate_wide_corridor_disclosure_enabled: bool = True
|
||||
# Порог ширины коридора (range_high-range_low)/median_price для disclosure.
|
||||
# 1.2 (120%): по prod-данным corridor_pct median≈0.48, p90≈0.93 — порог 0.6
|
||||
# фаерил бы на ~31% оценок (широкий коридор ≠ split-дом, ложная атрибуция).
|
||||
# Genuine split-дома из аудита = 148-170% (1.48-1.70) → 1.2 ловит только
|
||||
# экстремальный хвост (>p99), не трогая нормальную оценочную неопределённость.
|
||||
estimate_wide_corridor_threshold: float = 1.2
|
||||
|
||||
# ── Mera-audit fix-1: Cian valuation sanity bounds ────────────────────────
|
||||
# API-ответ Cian иногда возвращает garbage-значения (999_999 или 9_999_999_999).
|
||||
|
|
|
|||
|
|
@ -2507,9 +2507,9 @@ async def estimate_quality(
|
|||
if corridor_pct > settings.estimate_wide_corridor_threshold:
|
||||
confidence = _downgrade_confidence(confidence)
|
||||
explanation = (explanation or "") + (
|
||||
" Широкий ценовой диапазон: дом разбит на секции разной "
|
||||
"этажности — оценка ориентировочная, уточните по конкретной "
|
||||
"секции."
|
||||
" Очень широкий ценовой диапазон по дому (вероятно, дом "
|
||||
"разбит на секции разной этажности или разнородный фонд) — "
|
||||
"оценка ориентировочная, уточните по конкретной секции."
|
||||
)
|
||||
|
||||
# ── #651: IMV / Yandex BLEND (killer accuracy fix) — SECONDARY, Tier D only ──
|
||||
|
|
|
|||
|
|
@ -67,12 +67,15 @@ _DISCLOSURE_MARKER = "разбит на секции"
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_disclosure_flag_default_off() -> None:
|
||||
assert settings.estimate_wide_corridor_disclosure_enabled is False
|
||||
def test_disclosure_flag_default_on() -> None:
|
||||
# Включено после валидации порога на prod-данных (corridor_pct median≈0.48).
|
||||
assert settings.estimate_wide_corridor_disclosure_enabled is True
|
||||
|
||||
|
||||
def test_threshold_default_0_6() -> None:
|
||||
assert settings.estimate_wide_corridor_threshold == 0.6
|
||||
def test_threshold_default_1_2() -> None:
|
||||
# 1.2: 0.6 фаерил бы на ~31% оценок (широкий коридор ≠ split-дом);
|
||||
# genuine split-дома аудита = 148-170% → 1.2 ловит только экстремальный хвост.
|
||||
assert settings.estimate_wide_corridor_threshold == 1.2
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -98,6 +101,28 @@ def test_flag_off_no_change() -> None:
|
|||
assert _DISCLOSURE_MARKER not in (off.confidence_explanation or "")
|
||||
|
||||
|
||||
# Boundary-тесты вокруг ПРОД-порога: _WIDE_SPLIT_COMPS даёт коридор ≈1.045
|
||||
# (estimator MAD-фильтрует более широкие спреды, поэтому проверяем границу
|
||||
# через сам threshold, а не пытаемся сконструировать коридор >1.2).
|
||||
|
||||
|
||||
def test_corridor_fires_when_above_threshold() -> None:
|
||||
"""corridor_pct (≈1.045) > threshold (1.0) → disclosure фаерит (механизм работает)."""
|
||||
on = _run(flag=True, anchor_tier="A", threshold=1.0)
|
||||
pct = (on.range_high_rub - on.range_low_rub) / on.median_price_rub
|
||||
assert pct > 1.0
|
||||
assert _DISCLOSURE_MARKER in (on.confidence_explanation or "")
|
||||
|
||||
|
||||
def test_moderate_corridor_no_fire_at_prod_threshold_1_2() -> None:
|
||||
"""Прод-дефолт 1.2: умеренный коридор (0.6<pct<1.2) НЕ фаерит — нет ложной
|
||||
атрибуции split-дома на ~31% оценок, которую дал бы старый порог 0.6."""
|
||||
on = _run(flag=True, anchor_tier="A", threshold=1.2) # _WIDE_SPLIT_COMPS ≈ 1.045
|
||||
pct = (on.range_high_rub - on.range_low_rub) / on.median_price_rub
|
||||
assert 0.6 < pct < 1.2, "moderate-fixture должен быть между 0.6 и 1.2"
|
||||
assert _DISCLOSURE_MARKER not in (on.confidence_explanation or "")
|
||||
|
||||
|
||||
def test_does_not_touch_point_median_range() -> None:
|
||||
"""Disclosure НЕ трогает point/median/range — только confidence+explanation."""
|
||||
off = _run(flag=False, anchor_tier="A")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue