clarke / backend /schemas.py
yashvshetty's picture
Restore all features from origin/fresh-main
874f913
Raw
History Blame
6.33 kB
"""Clarke data models — Pydantic v2 schemas for all system objects."""
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
class ConsultationStatus(str, Enum):
"""Status lifecycle for a consultation session."""
IDLE = "idle"
RECORDING = "recording"
PAUSED = "paused"
PROCESSING = "processing"
REVIEW = "review"
SIGNED_OFF = "signed_off"
class PipelineStage(str, Enum):
"""Discrete execution stages for the consultation pipeline."""
TRANSCRIBING = "transcribing"
RETRIEVING_CONTEXT = "retrieving_context"
GENERATING_DOCUMENT = "generating_document"
COMPLETE = "complete"
FAILED = "failed"
class Patient(BaseModel):
"""A patient in the clinic list."""
id: str = Field(description="FHIR Patient resource ID")
nhs_number: str = Field(description="NHS number (format: XXX XXX XXXX)")
name: str = Field(description="Full name (e.g., 'Mrs. Margaret Thompson')")
date_of_birth: str = Field(description="DOB in DD/MM/YYYY format")
age: int
sex: str = Field(description="'Male' or 'Female'")
appointment_time: str = Field(description="HH:MM format")
summary: str = Field(description="One-line clinical summary for dashboard card")
class LabResult(BaseModel):
"""A single laboratory result with trend."""
name: str = Field(description="e.g., 'HbA1c'")
value: str = Field(description="e.g., '55'")
unit: str = Field(description="e.g., 'mmol/mol'")
reference_range: Optional[str] = Field(default=None, description="e.g., '20-42'")
date: str = Field(description="ISO date of result")
trend: Optional[str] = Field(default=None, description="'rising', 'falling', 'stable', or None")
previous_value: Optional[str] = Field(default=None, description="Previous result value")
previous_date: Optional[str] = Field(default=None)
fhir_resource_id: Optional[str] = Field(default=None, description="Source FHIR Observation ID")
class PatientContext(BaseModel):
"""Structured patient context synthesised by the EHR Agent from FHIR data."""
patient_id: str
demographics: dict = Field(description="name, dob, nhs_number, age, sex, address")
problem_list: list[str] = Field(description="Active diagnoses, e.g., ['Type 2 Diabetes Mellitus (2019)', ...]")
medications: list[dict] = Field(
description="[{'name': 'Metformin', 'dose': '1g', 'frequency': 'BD', 'fhir_id': '...'}]"
)
allergies: list[dict] = Field(
description="[{'substance': 'Penicillin', 'reaction': 'Anaphylaxis', 'severity': 'high'}]"
)
recent_labs: list[LabResult] = Field(default_factory=list)
recent_imaging: list[dict] = Field(default_factory=list, description="[{'type': 'CXR', 'date': '...', 'summary': '...'}]")
clinical_flags: list[str] = Field(default_factory=list, description="['HbA1c rising trend over 6 months']")
last_letter_excerpt: Optional[str] = Field(default=None, description="Key excerpt from most recent clinic letter")
retrieval_warnings: list[str] = Field(default_factory=list, description="Warnings if some FHIR queries failed")
retrieved_at: str = Field(description="ISO timestamp of retrieval")
class Transcript(BaseModel):
"""Consultation transcript produced by MedASR."""
consultation_id: str
text: str = Field(description="Full transcript text")
duration_s: float = Field(description="Audio duration in seconds")
word_count: int
created_at: str
class DocumentSection(BaseModel):
"""A single section of the generated clinical letter."""
heading: str = Field(description="e.g., 'History of presenting complaint'")
content: str = Field(description="Section body text")
editable: bool = Field(default=True)
fhir_sources: list[str] = Field(default_factory=list, description="FHIR resource IDs cited in this section")
class ClinicalDocument(BaseModel):
"""A generated NHS clinical letter."""
consultation_id: str
letter_date: str
patient_name: str
patient_dob: str
nhs_number: str
addressee: str = Field(description="GP name and address")
salutation: str = Field(description="e.g., 'Dear Dr. Patel,'")
sections: list[DocumentSection]
medications_list: list[str] = Field(description="Current medications (formatted)")
sign_off: str = Field(description="e.g., 'Dr. S. Chen, Consultant Diabetologist'")
status: ConsultationStatus = ConsultationStatus.REVIEW
generated_at: str
generation_time_s: float = Field(description="Time taken for MedGemma 27B inference")
discrepancies: list[dict] = Field(default_factory=list, description="[{'type': 'allergy_mismatch', 'detail': '...'}]")
class Consultation(BaseModel):
"""A complete consultation session — links patient, transcript, context, and document."""
id: str = Field(description="Unique consultation ID (UUID)")
patient: Patient
status: ConsultationStatus = ConsultationStatus.IDLE
pipeline_stage: Optional[PipelineStage] = None
context: Optional[PatientContext] = None
transcript: Optional[Transcript] = None
document: Optional[ClinicalDocument] = None
started_at: Optional[str] = None
ended_at: Optional[str] = None
audio_file_path: Optional[str] = None
doc_type: str = Field(default="Clinic Letter", description="Document type: 'Clinic Letter' or 'Ward Round Note'")
letter_prefs: dict = Field(default_factory=dict, description="Letter preferences from frontend (clinician name, GP, etc.)")
class PipelineProgress(BaseModel):
"""Real-time pipeline progress updates pushed to the UI."""
consultation_id: str
stage: PipelineStage
progress_pct: int = Field(ge=0, le=100)
message: str = Field(description="Human-readable status, e.g., 'Finalising transcript...'")
class ErrorResponse(BaseModel):
"""Standardised error response format."""
error: str = Field(description="Error category: 'model_error', 'fhir_error', 'audio_error', 'timeout'")
message: str = Field(description="Human-readable error message for UI display")
detail: Optional[str] = Field(default=None, description="Technical detail (logged, not shown to user)")
consultation_id: Optional[str] = None
timestamp: str