Fix admin import chain and tests

This commit is contained in:
root
2026-06-06 20:59:23 +00:00
parent fcc22063ed
commit 3548733019
15 changed files with 1818 additions and 1193 deletions
+110 -239
View File
@@ -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