"""Pydantic v2 schema for the ATLS primary-survey output. This is the contract the Drafter writes to via vLLM guided JSON decoding. `TriageOutput.model_json_schema()` is fed directly to the server as the `guided_json` constraint, so every `Field(description=...)` here doubles as a hint to the model. """ from __future__ import annotations from typing import Literal from pydantic import BaseModel, Field, field_validator ConcernLevel = Literal["low", "moderate", "high", "critical"] Severity = Literal["mild", "moderate", "severe"] Laterality = Literal["left", "right", "bilateral", "midline", "n/a"] Priority = Literal["immediate", "urgent", "delayed", "expectant"] Urgency = Literal["now", "<5min", "<15min", "<1hr"] Confidence = Literal["high", "moderate", "low"] Sex = Literal["M", "F"] class PatientBrief(BaseModel): """Three-line patient summary at the top of the output.""" age: int = Field(..., description="Patient age in years.", ge=0, le=120) sex: Sex = Field(..., description="Biological sex: 'M' or 'F'.") mechanism: str = Field( ..., description="Mechanism of injury, one short clause (e.g. 'motorbike vs car, ejected').", ) class ImagingFinding(BaseModel): """One finding extracted from the chest X-ray.""" finding: str = Field(..., description="Concise finding, e.g. 'tension pneumothorax'.") laterality: Laterality = Field( ..., description="Side of the finding. Use 'n/a' for findings without a side.", ) severity: Severity = Field(..., description="Clinical severity: mild / moderate / severe.") citation_id: str | None = Field( default=None, description="ID of a retrieved guideline chunk that supports interpreting this finding.", ) class AirwayAssessment(BaseModel): """A — Airway.""" concern_level: ConcernLevel = Field(..., description="Overall concern for airway.") imaging_findings: list[ImagingFinding] = Field( default_factory=list, description="Imaging findings relevant to airway (e.g. tracheal deviation).", ) actions_required: list[str] = Field( default_factory=list, description="Concrete airway actions in order of urgency.", ) class BreathingAssessment(BaseModel): """B — Breathing and ventilation.""" concern_level: ConcernLevel = Field(..., description="Overall concern for breathing.") rr: int | None = Field(default=None, description="Respiratory rate (breaths per minute).") spo2: int | None = Field(default=None, description="Pulse oximetry, percent.") imaging_findings: list[ImagingFinding] = Field( default_factory=list, description="Imaging findings relevant to breathing (pneumothorax, hemothorax, etc.).", ) actions_required: list[str] = Field( default_factory=list, description="Breathing actions, e.g. needle decompression, chest tube, supplemental O2.", ) class CirculationAssessment(BaseModel): """C — Circulation with hemorrhage control.""" concern_level: ConcernLevel = Field(..., description="Overall concern for circulation.") hr: int | None = Field(default=None, description="Heart rate (beats per minute).") sbp: int | None = Field(default=None, description="Systolic blood pressure (mmHg).") dbp: int | None = Field(default=None, description="Diastolic blood pressure (mmHg).") imaging_findings: list[ImagingFinding] = Field( default_factory=list, description="Imaging findings relevant to circulation (widened mediastinum, etc.).", ) actions_required: list[str] = Field( default_factory=list, description="Circulation actions, e.g. large-bore IV, blood, TXA, FAST.", ) class DisabilityAssessment(BaseModel): """D — Disability (neurological).""" concern_level: ConcernLevel = Field(..., description="Overall concern for disability.") gcs: int | None = Field(default=None, description="Glasgow Coma Scale (3-15).") actions_required: list[str] = Field( default_factory=list, description="Disability actions, e.g. CT head, neurosurgical consult.", ) class ExposureAssessment(BaseModel): """E — Exposure / environmental control.""" concern_level: ConcernLevel = Field(..., description="Overall concern for exposure.") temp: float | None = Field(default=None, description="Temperature in degrees Celsius.") actions_required: list[str] = Field( default_factory=list, description="Exposure actions, e.g. warming blankets, log-roll, full skin survey.", ) class PrimarySurvey(BaseModel): """The five ABCDE assessments.""" A_airway: AirwayAssessment = Field(..., description="Airway assessment.") B_breathing: BreathingAssessment = Field(..., description="Breathing assessment.") C_circulation: CirculationAssessment = Field(..., description="Circulation assessment.") D_disability: DisabilityAssessment = Field(..., description="Disability assessment.") E_exposure: ExposureAssessment = Field(..., description="Exposure assessment.") class RecommendedAction(BaseModel): """One recommended action with rationale and a target time.""" action: str = Field(..., description="The action, imperative (e.g. 'place 14G left chest').") rationale: str = Field(..., description="One-line justification rooted in the findings.") urgency: Urgency = Field(..., description="Target window: now / <5min / <15min / <1hr.") citation_id: str | None = Field( default=None, description="ID of a retrieved guideline chunk supporting this action." ) class Escalation(BaseModel): """Whom to call and how soon.""" who: str = Field( ..., description="Service or role to escalate to, e.g. 'trauma surgery attending'." ) when: Urgency = Field(..., description="Target window for the escalation call.") class Disposition(BaseModel): """Where the patient goes next and what they need on the way.""" priority: Priority = Field( ..., description="Triage priority: immediate / urgent / delayed / expectant." ) recommended_actions: list[RecommendedAction] = Field( default_factory=list, description="Ordered list of recommended next actions." ) escalation: Escalation = Field(..., description="Escalation target and timing.") additional_imaging: list[str] = Field( default_factory=list, description="Imaging studies that should be ordered next (e.g. CT chest, FAST).", ) labs: list[str] = Field( default_factory=list, description="Labs to send (e.g. type and screen, lactate, ABG, troponin).", ) class RedFlag(BaseModel): """An urgent finding or pattern that requires an immediate response.""" flag: str = Field(..., description="The red flag, e.g. 'tension physiology'.") evidence: list[str] = Field( default_factory=list, description="Concrete evidence (vitals, imaging features) backing the flag.", ) immediate_action: str = Field(..., description="The immediate response to take.") class Citation(BaseModel): """A retrieved guideline chunk that the model relied on.""" id: str = Field(..., description="Citation ID matching the retrieved chunk id.") source: str = Field(..., description="Source, e.g. 'EAST PMG'.") section: str = Field(..., description="Section or page, e.g. 'page 12'.") quote: str = Field(..., description="Direct quote, trimmed.") url: str | None = Field(default=None, description="URL to the original source if available.") class ModelMetadata(BaseModel): """Provenance and self-reported limitations for the run.""" model: str = Field(..., description="Model identifier used.") confidence: Confidence = Field(..., description="Self-rated confidence: high/moderate/low.") limitations: list[str] = Field( ..., description=( "Concrete limitations of this run. Must be non-empty: the model is required to " "name at least one realistic limitation (e.g. 'single AP view, no lateral')." ), ) disclaimer: str = Field( ..., description="Plain-language disclaimer, e.g. 'Decision support only. Not a diagnosis.'", ) verifier_notes: list[str] = Field( default_factory=list, description="Notes appended by the verifier pass.", ) @field_validator("limitations") @classmethod def _limitations_non_empty(cls, v: list[str]) -> list[str]: if not v: raise ValueError("model_metadata.limitations must not be empty") return v class TriageOutput(BaseModel): """Top-level output of the ATLS pipeline.""" case_id: str = Field(..., description="Stable case identifier.") patient_brief: PatientBrief = Field(..., description="Patient summary.") primary_survey: PrimarySurvey = Field(..., description="ABCDE primary survey.") disposition: Disposition = Field(..., description="Disposition and recommended actions.") red_flags: list[RedFlag] = Field( default_factory=list, description="Red flags requiring immediate action." ) citations: list[Citation] = Field( default_factory=list, description="Retrieved guideline chunks the model relied on." ) model_metadata: ModelMetadata = Field(..., description="Provenance and self-reported limits.") class Patch(BaseModel): """A single field-level patch produced by the verifier.""" path: str = Field( ..., description=( "Dot-and-bracket path into TriageOutput, e.g. " "'primary_survey.B_breathing.imaging_findings[0].laterality'." ), ) value: object = Field(..., description="Replacement value at the given path.") class VerifierOutput(BaseModel): """Output of the verifier pass.""" verifier_notes: list[str] = Field( default_factory=list, description="Plain-language notes from the verifier (one per concern).", ) patches: list[Patch] = Field( default_factory=list, description="Field-level patches to apply to the draft.", )