from __future__ import annotations from datetime import datetime 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.claude_service import analyze_case as claude_analyze from services.distance_service import calculate_max_distance from services.psychotype_service import ( detect_psychotype, get_psychotype_modifiers, get_search_recommendations, ) from services.scoring_service import WeightedScorer 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) 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) def _first_terrain(value: str | list[str] | None) -> str | None: if isinstance(value, list): return value[0] if value else None return value def _as_case_data(payload: AnalysisRequest) -> dict[str, Any]: terrain = _first_terrain(payload.terrain) case_data: dict[str, Any] = { 'age': payload.age, 'gender': payload.gender, 'terrain': terrain, 'terrain_primary': 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, 'profiles': list(payload.profiles or []), '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, } return {k: v for k, v in case_data.items() if v is not None} @router.post('', dependencies=[Depends(require_roles(['operator', 'field', 'admin']))]) async def analyze_case(payload: AnalysisRequest) -> dict[str, Any]: case_data = _as_case_data(payload) 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} max_distance_km = calculate_max_distance(case_data) claude_result = await claude_analyze(case_data) scorer = WeightedScorer() if case_data.get('age'): scorer.apply_age_modifiers(int(case_data['age'])) if case_data.get('season'): scorer.apply_season_modifiers(str(case_data['season'])) if case_data.get('profiles'): scorer.apply_profile(list(case_data['profiles'])) scorer._normalize_weights() psychotype = None psychotype_modifiers = None psychotype_recommendations = None if case_data.get('psychotype_answers'): psychotype = detect_psychotype(case_data['psychotype_answers']) psychotype_modifiers = get_psychotype_modifiers(psychotype) psychotype_recommendations = get_search_recommendations(psychotype) result = { 'case_id': str(payload.case_id) if payload.case_id else None, 'analyzed_at': datetime.utcnow().isoformat(), 'max_distance_km': max_distance_km, 'psychotype': psychotype, 'psychotype_modifiers': psychotype_modifiers, 'psychotype_recommendations': psychotype_recommendations, 'weights': scorer.weights, 'distance_multiplier': scorer.distance_multiplier, 'urgency': claude_result.urgency, 'primary_zones': [zone.model_dump() for zone in claude_result.primary_zones], 'search_radius_km': claude_result.search_radius_km, 'key_locations': claude_result.key_locations, 'behavioral_prediction': claude_result.behavioral_prediction, 'immediate_actions': claude_result.immediate_actions, 'summary': claude_result.summary, 'fallback_used': claude_result.fallback_used, } 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'], }