from __future__ import annotations from datetime import datetime from typing import Any from pydantic import BaseModel, ConfigDict, Field class CaseBase(BaseModel): model_config = ConfigDict(extra='allow') age: int | None = None age_years: int | None = None gender: str | None = None child_name: str | None = None height_build: str | None = None clothes_upper: str | None = None clothes_lower: str | None = None clothes_shoes: str | None = None shoes: str | None = None clothes_description: str | None = None special_marks: str | None = None phone_status: str | None = None health_flags: list[str] = Field(default_factory=list) has_diagnosis: bool | None = None diagnosis_type: list[str] = Field(default_factory=list) fitness_level: str | None = None has_transport: str | None = None cant_swim: bool | None = None psychotype: str | None = None psychotype_answers: dict[str, Any] | None = None elapsed_hours: float | None = None last_known_place: str | None = None direction: str | None = None reason: str | None = None loss_reason: str | None = None loss_time: datetime | None = None last_seen_direction: str | None = None last_seen_reliability: str | None = None last_seen_description: str | None = None behavior_description: str | None = None familiar_places: str | None = None lost_before: str | None = None terrain_primary: str | None = None terrain: list[str] = Field(default_factory=list) weather: str | None = None season: str | None = None temperature_c: float | None = None precipitation: str | None = None visibility: str | None = None wind: str | None = None gps: dict[str, float] | None = None gps_lat: float | None = None gps_lon: float | None = None tnp_lat: float | None = None tnp_lon: float | None = None tnp_address: str | None = None resources: list[str] = Field(default_factory=list) teams_count: int | None = None team_size: int | None = None has_dog: bool | None = None extra_resources: list[str] = Field(default_factory=list) notes: str | None = None note: str | None = None status: str | None = None found_alive: bool | None = None found_distance_km: float | None = None found_direction: str | None = None found_location_type: str | None = None found_lat: float | None = None found_lon: float | None = None search_duration_hours: float | None = None who_found: str | None = None raw_text: str | None = None analysis_log: dict[str, Any] | None = None mobile: bool = False class CaseCreate(CaseBase): status: str | None = 'new' class CaseUpdate(CaseBase): pass class CaseResponse(CaseBase): id: str created_at: datetime updated_at: datetime status: str health_flags: list[str] = Field(default_factory=list) diagnosis_type: list[str] = Field(default_factory=list) terrain: list[str] = Field(default_factory=list) resources: list[str] = Field(default_factory=list) extra_resources: list[str] = Field(default_factory=list) result: dict[str, Any] | None = None class CaseListResponse(BaseModel): items: list[CaseResponse] total: int page: int page_size: int class DashboardResponse(BaseModel): total_cases: int found_alive_count: int found_deceased_count: int unknown_outcome_count: int median_found_distance_km: float | None recent_activity_count: int active_operations: int = 0 completed_operations: int = 0 class RecommendationRequest(BaseModel): age: int | None = None gender: str | None = None health_flags: list[str] = Field(default_factory=list) elapsed_hours: int | None = None terrain_primary: str | None = None weather: str | None = None resources: list[str] = Field(default_factory=list) class AnalyzeResponse(BaseModel): urgency: str primary_zones: list[dict[str, Any]] immediate_actions: list[str] behavioral_prediction: str psychotype_recommendations: dict[str, Any] key_locations: list[str] case_id: str # ================= ЗАВЕРШЁННЫЕ ПОИСКИ (ручной ввод, без ИИ) ================= # Оператор руками вносит данные ЗАВЕРШЁННОГО поиска: параметры субъекта/среды # (вход матмодели) + реальный исход. Цель — датасет для калибровки модели # (сравнение прогноза ВЕКТОРА с фактом) и архив 14 лет без LLM. class ClosedCaseCreate(BaseModel): # --- Субъект (вход модели) --- age_years: int gender: str = 'м' # 'м' | 'ж' diagnosis_type: list[str] = Field(default_factory=list) # РАС, эпилепсия, СДВГ, ЗПР... has_transport: str = 'none' # none|bike|scooter|other cant_swim: bool = False fitness_level: str | None = None # низкий|средний|высокий # --- Обстоятельства (вход матмодели) --- elapsed_hours: float loss_time: datetime | None = None # время потери (для time_of_day) terrain: list[str] = Field(default_factory=list) # лес, болото, поле... weather: str | None = None season: str | None = None temperature_c: float | None = None tnp_lat: float | None = None tnp_lon: float | None = None tnp_address: str | None = None # --- Психотип (опционально, вход скоринга) --- psychotype: str | None = None psychotype_answers: dict[str, Any] | None = None # --- Ресурсы --- teams_count: int | None = None team_size: int | None = None has_dog: bool = False # --- Исход (факт, для сравнения с прогнозом) --- found_alive: bool found_distance_km: float | None = None found_direction: str | None = None found_location_type: str | None = None found_lat: float | None = None found_lon: float | None = None search_duration_hours: float | None = None who_found: str | None = None # --- Мета --- notes: str | None = None source: str | None = None # откуда: спецдонесение 2015, бумажный журнал... class ClosedCaseResponse(ClosedCaseCreate): id: str created_at: datetime # Сохранённый прогноз модели на момент внесения (для калибровки) model_prediction: dict[str, Any] | None = None # Насколько прогноз совпал с фактом accuracy_note: str | None = None class ClosedCaseListResponse(BaseModel): items: list[ClosedCaseResponse] total: int