faisalAI27
setting up project for deployment
8f3f74c
Raw
History Blame Contribute Delete
4.51 kB
from __future__ import annotations
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from app.core.config import get_settings
from app.schemas import AnalyzeRequest, AnalyzeResponse, HealthResponse
from app.services.explanation_service import generate_explanation
from app.services.model_service import ModelService
settings = get_settings()
model_service = ModelService(settings)
static_dir = Path(__file__).resolve().parents[1] / "static"
app = FastAPI(
title="Variant Risk Explainer API",
version="0.2.0",
description="Research-only FastAPI backend for DNABERT-2 ClinVar variant risk exploration.",
)
app.add_middleware(
CORSMiddleware,
allow_origins=list(settings.allowed_origins),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/api")
def root() -> dict[str, str]:
return {
"message": "Variant Risk Explainer API",
"model": settings.model_name,
"documentation": "/docs",
}
@app.get("/api/health", response_model=HealthResponse)
@app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
openai_configured = bool(settings.openai_api_key)
explanation_mode = "openai" if settings.use_openai_explanation and openai_configured else "rule-based"
return HealthResponse(
status="ok" if model_service.model_loaded else "degraded",
model_loaded=model_service.model_loaded,
device=model_service.device,
model_dir=str(model_service.model_dir),
threshold=model_service.threshold,
model_name=model_service.model_name,
explanation_mode=explanation_mode,
ai_explanation_enabled=settings.use_openai_explanation,
openai_configured=openai_configured,
load_error=model_service.load_error,
)
@app.post("/api/analyze", response_model=AnalyzeResponse)
@app.post("/analyze", response_model=AnalyzeResponse)
def analyze(request: AnalyzeRequest) -> AnalyzeResponse:
try:
prediction = model_service.predict(request.sequence)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except RuntimeError as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
except Exception as exc: # pragma: no cover - defensive boundary for inference failures.
raise HTTPException(status_code=500, detail=f"Prediction failed: {type(exc).__name__}: {exc}") from exc
explanation = generate_explanation(
prediction_class=prediction.prediction_class,
prediction_label=prediction.prediction_label,
risk_level=prediction.risk_level,
benign_probability=prediction.benign_probability,
pathogenic_probability=prediction.pathogenic_probability,
threshold=prediction.threshold,
variant_name=request.variant_name,
gene=request.gene,
sequence_length_used=prediction.sequence_length_used,
use_openai=settings.use_openai_explanation,
openai_api_key=settings.openai_api_key,
openai_model=settings.openai_explanation_model,
openai_timeout=settings.openai_explanation_timeout,
)
return AnalyzeResponse(
variant_name=request.variant_name,
gene=request.gene,
prediction_class=prediction.prediction_class,
prediction_label=prediction.prediction_label,
risk_level=prediction.risk_level,
benign_probability=prediction.benign_probability,
pathogenic_probability=prediction.pathogenic_probability,
threshold=prediction.threshold,
model_name=prediction.model_name,
sequence_length_used=prediction.sequence_length_used,
explanation=explanation["explanation"],
explanation_source=explanation["explanation_source"],
confidence_level=explanation["confidence_level"],
recommendation=explanation["recommendation"],
limitations=explanation["limitations"],
disclaimer=prediction.disclaimer,
)
if static_dir.exists():
app.mount("/", StaticFiles(directory=static_dir, html=True), name="frontend")
else:
@app.get("/")
def local_root() -> dict[str, str]:
return {
"message": "Variant Risk Explainer API",
"documentation": "/docs",
"frontend": "Static frontend not built. Run the Next.js frontend separately for local development.",
}