Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
90 lines
3.2 KiB
Python
90 lines
3.2 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
|
||
# 2-й процент: distinct vtorichka за последние 45д (активные ИЛИ недавно
|
||
# закрытые) / знаменатель. Дом проходит фильтр по ЛЮБОМУ из двух процентов.
|
||
sale_share_pct_45d: float | None = None
|
||
# implausible-матч (числитель > знаменателя по любому из %) — скорее всего
|
||
# коллизия адреса при ГАР-матче; не прячем, а маркируем для UI.
|
||
over_100: bool = False
|
||
|
||
active_secondary: int | None = None
|
||
# distinct vtorichka-листинги (активные ИЛИ закрытые) за окно 45д.
|
||
listings_45d: 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).
|
||
|
||
buildings_with_denominator/coverage считают дома с непустым ЛЮБЫМ из двух %.
|
||
max/p95 и histogram — отдельно для каждого процента (now + 45д).
|
||
"""
|
||
|
||
total_secondary_buildings: int
|
||
buildings_with_denominator: int
|
||
coverage_pct: float
|
||
max_pct: float | None = None
|
||
p95_pct: float | None = None
|
||
max_pct_45d: float | None = None
|
||
p95_pct_45d: float | None = None
|
||
histogram: list[HistogramBucket]
|
||
histogram_45d: list[HistogramBucket]
|