T0X1N's picture
chore: codebase audit and fixes (ruff, mypy, pytest)
9659593
"""
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(),
)
@field_validator("biomarkers")
@classmethod
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