Spaces:
Sleeping
Sleeping
File size: 6,012 Bytes
5bad2b7 a35719d 5bad2b7 a35719d 5bad2b7 a35719d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
import logging
import httpx
import os
from app.config import settings
from app.models import GenerateRequest, AIResponse, GuidelineSource, SafetyAssessment, BehavioralInsight
from app.services.ml_client import HFMLClient
from app.services.who_africa_rag import WhoAfricaRAG
from app.services.safety_engine import SafetyEngine
from app.services.rag_service import RAGService
logging.basicConfig(level=getattr(logging, settings.LOG_LEVEL, logging.INFO))
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.ml = HFMLClient()
app.state.who_africa = WhoAfricaRAG()
app.state.safety = SafetyEngine()
app.state.rag = RAGService()
logger.info("✅ DACM AI Service ready")
yield
app = FastAPI(title="DACM AI", lifespan=lifespan)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
# FIXED: Changed from /api/v1/generate to /api/v1/advice to match frontend
@app.post("/api/v1/advice", response_model=AIResponse)
async def generate(req: GenerateRequest):
# 1. Safety Check
safety = app.state.safety.check(req.user_profile.dict())
if safety["flagged"] and safety["risk_category"] == "critical":
return AIResponse(
answer=safety["message"], confidence=1.0, sources=[],
safety=SafetyAssessment(**safety), suggested_methods=[], contraindicated_methods=[]
)
# 2. RAG (ChromaDB + WHO MEC)
guidelines = app.state.rag.search(req.query, req.user_profile.dict(), k=2)
rag_context = "\n".join([f"[{g['category']}] {g['title']}: {g['content']}" for g in guidelines])
# 3. WHO Africa Regional Context
regional_context = []
regional_text = ""
if req.user_profile.country_iso3:
regional_context = app.state.who_africa.search(country_iso3=req.user_profile.country_iso3, top_k=2)
if regional_context:
regional_text = "\n\n🌍 Regional Context: " + "; ".join([f"{d['metadata']['method']} ({d['metadata']['prevalence_percent']}%)" for d in regional_context])
# 4. HF ML Satisfaction Prediction
ml_result = {}
try:
ml_result = app.state.ml.predict(req.query)
except Exception as e:
logger.warning(f"ML fallback: {e}")
# 5. LLM Prompt with FULL Clinical Data
prompt = f"""CLINICAL GUIDELINES (WHO MEC):
{rag_context}
USER CLINICAL PROFILE:
- Age: {req.user_profile.age}
- Breastfeeding: {req.user_profile.breastfeeding}
- Hypertension: {req.user_profile.hypertension}
- Smoking: {req.user_profile.smoking_status}
- Blood Clots History: {req.user_profile.history_of_clots}
- Migraines: {req.user_profile.migraines}
- Diabetes: {req.user_profile.diabetes}
- Pregnancy Intention: {req.user_profile.pregnancy_intention}
- Previous Method: {req.user_profile.previous_method or 'None'}
- Side Effects History: {req.user_profile.side_effects_history or 'None'}
- STI Protection Needed: {req.user_profile.sti_protection_needed}
- Number of Children: {req.user_profile.number_of_children or 'Not specified'}
- Country: {req.user_profile.country_iso3 or 'N/A'}
QUERY: {req.query}
INSTRUCTIONS: Provide a clear, medically accurate contraceptive recommendation based strictly on WHO MEC guidelines. Prioritize safety contraindications first. Address STI needs if flagged. Align suggestions with pregnancy intention. Cite specific MEC categories. Keep response empathetic and professional."""
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(
f"{settings.BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {settings.API_KEY}", "Content-Type": "application/json"},
json={"model": settings.AI_MODEL, "messages": [{"role": "user", "content": prompt}], "temperature": 0.3}
)
resp.raise_for_status()
llm_answer = resp.json()["choices"][0]["message"]["content"]
except Exception as e:
logger.error(f"LLM generation failed: {e}")
llm_answer = "Based on WHO guidelines, please consult a healthcare provider for personalized contraceptive advice."
answer = llm_answer + regional_text
if ml_result.get("satisfaction_probability", 0) > 0.7:
answer += f"\n\n💡 User Experience Insight: {ml_result['interpretation'].capitalize()} predicted satisfaction ({ml_result['satisfaction_probability']*100:.0f}%) based on similar user reviews."
sources_list = [GuidelineSource(**g) for g in guidelines]
if regional_context:
for d in regional_context:
sources_list.append(GuidelineSource(title=d["title"], category="WHO Africa Prevalence", content="", relevance_score=0.6))
contraindicated = []
if any(g["category"] in ["WHO MEC 3", "WHO MEC 4"] for g in guidelines):
contraindicated.append("combined_hormonal_methods")
return AIResponse(
answer=answer,
confidence=0.85,
sources=sources_list,
safety=SafetyAssessment(**safety),
suggested_methods=["progestin_only_pill", "implant", "copper_iud", "condoms"],
contraindicated_methods=contraindicated,
behavioral_insight=BehavioralInsight(**ml_result) if ml_result and "satisfaction_probability" in ml_result else None,
regional_context=regional_context if regional_context else None
)
@app.get("/health")
def health():
return {"status": "ok", "services": ["rag", "safety", "ml", "who_africa"]}
if __name__ == "__main__":
import uvicorn
# FIXED: Use PORT env var for HF Spaces (7860) or fallback to settings.AI_PORT (8001) for local
port = int(os.getenv("PORT", settings.AI_PORT))
uvicorn.run("app.main:app", host=settings.HOST, port=port, reload=True) |