feat: завершённые поиски — ручной ввод + детерминированный прогон модели (без ИИ)
Бэкенд:
- schemas.py: ClosedCaseCreate/Response — форма ~25 полей (субъект, среда,
ресурсы, исход) для внесения завершённых поисков вручную
- routers/closed_cases.py: CRUD + POST {id}/reanalyze. _run_model() —
тот же скоринг, что /analyze (WeightedScorer + distance_service +
психотип), НО БЕЗ claude_service: urgency/behavior/actions по правилам
- _accuracy_note(): прогнозный радиус vs факт (в радиусе / мимо)
- main.py: роутер подключён
Фронтенд:
- pages/ClosedCases.jsx + .css: форма внесения (VCL-density, чипы диагнозов/
рельефа), блок 'Прогноз vs факт', таблица архива (в радиусе/мимо)
- AdminDashboard.jsx: вкладки Кейсы | Завершённые поиски
Тесты: test_closed_cases.py — 6 passed. Существующие 15 failed
(test_distance_service) не связаны с изменениями — падали до них.
This commit is contained in:
@@ -138,3 +138,66 @@ class ParseDocResponse(BaseModel):
|
||||
parsed: bool
|
||||
preview: dict[str, Any]
|
||||
raw_text: str | None = None
|
||||
|
||||
|
||||
# ================= ЗАВЕРШЁННЫЕ ПОИСКИ (ручной ввод, без ИИ) =================
|
||||
# Оператор руками вносит данные ЗАВЕРШЁННОГО поиска: параметры субъекта/среды
|
||||
# (вход матмодели) + реальный исход. Цель — датасет для калибровки модели
|
||||
# (сравнение прогноза ВЕКТОРА с фактом) и архив 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
|
||||
|
||||
Reference in New Issue
Block a user