259 lines
8.5 KiB
Python
259 lines
8.5 KiB
Python
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)
|