From 6b99f7d23843b9aa6b3f71d73c6c9af85fd48a07 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 20 Jun 2026 15:00:12 +0300 Subject: [PATCH] =?UTF-8?q?fix(estimator):=20=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D1=80=D0=BD=D1=8B=D0=B9=20analog=5Ftier=20+?= =?UTF-8?q?=20address=5Fprecision=20=D0=B2=20API=20(#audit-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавляет AggregatedEstimate.analog_tier — стабильный enum для фронта: same_building (Tier A), micro_radius (Tier C), district (S/H/0), city (W), null (нет данных). Фронт не парсит tier из explanation. address_precision уже был в схеме (подтверждено: line 208). Explanation не удаляется — фронт fallback'ает на него. --- tradein-mvp/backend/app/schemas/trade_in.py | 9 +++++++++ tradein-mvp/backend/app/services/estimator.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tradein-mvp/backend/app/schemas/trade_in.py b/tradein-mvp/backend/app/schemas/trade_in.py index 777fce6d..9173d75d 100644 --- a/tradein-mvp/backend/app/schemas/trade_in.py +++ b/tradein-mvp/backend/app/schemas/trade_in.py @@ -206,6 +206,15 @@ class AggregatedEstimate(BaseModel): # «approximate» (qc_geo≥2: населённый пункт/город/регион/не распознан). # None если DaData не отрабатывала (адрес не геокодирован) / credentials не заданы. address_precision: Literal["house", "street", "approximate"] | None = None + # analog_tier — стабильный enum-тир источника аналогов (#audit-2). + # Значения (дословно, фронт на них завязан): + # "same_building" — Tier A: якорь из комплов ТОГО ЖЕ дома + # "micro_radius" — Tier C: якорь из micro-radius (≤500 м) + # "district" — radius-путь (Tier 0/S/H): когортный/узкий радиус + # "city" — radius Tier W: широкий fallback + # null — нет данных / оценка не построена + # НЕ удаляет/заменяет confidence_explanation (фронт fallback'ает на него). + analog_tier: Literal["same_building", "micro_radius", "district", "city"] | None = None # ── Параметры оценённой квартиры — нужны, чтобы восстановить карточку # при открытии оценки по ссылке (?id=), когда формы-инпута уже нет ── area_m2: float | None = None diff --git a/tradein-mvp/backend/app/services/estimator.py b/tradein-mvp/backend/app/services/estimator.py index 75b915e9..7549975c 100644 --- a/tradein-mvp/backend/app/services/estimator.py +++ b/tradein-mvp/backend/app/services/estimator.py @@ -2985,6 +2985,22 @@ async def estimate_quality( else None ) + # #audit-2: структурный analog_tier — стабильный enum для фронта. + # anchor-путь: anchor_tier "A" → "same_building", "C" → "micro_radius". + # radius-путь: analog_tier "W" → "city", остальные → "district". + # None только если нет аналогов (median=0, insufficient_data=True). + if median_price > 0: + if anchor_tier == "A": + api_analog_tier: str | None = "same_building" + elif anchor_tier == "C": + api_analog_tier = "micro_radius" + elif analog_tier == "W": + api_analog_tier = "city" + else: + api_analog_tier = "district" + else: + api_analog_tier = None + return AggregatedEstimate( estimate_id=estimate_id, median_price_rub=median_price, @@ -3049,6 +3065,7 @@ async def estimate_quality( house_fias_id=dadata.house_fias_id if dadata else None, metro_nearest=(dadata.metro if dadata and dadata.metro else []), address_precision=_qc_geo_to_precision(dadata.qc_geo if dadata else None), + analog_tier=api_analog_tier, # type: ignore[arg-type] ) @@ -4249,4 +4266,5 @@ def _empty_estimate( cian_valuation=None, # Адрес не геокодирован (DaData не отрабатывала) → точность неизвестна. address_precision=None, + analog_tier=None, # нет данных при empty estimate )