diff --git a/tradein-mvp/backend/app/api/v1/trade_in.py b/tradein-mvp/backend/app/api/v1/trade_in.py index c8badb8c..48bcc51a 100644 --- a/tradein-mvp/backend/app/api/v1/trade_in.py +++ b/tradein-mvp/backend/app/api/v1/trade_in.py @@ -21,6 +21,7 @@ from app.schemas.trade_in import ( HouseInfoForEstimate, IMVBenchmarkResponse, PhotoMeta, + PlacementHistoryEntry, TradeInEstimateInput, ) from app.services.exporters.trade_in_pdf import generate_trade_in_pdf @@ -418,7 +419,7 @@ def get_estimate_houses( # Path 2: geo-nearby (any source, 500м radius) nearby: list[Any] = [] - if target.lat and target.lon: + if target.lat is not None and target.lon is not None: nearby = list( db.execute( text( @@ -453,11 +454,11 @@ def get_estimate_houses( ] -@router.get("/estimate/{estimate_id}/placement-history") +@router.get("/estimate/{estimate_id}/placement-history", response_model=list[PlacementHistoryEntry]) def get_estimate_placement_history( estimate_id: UUID, db: Annotated[Session, Depends(get_db)], -) -> list[dict]: +) -> list[PlacementHistoryEntry]: """Историческая продажная активность по дому(ам) target estimate. Возвращает rows из house_placement_history для всех houses связанных с @@ -490,7 +491,7 @@ def get_estimate_placement_history( ).all() house_ids = [r.id for r in rows] - if not house_ids and target.lat and target.lon: + if not house_ids and target.lat is not None and target.lon is not None: # Geo fallback: 100м (tight radius чтобы не смешать соседние дома) rows = db.execute( text( @@ -528,7 +529,7 @@ def get_estimate_placement_history( {"house_ids": house_ids}, ).mappings().all() - return [dict(r) for r in history] + return [PlacementHistoryEntry(**dict(r)) for r in history] @router.get("/estimate/{estimate_id}/imv-benchmark", response_model=IMVBenchmarkResponse) diff --git a/tradein-mvp/backend/app/schemas/trade_in.py b/tradein-mvp/backend/app/schemas/trade_in.py index a8351d17..d7d62976 100644 --- a/tradein-mvp/backend/app/schemas/trade_in.py +++ b/tradein-mvp/backend/app/schemas/trade_in.py @@ -153,6 +153,27 @@ class IMVBenchmarkResponse(BaseModel): diff_pct: float | None = None # (our - imv) / imv * 100 +class PlacementHistoryEntry(BaseModel): + """Запись истории размещения лота в доме (`house_placement_history`).""" + + id: int + source: str + house_id: int | None = None + ext_item_id: str + title: str | None = None + rooms: int | None = None + area_m2: float | None = None + floor: int | None = None + total_floors: int | None = None + start_price: int | None = None + start_price_date: date | None = None + last_price: int | None = None + last_price_date: date | None = None + removed_date: date | None = None + exposure_days: int | None = None + notes: str | None = None + + # ── In-app scheduler (Stage 4e) ─────────────────────────────────────────────