Fix admin import chain and tests
This commit is contained in:
+110
-239
@@ -1,258 +1,129 @@
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
age: int | None = None
|
||||
gender: str | None = None
|
||||
health_flags: list[str] = Field(default_factory=list)
|
||||
elapsed_hours: int | None = None
|
||||
last_known_place: str | None = None
|
||||
direction: str | None = None
|
||||
terrain_primary: str | None = None
|
||||
weather: str | None = None
|
||||
resources: list[str] = Field(default_factory=list)
|
||||
gps: dict[str, float] | None = None
|
||||
notes: str | None = None
|
||||
note: str | None = None
|
||||
status: str = 'new'
|
||||
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
|
||||
who_found: str | None = None
|
||||
mobile: bool = False
|
||||
|
||||
|
||||
class CaseOut(BaseModel):
|
||||
"""Schema for case output"""
|
||||
class CaseUpdate(BaseModel):
|
||||
age: int | None = None
|
||||
gender: str | None = None
|
||||
health_flags: list[str] | None = None
|
||||
elapsed_hours: int | None = None
|
||||
last_known_place: str | None = None
|
||||
direction: str | None = None
|
||||
terrain_primary: str | None = None
|
||||
weather: str | None = None
|
||||
resources: list[str] | None = None
|
||||
gps_lat: float | None = None
|
||||
gps_lon: float | None = None
|
||||
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
|
||||
who_found: str | None = None
|
||||
raw_text: str | None = None
|
||||
analysis_log: str | None = None
|
||||
|
||||
id: UUID
|
||||
|
||||
class CaseResponse(BaseModel):
|
||||
id: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
age: int | None = None
|
||||
gender: str | None = None
|
||||
health_flags: list[str]
|
||||
elapsed_hours: int | None = None
|
||||
last_known_place: str | None = None
|
||||
direction: str | None = None
|
||||
terrain_primary: str | None = None
|
||||
weather: str | None = None
|
||||
resources: list[str]
|
||||
gps_lat: float | None = None
|
||||
gps_lon: float | None = None
|
||||
notes: str | None = None
|
||||
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)
|
||||
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
|
||||
who_found: str | None = None
|
||||
raw_text: str | None = None
|
||||
analysis_log: str | None = None
|
||||
result: dict[str, Any] | None = None
|
||||
|
||||
|
||||
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 CaseListResponse(BaseModel):
|
||||
items: list[CaseResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
class BehavioralProfile(BaseModel):
|
||||
"""Active behavioral profile"""
|
||||
type: str
|
||||
title: str
|
||||
recommendations: List[str]
|
||||
modifiers: Dict[str, float]
|
||||
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
|
||||
|
||||
|
||||
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 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 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 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
|
||||
|
||||
|
||||
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)
|
||||
class ParseDocResponse(BaseModel):
|
||||
filename: str
|
||||
parsed: bool
|
||||
preview: dict[str, Any]
|
||||
raw_text: str | None = None
|
||||
|
||||
Reference in New Issue
Block a user