fix(tradein): confidence_explanation no longer contradicts counters (#695)

When the same-building/micro-radius anchor built the headline, the explanation
CONCATENATED three fragments describing different populations: the radius base
text («Найдено N аналогов из M разных адресов»), the analog_tier note, and the
anchor sentence («построено по anchor['n'] аналогам из того же дома»). Result:
contradictory counts (n_analogs≠anchor['n'], e.g. «4 … по 5») and tiers
(«разных адресов» vs «того же дома»).

Fix: when the anchor fires, the anchor sentence becomes authoritative — replace
the explanation with it (+ repair_note, which is orthogonal) instead of
appending to the radius base + tier_note. Radius path unchanged.

Refs #695
This commit is contained in:
bot-backend 2026-05-30 18:07:44 +03:00
parent 83f3d70859
commit e6e2a2cdc4
2 changed files with 31 additions and 3 deletions

View file

@ -1808,10 +1808,16 @@ async def estimate_quality(
range_high = new_range_high
confidence = anchor["confidence"]
tier_label = "того же дома" if anchor_tier == "A" else "ближайшего окружения (≤500 м)"
explanation = (explanation or "") + (
f" Оценка построена по {anchor['n']} аналогам из {tier_label}"
# #695: когда якорь построил headline, explanation описывает ИМЕННО якорные
# комплы (anchor['n'] из tier_label). Радиусный base-текст («Найдено N из M
# разных адресов») и analog_tier tier_note относятся к ДРУГОЙ выборке и
# противоречат якорю (n_analogs≠anchor['n']; «разных адресов» vs «того же
# дома») — поэтому ЗАМЕНЯЕМ, а не конкатенируем. repair_note сохраняем
# (ортогонален — поправка на ремонт). confidence уже = anchor["confidence"].
explanation = (
f"Оценка построена по {anchor['n']} аналогам из {tier_label}"
f"{' (топ-уровень в доме)' if anchor['used_uplift'] else ''}."
)
) + repair_note
# ── #651: IMV / Yandex BLEND (killer accuracy fix) — SECONDARY, Tier D only ──
# Радиусная медиана системно занижает премиум/видовые квартиры (нет class/

View file

@ -925,3 +925,25 @@ def test_estimate_precise_geo_tier_c_not_downgraded() -> None:
# default geo = '…ул. Хохрякова, 48' → есть номер дома → не coarse
)
assert _APPROX_FLAG not in (est.confidence_explanation or "")
# ── #695: explanation согласован при сработавшем якоре (без радиус-противоречия) ──
def test_explanation_anchor_no_radius_contradiction() -> None:
"""#695: якорь дома построил headline → explanation описывает якорные комплы
(anchor['n']=3 из того же дома), БЕЗ противоречащего радиусного «Найдено N из M
разных адресов» и без analog_tier-note про «нет аналогов в том же доме»."""
est = _run_estimate(anchor_comps=_SB_COMPS_PREMIUM, anchor_tier="A")
exp = est.confidence_explanation or ""
assert "Оценка построена по 3 аналогам из того же дома" in exp
assert "Найдено" not in exp # радиусный base-текст не конкатенируется
assert "разных адресов" not in exp # нет противоречия «того же дома» vs «разных»
def test_explanation_radius_path_keeps_base_text() -> None:
"""#695 контроль: без якоря (radius-путь) base-текст «Найдено N…» сохраняется."""
est = _run_estimate(anchor_comps=[], anchor_tier=None)
exp = est.confidence_explanation or ""
assert "Найдено" in exp
assert "Оценка построена по" not in exp # якорной фразы нет на чистом радиусе