"""AI Result Analysis router (P3-M4). Endpoints for AI-powered simulation result analysis, multi-fidelity calibration, confidence grading, and convergence checks. """ from typing import Dict, Any, Optional, List from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from ..services.result_analyst import get_result_analyst, FIDELITY_LEVELS router = APIRouter(prefix="/api/analysis", tags=["Result Analysis"]) class AnalyzeRequest(BaseModel): """Request for result analysis.""" results: List[Dict[str, Any]] = Field(..., description="List of simulation result dicts") targets: Optional[Dict[str, float]] = Field(default=None, description="Target values for key metrics") fidelity: str = Field(default="L3", description="Simulation fidelity level (L0-L4)") scan_parameters: Optional[List[str]] = Field(default=None, description="List of scanned parameter names") class CalibrateRequest(BaseModel): """Request for multi-fidelity calibration.""" metric: str = Field(..., description="Metric name (tavg_nm, efficiency_pct, etc.)") value: float = Field(..., description="Raw value from simulation") fidelity: str = Field(..., description="Fidelity level (L0-L4)") class UpdateCalibrationRequest(BaseModel): """Request to update calibration factors.""" low_fidelity_results: Dict[str, float] high_fidelity_results: Dict[str, float] low_fidelity: str high_fidelity: str = Field(default="L4") @router.post("/analyze") def analyze_results(request: AnalyzeRequest): """Analyze simulation results with AI and quantitative methods. Returns comprehensive analysis including: - Summary and key metrics - Target comparison - Multi-fidelity calibration - Six convergence criteria checks - Confidence grade (A-D) - AI-powered trend analysis and optimization suggestions """ if not request.results: raise HTTPException(status_code=400, detail="No results provided") if request.fidelity not in FIDELITY_LEVELS: raise HTTPException(status_code=400, detail=f"Invalid fidelity level: {request.fidelity}. Use L0-L4.") try: analyst = get_result_analyst() result = analyst.analyze( results=request.results, targets=request.targets, fidelity=request.fidelity, scan_parameters=request.scan_parameters, ) return result except Exception as e: raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}") @router.post("/calibrate") def calibrate_result(request: CalibrateRequest): """Calibrate a low-fidelity result to high-fidelity equivalent. Uses multi-fidelity calibration factors to correct known biases between different simulation fidelity levels. """ if request.fidelity not in FIDELITY_LEVELS: raise HTTPException(status_code=400, detail=f"Invalid fidelity level: {request.fidelity}") analyst = get_result_analyst() result = analyst.calibrator.calibrate(request.metric, request.value, request.fidelity) return result @router.post("/calibration/update") def update_calibration(request: UpdateCalibrationRequest): """Update calibration factors using paired low-high fidelity results. When you have both low-fidelity and high-fidelity results for the same design point, use this to refine the calibration factors. """ if request.low_fidelity not in FIDELITY_LEVELS: raise HTTPException(status_code=400, detail=f"Invalid low fidelity: {request.low_fidelity}") if request.high_fidelity not in FIDELITY_LEVELS: raise HTTPException(status_code=400, detail=f"Invalid high fidelity: {request.high_fidelity}") analyst = get_result_analyst() analyst.calibrator.update_calibration( low_fidelity_results=request.low_fidelity_results, high_fidelity_results=request.high_fidelity_results, low_fidelity=request.low_fidelity, high_fidelity=request.high_fidelity, ) return { "status": "updated", "current_factors": analyst.calibrator.calibration_factors, "n_calibration_points": len(analyst.calibrator.calibration_data), } @router.get("/fidelity-levels") def get_fidelity_levels(): """Get available fidelity levels and their properties.""" return { "levels": FIDELITY_LEVELS, "description": "Multi-fidelity hierarchy per third-party review. L0=analytic, L4=full 3D transient+thermal.", } @router.get("/confidence/scale") def get_confidence_scale(): """Get confidence grade scale definition.""" return { "grades": { "A": {"min_score": 85, "description": "High confidence. High fidelity, sufficient samples, converged, no anomalies."}, "B": {"min_score": 70, "description": "Medium-high confidence. Good fidelity, reasonable samples, mostly converged."}, "C": {"min_score": 50, "description": "Medium confidence. Lower fidelity or limited samples. Use with caution."}, "D": {"min_score": 0, "description": "Low confidence. Insufficient data or significant anomalies. Results unreliable."}, }, "scoring": { "fidelity": "40% (L4=40, L3=32, L2=24, L1=12, L0=4)", "sample_density": "25% (min(25, points_per_dimension * 2.5))", "convergence": "25% (passed_criteria / 6 * 25)", "anomaly_penalty": "-10% max (min(10, anomalies * 3))", }, } @router.get("/convergence/criteria") def get_convergence_criteria(): """Get six convergence criteria definitions.""" return { "criteria": [ {"id": 1, "name": "objective_stability", "description": "Objective function change rate < 1% over last 5 points"}, {"id": 2, "name": "optimum_stability", "description": "Optimum location stable over last 5 batches"}, {"id": 3, "name": "surrogate_error", "description": "Surrogate model prediction error < 5%"}, {"id": 4, "name": "constraint_satisfaction", "description": "Constraint satisfaction rate > 95%"}, {"id": 5, "name": "sample_density", "description": "Sample density > 10 points per dimension"}, {"id": 6, "name": "physical_consistency", "description": "All metrics within physically reasonable bounds"}, ], "convergence_threshold": ">= 4 of 6 criteria passed", }