Spaces:
Sleeping
Sleeping
| """MuleGuard scoring API. | |
| Run: .venv/bin/uvicorn src.api.main:app --port 8000 | |
| """ | |
| from __future__ import annotations | |
| from datetime import datetime, timezone | |
| from typing import Any | |
| import pandas as pd | |
| from fastapi import FastAPI | |
| from pydantic import BaseModel, Field | |
| from src import config | |
| from src.models.scoring import explain_row, load_artifacts, narrative, score_frame | |
| app = FastAPI(title="MuleGuard — Mule Account Risk Scoring", version="1.0") | |
| class ScoreRequest(BaseModel): | |
| account_id: str = Field(..., description="Account identifier") | |
| features: dict[str, Any] = Field(..., description="Raw feature map (F1..F3923); missing allowed") | |
| class ScoreResponse(BaseModel): | |
| account_id: str | |
| risk_score: float | |
| risk_tier: str | |
| decision: str | |
| probability: float | |
| reason_codes: list[dict] | |
| narrative: str | |
| timestamp: str | |
| def _warmup() -> None: | |
| load_artifacts() # load model + explainers once | |
| def health() -> dict: | |
| art = load_artifacts() | |
| return {"status": "ok", "n_features": len(art.features), "threshold": art.threshold} | |
| def model_info() -> dict: | |
| art = load_artifacts() | |
| m = art.metadata | |
| return { | |
| "model": m["model"], | |
| "n_features": m["n_features"], | |
| "leakage_excluded": m["leakage_excluded"], | |
| "cv_metrics": m["cv_metrics"], | |
| "test_metrics": m["test_metrics"], | |
| "threshold": art.threshold, | |
| } | |
| def score(req: ScoreRequest) -> ScoreResponse: | |
| row = pd.DataFrame([req.features]) | |
| scored = score_frame(row).iloc[0] | |
| reasons = explain_row(row) | |
| return ScoreResponse( | |
| account_id=req.account_id, | |
| risk_score=float(scored["risk_score"]), | |
| risk_tier=str(scored["risk_tier"]), | |
| decision=str(scored["decision"]), | |
| probability=float(scored["probability"]), | |
| reason_codes=reasons, | |
| narrative=narrative(float(scored["risk_score"]), reasons), | |
| timestamp=datetime.now(timezone.utc).isoformat(), | |
| ) | |