"""FastAPI application entrypoint (API-01..09). Run with: `uvicorn app.main:app --host 0.0.0.0 --port $APP_PORT` The model + SHAP explainer are loaded exactly once during the lifespan startup hook (EXPL-04) and stored on `app.state.model_bundle` -- every request reuses the same in-memory objects, never reloading per-request. """ from __future__ import annotations from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from app.auth import seed_officer_if_missing from app.config import settings from app.db.session import session_scope from app.routers import auth, client, health, officer, pages, predict from app.scoring import load_model_bundle DESCRIPTION = """ Score banking transactions for fraud risk in real time. Every prediction returns a probability, a low/medium/high risk tier, and the top SHAP features driving that specific score -- not just a black-box number. Predictions and alerts are persisted to MySQL. Two role-scoped surfaces sit on top of the same scoring pipeline: banking officers manage clients and review the fraud alert queue (`/officer/*`), clients submit and review their own transactions (`/client/*`). See **/guide** for a plain-language walkthrough of the scoring pipeline. """ @asynccontextmanager async def lifespan(app: FastAPI): app.state.model_bundle = load_model_bundle(settings.MODEL_PATH) with session_scope() as db: seed_officer_if_missing(db) yield app = FastAPI( title="Banking Fraud Detection API", description=DESCRIPTION, version="1.0.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.mount("/static", StaticFiles(directory="app/static"), name="static") app.mount("/reports", StaticFiles(directory="reports"), name="reports") app.include_router(health.router) app.include_router(auth.router) app.include_router(predict.router) app.include_router(officer.router) app.include_router(client.router) app.include_router(pages.router)