All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m39s
Deploy Trade-In / build-backend (push) Successful in 8m5s
Deploy Trade-In / deploy (push) Successful in 52s
78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
"""Pydantic-схемы страницы «доля квартир дома в продаже» (мигр. 143).
|
||
|
||
Источник данных — view v_building_sale_share + базовая таблица listings.
|
||
Список домов вторичного рынка, где active_secondary / flat_count_effective >= threshold%.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import date
|
||
|
||
from pydantic import BaseModel, ConfigDict
|
||
|
||
|
||
class BuildingSaleShare(BaseModel):
|
||
"""Один дом из v_building_sale_share для списка /buildings/sale-share."""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
house_id: int
|
||
address: str | None = None
|
||
lat: float | None = None
|
||
lon: float | None = None
|
||
|
||
sale_share_pct: float | None = None
|
||
# implausible-матч (active_secondary > знаменателя) — скорее всего коллизия
|
||
# адреса при ГАР-матче; не прячем, а маркируем для UI.
|
||
over_100: bool = False
|
||
|
||
active_secondary: int | None = None
|
||
flat_count_effective: int | None = None
|
||
gar_match_method: str | None = None
|
||
|
||
median_price_rub: int | None = None
|
||
median_price_per_m2: int | None = None
|
||
avg_days_on_market: float | None = None
|
||
|
||
year_built: int | None = None
|
||
house_type: str | None = None
|
||
total_floors: int | None = None
|
||
series_name: str | None = None
|
||
is_emergency: bool | None = None
|
||
|
||
|
||
class BuildingListing(BaseModel):
|
||
"""Активное вторичное объявление в доме (click-through drawer)."""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
listing_id: int
|
||
source: str
|
||
source_url: str | None = None
|
||
price_rub: int | None = None
|
||
price_per_m2: int | None = None
|
||
rooms: int | None = None
|
||
area_m2: float | None = None
|
||
floor: int | None = None
|
||
total_floors: int | None = None
|
||
days_on_market: int | None = None
|
||
listing_date: date | None = None
|
||
address: str | None = None
|
||
|
||
|
||
class HistogramBucket(BaseModel):
|
||
"""Корзина гистограммы распределения sale_share_pct."""
|
||
|
||
bucket: str
|
||
count: int
|
||
|
||
|
||
class SaleShareSummary(BaseModel):
|
||
"""Сводка для слайдера % + баннера покрытия (/buildings/sale-share/summary)."""
|
||
|
||
total_secondary_buildings: int
|
||
buildings_with_denominator: int
|
||
coverage_pct: float
|
||
max_pct: float | None = None
|
||
p95_pct: float | None = None
|
||
histogram: list[HistogramBucket]
|