from __future__ import annotations from datetime import datetime, timezone from typing import Any from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from backend.database import db from backend.routers.auth import require_roles from services.rules_analysis import rules_analyze from services.search_engine import ( SearchInput, build_search_model, ) router = APIRouter(prefix='/api/v1/analyze', tags=['analyze']) class AnalysisRequest(BaseModel): case_id: UUID | None = None age: int | None = None gender: str | None = None terrain: str | list[str] | None = None weather: str | None = None elapsed_hours: float | None = None last_location: str | None = None circumstances: str | None = None physical_condition: str | None = None experience: str | None = None season: str | None = None diagnosis_type: list[str] = Field(default_factory=list) has_transport: str | None = None psychotype_answers: dict[str, Any] = Field(default_factory=dict) tnp_lat: float | None = None tnp_lon: float | None = None lat: float | None = None lon: float | None = None profiles: list[str] = Field(default_factory=list) @router.post('', dependencies=[Depends(require_roles(['operator', 'field', 'admin']))]) async def analyze_case(payload: AnalysisRequest) -> dict[str, Any]: # B14: роутер — тонкая обёртка. Вся расчётная логика — в # services/search_engine.py::build_search_model (без DB/auth/HTTP). case_data: dict[str, Any] = { 'age': payload.age, 'gender': payload.gender, 'terrain': payload.terrain, 'weather': payload.weather, 'elapsed_hours': payload.elapsed_hours, 'last_location': payload.last_location, 'circumstances': payload.circumstances, 'physical_condition': payload.physical_condition, 'experience': payload.experience, 'season': payload.season, 'diagnosis_type': payload.diagnosis_type, 'has_transport': payload.has_transport, 'psychotype_answers': payload.psychotype_answers, 'lat': payload.lat if payload.lat is not None else payload.tnp_lat, 'lon': payload.lon if payload.lon is not None else payload.tnp_lon, } case_data = { k: v for k, v in case_data.items() if v is not None and not (isinstance(v, (list, dict)) and len(v) == 0) } if payload.case_id is not None: case = db.get_case(str(payload.case_id)) if not case: raise HTTPException(status_code=404, detail='Case not found') case_data = {**case.to_detail(), **case_data} # Карточка хранит координаты в tnp_lat/tnp_lon (gps_lat/lon — алиасы # to_detail). Без этой подстройки case_id-режим терял координаты ТНП: # rules_analyze не строил geo-зоны и отдавал fallback «Ближняя N / # Средняя E» — источник «вверх-вправо» на карте. if case_data.get('lat') is None: case_data['lat'] = case_data.get('tnp_lat') or case_data.get('gps_lat') if case_data.get('lon') is None: case_data['lon'] = case_data.get('tnp_lon') or case_data.get('gps_lon') search_input = SearchInput( age=case_data.get('age'), gender=case_data.get('gender'), terrain=case_data.get('terrain'), terrain_primary=case_data.get('terrain_primary'), weather=case_data.get('weather'), elapsed_hours=case_data.get('elapsed_hours'), last_location=case_data.get('last_location'), circumstances=case_data.get('circumstances'), physical_condition=case_data.get('physical_condition'), experience=case_data.get('experience'), season=case_data.get('season'), diagnosis_type=case_data.get('diagnosis_type') or [], has_transport=case_data.get('has_transport'), cant_swim=bool(case_data.get('cant_swim')), psychotype_answers=case_data.get('psychotype_answers') or {}, profiles=case_data.get('profiles') or [], lat=case_data.get('lat'), lon=case_data.get('lon'), loss_time=case_data.get('loss_time'), time_of_day=case_data.get('time_of_day'), ) model = await build_search_model(search_input, analyzer=rules_analyze) result = model.model_dump() result['case_id'] = str(payload.case_id) if payload.case_id else None result['analyzed_at'] = datetime.now(timezone.utc).isoformat() if payload.case_id is not None: db.update_case(str(payload.case_id), analysis_log=result, status='analyzed') return result @router.post('/combined', dependencies=[Depends(require_roles(['operator', 'field', 'admin']))]) async def analyze_combined(payload: AnalysisRequest) -> dict[str, Any]: return await analyze_case(payload) @router.get('/{case_id}') def get_analysis(case_id: str) -> dict[str, Any]: case = db.get_case(case_id) if not case: raise HTTPException(status_code=404, detail='Case not found') detail = case.to_detail() if not detail.get('analysis_log'): raise HTTPException(status_code=404, detail=f'No analysis found for case {case_id}') return { 'case_id': case_id, 'analysis_log': detail['analysis_log'], 'created_at': detail['created_at'], }