from pydantic import BaseModel, Field, ConfigDict from typing import Optional, List, Dict, Any from datetime import datetime from uuid import UUID class CaseCreate(BaseModel): """Schema for creating a new case""" # Ребёнок (Шаг 1) child_name: Optional[str] = None age_years: int = Field(..., ge=0, le=18) gender: Optional[str] = Field(None, pattern="^[МЖ]$") height_build: Optional[str] = None clothes_upper: Optional[str] = None clothes_lower: Optional[str] = None shoes: Optional[str] = None clothes_description: Optional[str] = None special_marks: Optional[str] = None phone_status: Optional[str] = Field(None, pattern="^(answers|silent|none)$") # Здоровье (Шаг 2) has_diagnosis: bool = False diagnosis_type: Optional[List[str]] = None fitness_level: Optional[str] = Field(None, pattern="^(low|medium|high)$") has_transport: str = Field(default="none", pattern="^(none|bike|scooter|other)$") cant_swim: bool = False # Психотип (Шаг 2б) psychotype: Optional[str] = None psychotype_answers: Optional[Dict[str, Any]] = None # Обстоятельства (Шаг 3) loss_reason: Optional[str] = None loss_time: Optional[datetime] = None elapsed_hours: Optional[float] = Field(None, ge=0) last_seen_direction: Optional[str] = None last_seen_reliability: Optional[str] = Field(None, pattern="^(exact|approx|unknown)$") last_seen_description: Optional[str] = None behavior_description: Optional[str] = None familiar_places: Optional[str] = None lost_before: Optional[str] = Field(None, pattern="^(yes|no|unknown)$") # Среда (Шаг 4) season: Optional[str] = None temperature_c: Optional[float] = None precipitation: Optional[str] = None visibility: Optional[str] = None wind: Optional[str] = None terrain: Optional[List[str]] = None # GPS (Шаг 4) tnp_lat: Optional[float] = Field(None, ge=-90, le=90) tnp_lon: Optional[float] = Field(None, ge=-180, le=180) tnp_address: Optional[str] = None # Ресурсы (Шаг 5) teams_count: Optional[int] = Field(None, ge=0) team_size: Optional[int] = Field(None, ge=0) has_dog: bool = False extra_resources: Optional[List[str]] = None model_config = ConfigDict(json_schema_extra={ "example": { "child_name": "Иван", "age_years": 8, "gender": "М", "loss_reason": "потерялся в лесу", "season": "лето", "temperature_c": 22.0, "tnp_lat": 53.9, "tnp_lon": 27.56, "teams_count": 3, "team_size": 5 } }) class CaseOut(BaseModel): """Schema for case output""" id: UUID created_at: datetime status: str # Ребёнок child_name: Optional[str] = None age_years: int gender: Optional[str] = None height_build: Optional[str] = None clothes_upper: Optional[str] = None clothes_lower: Optional[str] = None shoes: Optional[str] = None clothes_description: Optional[str] = None special_marks: Optional[str] = None phone_status: Optional[str] = None # Здоровье has_diagnosis: bool diagnosis_type: Optional[List[str]] = None fitness_level: Optional[str] = None has_transport: str cant_swim: bool # Психотип psychotype: Optional[str] = None psychotype_answers: Optional[Dict[str, Any]] = None # Обстоятельства loss_reason: Optional[str] = None loss_time: Optional[datetime] = None elapsed_hours: Optional[float] = None last_seen_direction: Optional[str] = None last_seen_reliability: Optional[str] = None last_seen_description: Optional[str] = None behavior_description: Optional[str] = None familiar_places: Optional[str] = None lost_before: Optional[str] = None # Среда season: Optional[str] = None temperature_c: Optional[float] = None precipitation: Optional[str] = None visibility: Optional[str] = None wind: Optional[str] = None terrain: Optional[List[str]] = None # GPS tnp_lat: Optional[float] = None tnp_lon: Optional[float] = None tnp_address: Optional[str] = None # Ресурсы teams_count: Optional[int] = None team_size: Optional[int] = None has_dog: bool extra_resources: Optional[List[str]] = None # Исход found_alive: Optional[bool] = None found_distance_km: Optional[float] = None found_direction: Optional[str] = None found_location_type: Optional[str] = None found_lat: Optional[float] = None found_lon: Optional[float] = None search_duration_hours: Optional[float] = None who_found: Optional[str] = None # Мета confidence_avg: Optional[float] = None raw_text: Optional[str] = None analysis_log: Optional[Dict[str, Any]] = None model_config = ConfigDict(from_attributes=True) class SearchZone(BaseModel): """Search zone with priority and details""" priority: int = Field(..., ge=1, le=3) name: str direction: str distance_km: float score: float reasoning: str coordinates: Optional[List[List[float]]] = None class BehavioralProfile(BaseModel): """Active behavioral profile""" type: str title: str recommendations: List[str] modifiers: Dict[str, float] class AnalysisResult(BaseModel): """Complete analysis result""" case_id: UUID urgency_level: str = Field(..., pattern="^(КРИТИЧЕСКИЙ|ВЫСОКИЙ|УМЕРЕННЫЙ)$") urgency_reason: str time_window_hours: Optional[float] = None active_profiles: List[BehavioralProfile] immediate_actions: List[str] = Field(..., min_length=3, max_length=3) search_zones: List[SearchZone] = Field(..., min_length=1, max_length=3) key_objects: List[str] team_assignments: Optional[Dict[str, str]] = None behavioral_forecast: str dog_recommendations: Optional[List[str]] = None max_distance_km: float confidence_score: float = Field(..., ge=0, le=1) created_at: datetime execution_time: Optional[float] = None model_config = ConfigDict(json_schema_extra={ "example": { "case_id": "123e4567-e89b-12d3-a456-426614174000", "urgency_level": "ВЫСОКИЙ", "urgency_reason": "Ребёнок 8 лет, прошло 4 часа, температура +15°C", "time_window_hours": 12.0, "active_profiles": [ { "type": "age_8_12", "title": "Возраст 8-12 лет", "recommendations": ["Радиус поиска до 3 км", "Проверить дороги и тропы"], "modifiers": {"distance": 1.0, "roads": 1.2} } ], "immediate_actions": [ "Перекрыть все дороги в радиусе 2 км", "Проверить водоёмы в радиусе 1 км", "Организовать оклик по имени" ], "search_zones": [ { "priority": 1, "name": "Лесной массив северо-восток", "direction": "СВ", "distance_km": 1.2, "score": 0.85, "reasoning": "Последнее направление движения, густой лес" } ], "key_objects": ["Озеро Круглое (800м СВ)", "Лесная дорога (500м С)"], "behavioral_forecast": "Ребёнок скорее всего движется вдоль дороги или тропы", "max_distance_km": 3.5, "confidence_score": 0.82, "created_at": "2026-05-04T13:45:00Z" } }) class UserCreate(BaseModel): """Schema for creating a new user""" username: str = Field(..., min_length=3, max_length=100) email: str = Field(..., pattern=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") password: str = Field(..., min_length=8) full_name: Optional[str] = None role: str = Field(default="operator", pattern="^(operator|field|admin)$") class UserOut(BaseModel): """Schema for user output""" id: UUID username: str email: str full_name: Optional[str] = None role: str is_active: bool created_at: datetime last_login: Optional[datetime] = None model_config = ConfigDict(from_attributes=True)