P1 auth, CORS, and SQL filtering
This commit is contained in:
+142
-4
@@ -1,8 +1,146 @@
|
||||
from fastapi import APIRouter
|
||||
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'])
|
||||
|
||||
|
||||
@router.post('')
|
||||
def analyze_stub() -> dict:
|
||||
return {'status': 'ok'}
|
||||
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'],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user