"""Pydantic schemas для insight (#948 Part A, ТЗ §7.13/§8.9). InsightCreate — тело POST-запроса (title/body обязательны). InsightUpdate — тело PUT-запроса (все поля опциональны — partial update). InsightOut — ответ API (включает id, created_by, created_at, updated_at). created_by НЕ принимается из тела — проставляется роутером из X-Authenticated-User. """ from __future__ import annotations from datetime import datetime from typing import Literal from pydantic import BaseModel, Field # Рекомендованные категории (мягкий enum, совпадает с CHECK ck_insight_category # в data/sql/145_insight.sql). None допустим. InsightCategory = Literal["competition", "permitting", "demand", "risk", "other"] class InsightCreate(BaseModel): title: str = Field(..., min_length=1, max_length=300, description="Заголовок заметки") body: str = Field(..., min_length=1, max_length=20000, description="Тело заметки (intel)") category: InsightCategory | None = Field( None, description="Категория: competition|permitting|demand|risk|other" ) is_confidential: bool = Field( False, description="«Непублично» (§7.13): маркировка чувствительной заметки" ) district: str | None = Field( None, max_length=200, description="Location ref (район); Part B промоутит в Location FK", ) cad_num: str | None = Field( None, max_length=100, description="Опциональный кадастровый номер участка" ) lon: float | None = Field(None, ge=-180.0, le=180.0, description="Долгота WGS-84 (map-pin)") lat: float | None = Field(None, ge=-90.0, le=90.0, description="Широта WGS-84 (map-pin)") source: str | None = Field(None, max_length=500, description="Источник intel") class InsightUpdate(BaseModel): """Partial update (PUT) — переданные поля заменяются, отсутствующие не трогаются.""" title: str | None = Field(None, min_length=1, max_length=300) body: str | None = Field(None, min_length=1, max_length=20000) category: InsightCategory | None = None is_confidential: bool | None = None district: str | None = Field(None, max_length=200) cad_num: str | None = Field(None, max_length=100) lon: float | None = Field(None, ge=-180.0, le=180.0) lat: float | None = Field(None, ge=-90.0, le=90.0) source: str | None = Field(None, max_length=500) class InsightOut(BaseModel): id: int created_by: str title: str body: str category: str | None is_confidential: bool district: str | None cad_num: str | None lon: float | None lat: float | None source: str | None created_at: datetime updated_at: datetime class InsightList(BaseModel): """Ответ list-эндпоинта: total + страница строк (как admin_leads).""" total: int limit: int offset: int rows: list[InsightOut]