Spaces:
Running
Running
| """ | |
| MediGuard AI — Production API Schemas | |
| Pydantic v2 request/response models for the new production API layer. | |
| Keeps backward compatibility with existing schemas where possible. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from pydantic import BaseModel, Field, field_validator | |
| # ============================================================================ | |
| # REQUEST MODELS | |
| # ============================================================================ | |
| class PatientContext(BaseModel): | |
| """Patient demographic and context information.""" | |
| age: int | None = Field(None, ge=0, le=120, description="Patient age in years") | |
| gender: str | None = Field(None, description="Patient gender (male/female)") | |
| bmi: float | None = Field(None, ge=10, le=60, description="Body Mass Index") | |
| patient_id: str | None = Field(None, description="Patient identifier") | |
| class NaturalAnalysisRequest(BaseModel): | |
| """Natural language biomarker analysis request.""" | |
| message: str = Field( | |
| ..., | |
| min_length=5, | |
| max_length=2000, | |
| description="Natural language message with biomarker values", | |
| ) | |
| patient_context: PatientContext | None = Field( | |
| default_factory=lambda: PatientContext(), | |
| ) | |
| class StructuredAnalysisRequest(BaseModel): | |
| """Structured biomarker analysis request.""" | |
| biomarkers: dict[str, float] = Field( | |
| ..., | |
| description="Dict of biomarker name → measured value", | |
| ) | |
| patient_context: PatientContext | None = Field( | |
| default_factory=lambda: PatientContext(), | |
| ) | |
| def biomarkers_not_empty(cls, v: dict[str, float]) -> dict[str, float]: | |
| if not v: | |
| raise ValueError("biomarkers must contain at least one entry") | |
| return v | |
| class AskRequest(BaseModel): | |
| """Free‑form medical question (agentic RAG pipeline).""" | |
| question: str = Field( | |
| ..., | |
| min_length=3, | |
| max_length=4000, | |
| description="Medical question", | |
| ) | |
| biomarkers: dict[str, float] | None = Field( | |
| None, | |
| description="Optional biomarker context", | |
| ) | |
| patient_context: str | None = Field( | |
| None, | |
| description="Free‑text patient context", | |
| ) | |
| class SearchRequest(BaseModel): | |
| """Direct hybrid search (no LLM generation).""" | |
| query: str = Field(..., min_length=2, max_length=1000) | |
| top_k: int = Field(10, ge=1, le=100) | |
| mode: str = Field("hybrid", description="Search mode: bm25 | vector | hybrid") | |
| class FeedbackRequest(BaseModel): | |
| """User feedback for RAG responses.""" | |
| request_id: str = Field(..., description="ID of the request being rated") | |
| score: float = Field(..., ge=0, le=1, description="Normalized score 0.0 to 1.0") | |
| comment: str | None = Field(None, description="Optional textual feedback") | |
| class FeedbackResponse(BaseModel): | |
| status: str = "success" | |
| request_id: str | |
| # ============================================================================ | |
| # RESPONSE BUILDING BLOCKS | |
| # ============================================================================ | |
| class BiomarkerFlag(BaseModel): | |
| name: str | |
| value: float | |
| unit: str | |
| status: str | |
| reference_range: str | |
| warning: str | None = None | |
| class SafetyAlert(BaseModel): | |
| severity: str | |
| biomarker: str | None = None | |
| message: str | |
| action: str | |
| class KeyDriver(BaseModel): | |
| biomarker: str | |
| value: Any | |
| contribution: str | None = None | |
| explanation: str | |
| evidence: str | None = None | |
| class Prediction(BaseModel): | |
| disease: str | |
| confidence: float = Field(ge=0, le=1) | |
| probabilities: dict[str, float] | |
| class DiseaseExplanation(BaseModel): | |
| pathophysiology: str | |
| citations: list[str] = Field(default_factory=list) | |
| retrieved_chunks: list[dict[str, Any]] | None = None | |
| class Recommendations(BaseModel): | |
| immediate_actions: list[str] = Field(default_factory=list) | |
| lifestyle_changes: list[str] = Field(default_factory=list) | |
| monitoring: list[str] = Field(default_factory=list) | |
| follow_up: str | None = None | |
| class ConfidenceAssessment(BaseModel): | |
| prediction_reliability: str | |
| evidence_strength: str | |
| limitations: list[str] = Field(default_factory=list) | |
| reasoning: str | None = None | |
| class AgentOutput(BaseModel): | |
| agent_name: str | |
| findings: Any | |
| metadata: dict[str, Any] | None = None | |
| execution_time_ms: float | None = None | |
| class Analysis(BaseModel): | |
| biomarker_flags: list[BiomarkerFlag] | |
| safety_alerts: list[SafetyAlert] | |
| key_drivers: list[KeyDriver] | |
| disease_explanation: DiseaseExplanation | |
| recommendations: Recommendations | |
| confidence_assessment: ConfidenceAssessment | |
| alternative_diagnoses: list[dict[str, Any]] | None = None | |
| # ============================================================================ | |
| # TOP‑LEVEL RESPONSES | |
| # ============================================================================ | |
| class AnalysisResponse(BaseModel): | |
| """Full clinical analysis response (backward‑compatible).""" | |
| status: str | |
| request_id: str | |
| timestamp: str | |
| extracted_biomarkers: dict[str, float] | None = None | |
| input_biomarkers: dict[str, float] | |
| patient_context: dict[str, Any] | |
| prediction: Prediction | |
| analysis: Analysis | |
| agent_outputs: list[AgentOutput] | |
| workflow_metadata: dict[str, Any] | |
| conversational_summary: str | None = None | |
| processing_time_ms: float | |
| sop_version: str | None = None | |
| class AskResponse(BaseModel): | |
| """Response from the agentic RAG /ask endpoint.""" | |
| status: str = "success" | |
| request_id: str | |
| question: str | |
| answer: str | |
| guardrail_score: float | None = None | |
| documents_retrieved: int = 0 | |
| documents_relevant: int = 0 | |
| processing_time_ms: float = 0.0 | |
| class SearchResponse(BaseModel): | |
| """Direct hybrid search response.""" | |
| status: str = "success" | |
| query: str | |
| mode: str | |
| total_hits: int | |
| results: list[dict[str, Any]] | |
| processing_time_ms: float = 0.0 | |
| class ErrorResponse(BaseModel): | |
| """Error envelope.""" | |
| status: str = "error" | |
| error_code: str | |
| message: str | |
| details: dict[str, Any] | None = None | |
| timestamp: str | |
| request_id: str | None = None | |
| # ============================================================================ | |
| # HEALTH / INFO | |
| # ============================================================================ | |
| class ServiceHealth(BaseModel): | |
| name: str | |
| status: str # ok | degraded | unavailable | |
| latency_ms: float | None = None | |
| detail: str | None = None | |
| class HealthResponse(BaseModel): | |
| """Production health check.""" | |
| status: str # healthy | degraded | unhealthy | |
| timestamp: str | |
| version: str | |
| uptime_seconds: float | |
| services: list[ServiceHealth] = Field(default_factory=list) | |
| class BiomarkerReferenceRange(BaseModel): | |
| min: float | None = None | |
| max: float | None = None | |
| male: dict[str, float] | None = None | |
| female: dict[str, float] | None = None | |
| class BiomarkerInfo(BaseModel): | |
| name: str | |
| unit: str | |
| normal_range: BiomarkerReferenceRange | |
| critical_low: float | None = None | |
| critical_high: float | None = None | |