import json from fastapi import APIRouter, Depends, HTTPException from app.database import get_db from app.feature_engineering import normalize_object from app.services import score_pair from app.repository import list_high_risk_pairs, get_pair, get_pair_history, pairs_in_same_shell, object_pairs router=APIRouter(prefix="/api/v1", tags=["pairs"]) @router.post("/score/pair") def score_pair_route(payload:dict, db=Depends(get_db)): return score_pair(db, normalize_object(payload["primary"]), normalize_object(payload["secondary"])) @router.get("/pairs/high-risk") def high_risk(limit:int=50, db=Depends(get_db)): rows=list_high_risk_pairs(db, limit) return [{"pair_id":r.pair_id,"risk_score":r.risk_score,"anomaly_score":r.anomaly_score,"final_score":r.final_score,"risk_label":r.risk_label,"recurrence_count":r.recurrence_count,"trend_delta_24h":r.trend_delta_24h,"shell_key":r.shell_key,"top_factors":json.loads(r.top_factors_json)} for r in rows] @router.get("/pairs/{pair_id}") def pair_detail(pair_id:str, db=Depends(get_db)): row=get_pair(db, pair_id) if not row: raise HTTPException(status_code=404, detail="Pair not found") payload=json.loads(row.feature_payload_json) return {"pair_id":row.pair_id,"primary_object_id":row.primary_object_id,"secondary_object_id":row.secondary_object_id,"risk_score":row.risk_score,"anomaly_score":row.anomaly_score,"final_score":row.final_score,"risk_label":row.risk_label,"recurrence_count":row.recurrence_count,"trend_delta_24h":row.trend_delta_24h,"shell_key":row.shell_key,"top_factors":json.loads(row.top_factors_json),"features":payload,"analyst_summary":payload.get("analyst_summary",""),"structured_explanation":payload.get("structured_explanation",{})} @router.get("/pairs/{pair_id}/history") def pair_history(pair_id:str, limit:int=20, db=Depends(get_db)): rows=get_pair_history(db, pair_id, limit) return [{"history_id":r.history_id,"run_id":r.run_id,"risk_score":r.risk_score,"anomaly_score":r.anomaly_score,"final_score":r.final_score,"created_at":r.created_at} for r in rows] @router.get("/pairs/{pair_id}/neighbors") def pair_neighbors(pair_id:str, limit:int=20, db=Depends(get_db)): row=get_pair(db, pair_id) if not row: raise HTTPException(status_code=404, detail="Pair not found") shell_rows=pairs_in_same_shell(db, row.shell_key or "unknown", row.pair_id, limit) object_related=object_pairs(db, row.primary_object_id, limit)+object_pairs(db, row.secondary_object_id, limit) seen={pair_id}; related=[] for r in shell_rows+object_related: if r.pair_id in seen: continue seen.add(r.pair_id); related.append({"pair_id":r.pair_id,"final_score":r.final_score,"risk_label":r.risk_label,"top_factors":json.loads(r.top_factors_json)}) if len(related)>=limit: break return related