diff --git a/tradein-mvp/backend/app/schemas/trade_in.py b/tradein-mvp/backend/app/schemas/trade_in.py index 7ae83e1b..2bc0993d 100644 --- a/tradein-mvp/backend/app/schemas/trade_in.py +++ b/tradein-mvp/backend/app/schemas/trade_in.py @@ -46,6 +46,20 @@ class AnalogLot(BaseModel): distance_m: int | None = None # расстояние до целевой квартиры в метрах +class CianValuationSummary(BaseModel): + """Cian Valuation Calculator данные для UI. + + Источник: external_valuations table (source='cian_valuation'). + Заполняется только при successful Cian Calculator call в estimator.py. + """ + + sale_price_rub: int | None = None + rent_price_rub: int | None = None + chart: list[dict[str, Any]] = Field(default_factory=list) + chart_change_pct: float | None = None + chart_change_direction: Literal["increase", "decrease", "neutral"] | None = None + + class AggregatedEstimate(BaseModel): estimate_id: UUID median_price_rub: int @@ -66,6 +80,7 @@ class AggregatedEstimate(BaseModel): sources_used: list[str] = Field(default_factory=list) # ['avito', 'cian', 'rosreestr'] data_freshness_minutes: int | None = None # сколько минут назад был самый свежий парсинг est_days_on_market: int | None = None # прогноз срока продажи (медиана по аналогам) + cian_valuation: CianValuationSummary | 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 da4c104f..dead7bd5 100644 --- a/tradein-mvp/backend/app/services/estimator.py +++ b/tradein-mvp/backend/app/services/estimator.py @@ -31,7 +31,12 @@ from uuid import uuid4 from sqlalchemy import text from sqlalchemy.orm import Session -from app.schemas.trade_in import AggregatedEstimate, AnalogLot, TradeInEstimateInput +from app.schemas.trade_in import ( + AggregatedEstimate, + AnalogLot, + CianValuationSummary, + TradeInEstimateInput, +) from app.services.geocoder import GeocodeResult, geocode from app.services.house_metadata import get_house_metadata from app.services.scrapers.avito_imv import ( @@ -865,6 +870,17 @@ async def estimate_quality( sources_used=sources_used, data_freshness_minutes=freshness_min, est_days_on_market=_estimate_days_on_market(listings_clean, deals), + cian_valuation=( + CianValuationSummary( + sale_price_rub=int(cian_val.sale_price_rub) if cian_val.sale_price_rub else None, + rent_price_rub=int(cian_val.rent_price_rub) if cian_val.rent_price_rub else None, + chart=list(cian_val.chart or []), + chart_change_pct=cian_val.chart_change_pct, + chart_change_direction=cian_val.chart_change_direction, + ) + if cian_val is not None + else None + ), area_m2=payload.area_m2, rooms=payload.rooms, floor=payload.floor, @@ -1598,4 +1614,5 @@ def _empty_estimate( analogs=[], actual_deals=[], expires_at=expires_at, + cian_valuation=None, )