"""Pydantic schemas для location (#948 Part B, ТЗ §8.2). LocationOut — ответ API: район + 4 district-level индекса (0..1 или None). LocationList — ответ list-эндпоинта (total + rows). Read-only: индексы ВЫЧИСЛЯЮТСЯ воркером (tasks/location_refresh.py), не вводятся пользователем → Create/Update-схем нет (CRUD отсутствует, только GET-API). Все индексы Optional[float] ∈ [0,1]: None = нет данных (graceful-on-thin-data — никогда не фабрикуем 0). centroid не отдаём бинарём — lon/lat достаточно для карты (NULL если centroid не вычислен). """ from __future__ import annotations from datetime import datetime from pydantic import BaseModel, Field class LocationOut(BaseModel): """Локация (район) с district-level индексами (#948 Part B, §8.2).""" id: int district_name: str = Field(..., description="Логический ключ района (8 админ-районов ЕКБ)") region: str | None = Field(None, description="Регион (NULL = ЕКБ/Свердл.)") lon: float | None = Field(None, description="Долгота центроида района (WGS-84), None если нет") lat: float | None = Field(None, description="Широта центроида района (WGS-84), None если нет") infra_index: float | None = Field( None, ge=0.0, le=1.0, description="Инфра-индекс 0..1 (POI района / POI города); None=нет данных", ) competition_index: float | None = Field( None, ge=0.0, le=1.0, description=( "Индекс конкуренции 0..1 (доступное/затоваренное конкурирующее предложение " "= overstock; выше = больше конкур. давления; ортогонален demand); " "None=нет данных" ), ) demand_index: float | None = Field( None, ge=0.0, le=1.0, description="Индекс спроса 0..1 (saturated velocity); None=нет данных", ) future_supply_index: float | None = Field( None, ge=0.0, le=1.0, description="Индекс будущего предложения 0..1; None=нет данных", ) indices_computed_at: datetime | None = Field( None, description="Когда индексы последний раз пересчитаны (None до первого refresh)" ) created_at: datetime updated_at: datetime class LocationList(BaseModel): """Ответ list-эндпоинта: total + страница строк (зеркало InsightList).""" total: int rows: list[LocationOut]