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 )