update the codebase poc ver1
This commit is contained in:
115
workspace/sprint_1_2/CODEBASE/data/spec/oop/dependencies.md
Normal file
115
workspace/sprint_1_2/CODEBASE/data/spec/oop/dependencies.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Dependencies, Orchestration, and Integration — Sprint 1_2
|
||||
|
||||
## Data Engineering Alignment
|
||||
|
||||
### Storage Strategy
|
||||
- **Structured metadata**: PostgreSQL (aligned with backend modules)
|
||||
- **Artifacts** (DICOM, images, masks, overlays, models): S3-compatible bucket (MinIO)
|
||||
- **Naming convention**: UUIDs only — no PHI in filenames, keys, or URLs
|
||||
- **Access**: Presigned URLs for temporary access
|
||||
|
||||
### Canonical JSON Schemas
|
||||
All serialized domain objects must validate against canonical schemas defined in `data/schemas/`. Key schemas:
|
||||
- `session.schema.json`
|
||||
- `frame.schema.json`
|
||||
- `prediction.schema.json`
|
||||
- `measurement.schema.json`
|
||||
- `audit.schema.json`
|
||||
|
||||
### Model Output Normalization
|
||||
All model adapters must normalize outputs to canonical labels.
|
||||
|
||||
**Segmentation classes:**
|
||||
- `background`
|
||||
- `effusion`
|
||||
- `fat`
|
||||
- `fat-pat`
|
||||
- `femur`
|
||||
- `synovium`
|
||||
- `tendon`
|
||||
|
||||
**Angle classes:**
|
||||
- `med-lat`
|
||||
- `post-trans`
|
||||
- `sup-trans-flex`
|
||||
- `sup-up-long`
|
||||
|
||||
**Severity grades:**
|
||||
- 0: Rất nhẹ
|
||||
- 1: Nhẹ
|
||||
- 2: Trung bình
|
||||
- 3: Nặng
|
||||
|
||||
---
|
||||
|
||||
## Orchestrators and Use Cases
|
||||
|
||||
Orchestrators coordinate the workflow by sequencing agents and enforcing state machines.
|
||||
|
||||
### Key Use Cases
|
||||
|
||||
1. **Upload and Ingest**
|
||||
- Input: multipart DICOM or image upload
|
||||
- Steps: `DICOMIngestAgent` / `ImageUploadIngestAgent` → `FrameStorageAdapter`
|
||||
- Output: `DiagnosticSession`, `ScanFrame`, `ImageAsset`
|
||||
|
||||
2. **Run Analysis Pipeline**
|
||||
- Input: `DiagnosticSession`
|
||||
- Steps: `VisionPipelineAgent` → `InferenceRunner` → `MeasurementAgent` → `SeverityScorerAgent`
|
||||
- Output: `AnalysisJob` with completed results
|
||||
|
||||
3. **Review and Finalize**
|
||||
- Input: Clinician review data
|
||||
- Steps: `LedgerWriterAgent` → `ReviewDecision`
|
||||
- Output: Updated session state
|
||||
|
||||
---
|
||||
|
||||
## Integration with Backend Architecture
|
||||
|
||||
This OOP design maps to the backend specification modules:
|
||||
|
||||
| OOP Layer | Backend Module |
|
||||
|-----------|---------------|
|
||||
| Orchestrators & APIs | `api/` routers (session_api, analysis_api, etc.) |
|
||||
| Agents/Services | `implementation/` services |
|
||||
| Adapters | `implementation/` adapters |
|
||||
| Domain Objects | ORM models (PostgreSQL) + S3 references |
|
||||
| Orchestration | `implementation/analysis_jobs/service.py` (async jobs) |
|
||||
|
||||
---
|
||||
|
||||
## Validation and Testing
|
||||
|
||||
### Structural Validation
|
||||
- All candidate objects must map to either a PostgreSQL table or an S3 artifact reference.
|
||||
- No object may contain PHI fields that bypass scrubbing.
|
||||
|
||||
### Behavioral Validation
|
||||
- Adapter interfaces must support both mock and real implementations.
|
||||
- Agents must be stateless and idempotent where possible.
|
||||
|
||||
### End-to-End Flow
|
||||
```
|
||||
image/DICOM upload → secure local ingest → frame extraction → preprocessing → model inference → structured metrics/mask → API result → browser mask preview
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Object–Object and Object–Service Relationship Summary
|
||||
|
||||
- `ClinicianUser` owns `DiagnosticSession` and authors `ReviewDecision`
|
||||
- `PatientCase` groups many `DiagnosticSession` records
|
||||
- `DiagnosticSession` contains `ScanFrame`s, spawns `AnalysisJob`s, and tracks `Calibration` and `ReviewDecision` records
|
||||
- `AnalysisJob` consists of `PipelineStep`s and produces prediction, mask, measurement, and grade objects
|
||||
- `ScanFrame` becomes a `PreprocessedImage` via `FramePreprocessor`
|
||||
- `ImageAsset` stores the raw binary artifact for a `ScanFrame`
|
||||
- `ArtifactReference` can point to any `ScanFrame` or mask/overlay S3 object
|
||||
- `LedgerWriterAgent` writes `AuditLedgerEntry` for all state changes
|
||||
|
||||
## Agent–Adapter Dependencies
|
||||
|
||||
- `DICOMIngestAgent` and `ImageUploadIngestAgent` → `FrameStorageAdapter`
|
||||
- `ArtifactStoreAgent` → `FrameStorageAdapter` and `ArtifactStorageAdapter`
|
||||
- `InferenceRunner` → `InferenceAdapter` (PyTorch, Triton, or Mock)
|
||||
- `ModelRegistryAgent` → `ArtifactStorageAdapter`
|
||||
393
workspace/sprint_1_2/CODEBASE/data/spec/oop/objects.md
Normal file
393
workspace/sprint_1_2/CODEBASE/data/spec/oop/objects.md
Normal file
@@ -0,0 +1,393 @@
|
||||
# Object Specifications — Sprint 1_2
|
||||
|
||||
## Overview
|
||||
|
||||
Domain objects represent **persistable clinical and analysis facts**. They are pure data structures with minimal behavior, focused on encapsulating business rules and state. They are persisted via PostgreSQL (structured metadata) and S3-compatible storage (artifacts).
|
||||
|
||||
## OOP Boundary
|
||||
|
||||
```
|
||||
Domain objects = persistable clinical/analysis facts.
|
||||
Agents/services = runtime workers that transform facts.
|
||||
Orchestrators = coordinate use cases and enforce workflow state.
|
||||
Adapters = hide PyTorch, filesystem, image, and API details.
|
||||
```
|
||||
|
||||
## Layer Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API Layer (FastAPI) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Orchestrators (Use Cases) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Agents & Services (Workers) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Domain Objects │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Adapters │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domain Objects
|
||||
|
||||
### ClinicianUser
|
||||
Represents an authenticated medical professional.
|
||||
|
||||
**Fields:**
|
||||
- `user_id`: UUID / primary key
|
||||
- `username`: str
|
||||
- `hashed_password`: str
|
||||
- `name`: str
|
||||
- `role`: str (e.g., "radiologist", "support")
|
||||
- `credentials`: dict | None
|
||||
- `specialization`: str
|
||||
- `created_at`: datetime
|
||||
- `last_login`: datetime | None
|
||||
|
||||
**Relationships:**
|
||||
- owns many `DiagnosticSession`s
|
||||
- author of many `ReviewDecision`s
|
||||
|
||||
**Responsibilities:**
|
||||
- Authentication (via `AuthModule`)
|
||||
- Session ownership
|
||||
- Review and sign decisions
|
||||
|
||||
---
|
||||
|
||||
### 2. PatientCase
|
||||
Represents a patient's overall medical case.
|
||||
|
||||
**Fields:**
|
||||
- `case_id`: UUID
|
||||
- `patient_identifier`: str (hashed / pseudonymized)
|
||||
- `demographic_info`: dict
|
||||
- `medical_history_summary`: dict
|
||||
- `created_by`: `ClinicianUser`
|
||||
- `created_at`: datetime
|
||||
|
||||
**Relationships:**
|
||||
- has many `DiagnosticSession`s
|
||||
|
||||
**Responsibilities:**
|
||||
- Case registration and tracking
|
||||
- Session grouping
|
||||
|
||||
---
|
||||
|
||||
### 3. DiagnosticSession
|
||||
Represents a single ultrasound examination session.
|
||||
|
||||
**Fields:**
|
||||
- `session_id`: UUID
|
||||
- `case_id`: ForeignKey → PatientCase
|
||||
- `clinician_id`: ForeignKey → ClinicianUser
|
||||
- `status`: str (e.g., "created", "uploaded", "in_progress", "completed", "reviewed")
|
||||
- `created_at`: datetime
|
||||
- `updated_at`: datetime
|
||||
|
||||
**Relationships:**
|
||||
- belongs to `PatientCase`
|
||||
- belongs to `ClinicianUser`
|
||||
- has many `ScanFrame`s
|
||||
- has many `AnalysisJob`s
|
||||
- has many `ReviewDecision`s
|
||||
|
||||
**Responsibilities:**
|
||||
- Session lifecycle management
|
||||
- Frame and job grouping
|
||||
- Review state enforcement
|
||||
|
||||
---
|
||||
|
||||
### 4. ScanFrame
|
||||
Represents a single ultrasound image frame extracted from DICOM or standard image upload.
|
||||
|
||||
**Fields:**
|
||||
- `frame_id`: UUID
|
||||
- `session_id`: ForeignKey → DiagnosticSession
|
||||
- `storage_reference`: str (S3 key)
|
||||
- `original_format`: str (e.g., "dicom", "png", "jpeg")
|
||||
- `frame_number`: int | None
|
||||
- `metadata`: dict (DICOM tags, image dimensions, etc.)
|
||||
- `checksum`: str (SHA-256)
|
||||
- `created_at`: datetime
|
||||
|
||||
**Relationships:**
|
||||
- belongs to `DiagnosticSession`
|
||||
- has one `ImageAsset` (the raw artifact)
|
||||
- has one `PreprocessedImage`
|
||||
|
||||
**Responsibilities:**
|
||||
- Frame metadata capture
|
||||
- PHI-safe storage reference management
|
||||
|
||||
---
|
||||
|
||||
### 5. ImageAsset
|
||||
Represents the raw storage artifact for a frame.
|
||||
|
||||
**Fields:**
|
||||
- `asset_id`: UUID
|
||||
- `frame_id`: ForeignKey → ScanFrame
|
||||
- `storage_key`: str (S3 / MinIO key, UUID-based, no PHI)
|
||||
- `content_type`: str
|
||||
- `size_bytes`: int
|
||||
- `checksum`: str (SHA-256)
|
||||
- `uploaded_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Binary artifact storage reference
|
||||
- Integrity verification
|
||||
|
||||
---
|
||||
|
||||
### 6. Calibration
|
||||
Device-specific calibration parameters for a session.
|
||||
|
||||
**Fields:**
|
||||
- `calibration_id`: UUID
|
||||
- `session_id`: ForeignKey → DiagnosticSession
|
||||
- `pixel_to_mm_ratio`: float
|
||||
- `parameters`: dict
|
||||
- `recorded_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Measurement calibration
|
||||
- ROI metric scaling
|
||||
|
||||
---
|
||||
|
||||
### 7. AnalysisJob
|
||||
Request for AI/ML analysis on session frame(s).
|
||||
|
||||
**Fields:**
|
||||
- `job_id`: UUID
|
||||
- `session_id`: ForeignKey → DiagnosticSession
|
||||
- `parameters`: dict (e.g., selected models, flags)
|
||||
- `model_versions`: dict (task → model_id + version)
|
||||
- `status`: str (e.g., "pending", "running", "completed", "failed")
|
||||
- `result`: dict | None
|
||||
- `created_at`: datetime
|
||||
- `updated_at`: datetime
|
||||
|
||||
**Relationships:**
|
||||
- belongs to `DiagnosticSession`
|
||||
- has many `PipelineStep`s
|
||||
- produces angle, inflammation, segmentation, measurement, and grade results
|
||||
|
||||
**Responsibilities:**
|
||||
- Async job orchestration
|
||||
- Result aggregation
|
||||
|
||||
---
|
||||
|
||||
### 8. PipelineStep
|
||||
Single step in the analysis pipeline.
|
||||
|
||||
**Fields:**
|
||||
- `step_id`: UUID
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `task_type`: str (e.g., "angle_classification", "inflammation_detection", "segmentation_sup", "segmentation_post", "measurement", "severity_scoring")
|
||||
- `status`: str
|
||||
- `output`: dict | None
|
||||
- `duration_ms`: int | None
|
||||
- `started_at`: datetime | None
|
||||
- `completed_at`: datetime | None
|
||||
|
||||
**Responsibilities:**
|
||||
- Step-level progress tracking
|
||||
- Error isolation
|
||||
|
||||
---
|
||||
|
||||
### 9. ModelRegistryEntry
|
||||
Metadata record for a registered ML model.
|
||||
|
||||
**Fields:**
|
||||
- `model_id`: str
|
||||
- `name`: str
|
||||
- `task_type`: str
|
||||
- `version`: str
|
||||
- `description`: str
|
||||
- `framework`: str (e.g., "pytorch", "onnx")
|
||||
- `labels`: list[str]
|
||||
- `registered_at`: datetime
|
||||
- `is_active`: bool
|
||||
|
||||
**Responsibilities:**
|
||||
- Model discovery and selection
|
||||
- Version tracking
|
||||
|
||||
---
|
||||
|
||||
### 10. ModelArtifact
|
||||
Actual stored ML model artifact.
|
||||
|
||||
**Fields:**
|
||||
- `artifact_id`: UUID
|
||||
- `model_id`: ForeignKey → ModelRegistryEntry
|
||||
- `storage_key`: str (S3 key, UUID-based)
|
||||
- `format`: str (e.g., ".pth", ".onnx")
|
||||
- `size_bytes`: int
|
||||
- `checksum`: str
|
||||
- `uploaded_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Secure storage of model weights
|
||||
- Integrity verification
|
||||
|
||||
---
|
||||
|
||||
### 11. PreprocessedImage
|
||||
Frame after preprocessing transformations.
|
||||
|
||||
**Fields:**
|
||||
- `preprocessed_id`: UUID
|
||||
- `frame_id`: ForeignKey → ScanFrame
|
||||
- `preprocessing_steps`: list[str]
|
||||
- `storage_reference`: str (S3 key, or inline base64 for small artifacts)
|
||||
- `width`: int
|
||||
- `height`: int
|
||||
- `created_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Intermediate processing artifact management
|
||||
|
||||
---
|
||||
|
||||
### 12. AnglePrediction
|
||||
Output of the angle classification model.
|
||||
|
||||
**Fields:**
|
||||
- `prediction_id`: UUID
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `step_id`: ForeignKey → PipelineStep
|
||||
- `angle_class`: str (e.g., "med-lat", "post-trans", "sup-trans-flex", "sup-up-long")
|
||||
- `confidence`: float
|
||||
- `metadata`: dict
|
||||
|
||||
**Responsibilities:**
|
||||
- Classification result encapsulation
|
||||
|
||||
---
|
||||
|
||||
### 13. InflammationPrediction
|
||||
Output of the inflammation detection model.
|
||||
|
||||
**Fields:**
|
||||
- `prediction_id`: UUID
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `step_id`: ForeignKey → PipelineStep
|
||||
- `detected`: bool
|
||||
- `confidence`: float
|
||||
|
||||
**Responsibilities:**
|
||||
- Binary detection result encapsulation
|
||||
|
||||
---
|
||||
|
||||
### 14. SegmentationMask
|
||||
Output of the segmentation model.
|
||||
|
||||
**Fields:**
|
||||
- `mask_id`: UUID
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `step_id`: ForeignKey → PipelineStep
|
||||
- `storage_reference`: str (S3 key)
|
||||
- `overlay_reference`: str (S3 key)
|
||||
- `color_legend`: dict (class → color)
|
||||
- `metadata`: dict
|
||||
|
||||
**Responsibilities:**
|
||||
- Segmentation result storage and retrieval
|
||||
|
||||
---
|
||||
|
||||
### 15. Measurement
|
||||
Quantitative measurement derived from a segmentation mask.
|
||||
|
||||
**Fields:**
|
||||
- `measurement_id`: UUID
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `step_id`: ForeignKey → PipelineStep
|
||||
- `thickness_mm`: float | None
|
||||
- `pixel_to_mm_ratio`: float
|
||||
- `roi_specification`: dict (e.g., bounding box, region)
|
||||
- `created_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Measurement calculation and storage
|
||||
|
||||
---
|
||||
|
||||
### 16. SynovitisGrade
|
||||
Final severity grade (0–3) for synovitis.
|
||||
|
||||
**Fields:**
|
||||
- `grade_id`: UUID
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `step_id`: ForeignKey → PipelineStep
|
||||
- `level`: int (0–3)
|
||||
- `label`: str
|
||||
- `combined_score`: float | None
|
||||
- `confidence`: float | None
|
||||
|
||||
**Responsibilities:**
|
||||
- Severity scoring encapsulation
|
||||
|
||||
---
|
||||
|
||||
### 17. ReviewDecision
|
||||
Clinician's approval, correction, or rejection of AI results.
|
||||
|
||||
**Fields:**
|
||||
- `decision_id`: UUID
|
||||
- `session_id`: ForeignKey → DiagnosticSession
|
||||
- `job_id`: ForeignKey → AnalysisJob
|
||||
- `reviewer_id`: ForeignKey → ClinicianUser
|
||||
- `decision_type`: str ("approve", "correct", "reject")
|
||||
- `justification`: str | None
|
||||
- `created_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- HITL decision capture
|
||||
- Review audit trail
|
||||
|
||||
---
|
||||
|
||||
### 18. ArtifactReference
|
||||
Polymorphic reference to any stored artifact.
|
||||
|
||||
**Fields:**
|
||||
- `reference_id`: UUID
|
||||
- `artifact_type`: str
|
||||
- `associated_entity_id`: UUID
|
||||
- `storage_key`: str (S3 key)
|
||||
- `content_type`: str
|
||||
- `created_at`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Unified artifact reference management
|
||||
|
||||
---
|
||||
|
||||
### 19. AuditLedgerEntry
|
||||
Immutable audit trail entry for any significant event.
|
||||
|
||||
**Fields:**
|
||||
- `entry_id`: UUID
|
||||
- `entity_type`: str
|
||||
- `entity_id`: UUID
|
||||
- `action`: str
|
||||
- `user_id`: UUID | None
|
||||
- `checksum`: str (SHA-256 of the event payload)
|
||||
- `metadata`: dict
|
||||
- `timestamp`: datetime
|
||||
|
||||
**Responsibilities:**
|
||||
- Immutable audit trail
|
||||
- Compliance (Decree 13 / Circular 46)
|
||||
1119
workspace/sprint_1_2/CODEBASE/data/spec/oop/oop_spec.md
Normal file
1119
workspace/sprint_1_2/CODEBASE/data/spec/oop/oop_spec.md
Normal file
File diff suppressed because it is too large
Load Diff
136
workspace/sprint_1_2/CODEBASE/data/spec/oop/services.md
Normal file
136
workspace/sprint_1_2/CODEBASE/data/spec/oop/services.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Services, Agents, and Adapters — Sprint 1_2
|
||||
|
||||
Agents and services are the **runtime workers** that transform domain objects. Each agent has a single, focused responsibility and collaborates via well-defined interfaces.
|
||||
|
||||
## Ingestion Agents
|
||||
|
||||
### DICOMIngestAgent
|
||||
- **Responsibility:** Parse and validate DICOM files, extract metadata and renderable frames.
|
||||
- **Input:** `UploadFile` (DICOM bytes)
|
||||
- **Output:** `ScanFrame`, `ImageAsset`
|
||||
- **Collaborators:** `FrameStorageAdapter` (S3)
|
||||
|
||||
### ImageUploadIngestAgent
|
||||
- **Responsibility:** Handle standard image uploads (JPEG, PNG, etc.).
|
||||
- **Input:** `UploadFile` (image bytes)
|
||||
- **Output:** `ScanFrame`, `ImageAsset`
|
||||
- **Collaborators:** `FrameStorageAdapter`
|
||||
|
||||
## Preprocessing and Validation Agents
|
||||
|
||||
### FramePreprocessor
|
||||
- **Responsibility:** Apply preprocessing transformations (CLAHE, resizing, normalization).
|
||||
- **Input:** `ScanFrame`
|
||||
- **Output:** `PreprocessedImage`
|
||||
- **Collaborators:** Image libraries via adapter
|
||||
|
||||
### AngleValidatorAgent
|
||||
- **Responsibility:** Validate angle classification results against clinical rules.
|
||||
- **Input:** `AnglePrediction`
|
||||
- **Output:** `AnglePrediction` (possibly adjusted confidence)
|
||||
- **Collaborators:** Clinical rule engine
|
||||
|
||||
### ROICropperAgent
|
||||
- **Responsibility:** Extract regions of interest for specialized models.
|
||||
- **Input:** `PreprocessedImage`
|
||||
- **Output:** Cropped image segments
|
||||
- **Collaborators:** Frame storage, preprocessing
|
||||
|
||||
## Core Analysis Agents
|
||||
|
||||
### VisionPipelineAgent
|
||||
- **Responsibility:** Orchestrate the end-to-end vision inference pipeline for a session.
|
||||
- **Input:** `DiagnosticSession`, list of `ScanFrame`s
|
||||
- **Output:** `AnalysisJob` with completed `PipelineStep`s and results
|
||||
- **Collaborators:** `InferenceRunner`, `MeasurementAgent`, `SeverityScorerAgent`, `ModelRegistryAgent`
|
||||
|
||||
### InferenceRunner
|
||||
- **Responsibility:** Execute ML model inference via adapters (PyTorch, Triton, or Mock).
|
||||
- **Input:** `ModelReference` (id + version), `ProcessedImage` data
|
||||
- **Output:** Raw prediction payloads
|
||||
- **Collaborators:** `PyTorchAdapter`, `TritonAdapter`, `MockAdapter`
|
||||
|
||||
### MeasurementAgent
|
||||
- **Responsibility:** Calculate quantitative measurements from `SegmentationMask` using `Calibration`.
|
||||
- **Input:** `SegmentationMask`, `Calibration`
|
||||
- **Output:** `Measurement`
|
||||
- **Collaborators:** Calibration service, segmentation model geometry
|
||||
|
||||
### SeverityScorerAgent
|
||||
- **Responsibility:** Compute synovitis grade (0–3) from effusion and synovium measurements and inflammation prediction.
|
||||
- **Input:** `Measurement`, `InflammationPrediction`
|
||||
- **Output:** `SynovitisGrade`
|
||||
- **Collaborators:** Clinical scoring rules
|
||||
|
||||
## Management Agents
|
||||
|
||||
### ModelRegistryAgent
|
||||
- **Responsibility:** Manage model registration, versioning, and availability checks.
|
||||
- **Input:** `ModelRegistryEntry` data, `ModelArtifact` binaries
|
||||
- **Output:** `ModelRegistryEntry`, `ModelArtifact`
|
||||
- **Collaborators:** `ArtifactStoreAgent`, database persistence
|
||||
|
||||
### ArtifactStoreAgent
|
||||
- **Responsibility:** Store and retrieve large artifacts via S3-compatible storage.
|
||||
- **Input:** Binary data, storage key
|
||||
- **Output:** Storage confirmation, presigned URLs or S3 references
|
||||
- **Collaborators:** `FrameStorageAdapter`, S3 / MinIO
|
||||
|
||||
### LedgerWriterAgent
|
||||
- **Responsibility:** Write immutable `AuditLedgerEntry` records for state changes.
|
||||
- **Input:** Audit event payloads
|
||||
- **Output:** `AuditLedgerEntry`
|
||||
- **Collaborators:** PostgreSQL persistence
|
||||
|
||||
---
|
||||
|
||||
## Adapter Interfaces
|
||||
|
||||
Adapters encapsulate external system details and provide a uniform internal interface.
|
||||
|
||||
### Storage Adapters
|
||||
|
||||
```python
|
||||
class FrameStorageAdapter(ABC):
|
||||
@abstractmethod
|
||||
def store_frame(self, frame_id: UUID, data: bytes, content_type: str) -> str:
|
||||
"""Returns S3 storage key"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def generate_presigned_url(self, storage_key: str, expires_in: int) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_frame(self, storage_key: str) -> None:
|
||||
pass
|
||||
```
|
||||
|
||||
```python
|
||||
class ArtifactStorageAdapter(ABC):
|
||||
@abstractmethod
|
||||
def store_artifact(self, artifact_id: UUID, data: bytes, content_type: str) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def retrieve_artifact(self, storage_key: str) -> bytes:
|
||||
pass
|
||||
```
|
||||
|
||||
### ML Inference Adapters
|
||||
|
||||
```python
|
||||
class InferenceAdapter(ABC):
|
||||
@abstractmethod
|
||||
def load_model(self, model_reference: str) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def infer(self, input_data: ProcessedImage) -> dict:
|
||||
"""Returns standardized prediction dict"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def unload_model(self, model_reference: str) -> None:
|
||||
pass
|
||||
```
|
||||
501
workspace/sprint_1_2/CODEBASE/data/spec/oop/visualization.md
Normal file
501
workspace/sprint_1_2/CODEBASE/data/spec/oop/visualization.md
Normal file
@@ -0,0 +1,501 @@
|
||||
# Visualization — Sprint 1_2 Class and Architecture Diagrams
|
||||
|
||||
## Layer Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API Layer (FastAPI) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Orchestrators (Use Cases) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Agents & Services (Workers) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Domain Objects │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Adapters │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Full Class Diagram
|
||||
|
||||
```plantuml
|
||||
@startuml Sprint 1_2 OOP Class Diagram
|
||||
skinparam classAttributeAlignment left
|
||||
skinparam classFontSize 11
|
||||
skinparam backgroundColor #FEFEFF
|
||||
skinparam handwritten false
|
||||
|
||||
package "Domain Objects" {
|
||||
class ClinicianUser {
|
||||
-user_id: UUID
|
||||
-username: str
|
||||
-hashed_password: str
|
||||
-name: str
|
||||
-role: str
|
||||
-credentials: dict | None
|
||||
-specialization: str
|
||||
-created_at: datetime
|
||||
-last_login: datetime | None
|
||||
--
|
||||
+authenticate(password: str): bool
|
||||
+owns_sessions(): List[DiagnosticSession]
|
||||
+creates_review(decision: ReviewDecision): ReviewDecision
|
||||
}
|
||||
|
||||
class PatientCase {
|
||||
-case_id: UUID
|
||||
-patient_identifier: str
|
||||
-demographic_info: dict
|
||||
-medical_history_summary: dict
|
||||
-created_at: datetime
|
||||
--
|
||||
+add_session(session: DiagnosticSession): void
|
||||
+list_sessions(): List[DiagnosticSession]
|
||||
}
|
||||
|
||||
class DiagnosticSession {
|
||||
-session_id: UUID
|
||||
-case_id: UUID
|
||||
-clinician_id: UUID
|
||||
-status: str
|
||||
-created_at: datetime
|
||||
-updated_at: datetime
|
||||
--
|
||||
+add_frame(frame: ScanFrame): void
|
||||
+add_job(job: AnalysisJob): void
|
||||
+add_review(decision: ReviewDecision): void
|
||||
+can_upload(): bool
|
||||
+can_analyze(): bool
|
||||
+can_review(): bool
|
||||
}
|
||||
|
||||
class ScanFrame {
|
||||
-frame_id: UUID
|
||||
-session_id: UUID
|
||||
-storage_reference: str
|
||||
-original_format: str
|
||||
-frame_number: int | None
|
||||
-metadata: dict
|
||||
-checksum: str
|
||||
-created_at: datetime
|
||||
--
|
||||
+get_image_data(): bytes
|
||||
+get_metadata(): dict
|
||||
+has_preprocessed(): bool
|
||||
+calculate_checksum(): str
|
||||
}
|
||||
|
||||
class ImageAsset {
|
||||
-asset_id: UUID
|
||||
-frame_id: UUID
|
||||
-storage_key: str
|
||||
-content_type: str
|
||||
-size_bytes: int
|
||||
-checksum: str
|
||||
-uploaded_at: datetime
|
||||
--
|
||||
+get_storage_key(): str
|
||||
+verify_checksum(): bool
|
||||
}
|
||||
|
||||
class Calibration {
|
||||
-calibration_id: UUID
|
||||
-session_id: UUID
|
||||
-pixel_to_mm_ratio: float
|
||||
-parameters: dict
|
||||
-recorded_at: datetime
|
||||
--
|
||||
+scale_pixels_to_mm(pixels: float): float
|
||||
+get_parameters(): dict
|
||||
}
|
||||
|
||||
class AnalysisJob {
|
||||
-job_id: UUID
|
||||
-session_id: UUID
|
||||
-parameters: dict
|
||||
-model_versions: dict
|
||||
-status: str
|
||||
-result: dict | None
|
||||
-created_at: datetime
|
||||
-updated_at: datetime
|
||||
--
|
||||
+add_step(step: PipelineStep): void
|
||||
+set_status(status: str): void
|
||||
+set_result(result: dict): void
|
||||
+is_running(): bool
|
||||
+is_completed(): bool
|
||||
+is_failed(): bool
|
||||
+get_steps(): List[PipelineStep]
|
||||
}
|
||||
|
||||
class PipelineStep {
|
||||
-step_id: UUID
|
||||
-job_id: UUID
|
||||
-task_type: str
|
||||
-status: str
|
||||
-output: dict | None
|
||||
-duration_ms: int | None
|
||||
-started_at: datetime | None
|
||||
-completed_at: datetime | None
|
||||
--
|
||||
+start(model: ModelReference): void
|
||||
+complete(output: dict): void
|
||||
+fail(error: str): void
|
||||
+get_duration(): int | None
|
||||
}
|
||||
|
||||
class ModelRegistryEntry {
|
||||
-model_id: str
|
||||
-name: str
|
||||
-task_type: str
|
||||
-version: str
|
||||
-description: str
|
||||
-framework: str
|
||||
-labels: list[str]
|
||||
-registered_at: datetime
|
||||
-is_active: bool
|
||||
--
|
||||
+get_labels(): list[str]
|
||||
+is_compatible_with(task: str): bool
|
||||
+activate(): void
|
||||
+deactivate(): void
|
||||
}
|
||||
|
||||
class ModelArtifact {
|
||||
-artifact_id: UUID
|
||||
-model_id: str
|
||||
-storage_key: str
|
||||
-format: str
|
||||
-size_bytes: int
|
||||
-checksum: str
|
||||
-uploaded_at: datetime
|
||||
--
|
||||
+get_model_file(): bytes
|
||||
+verify_checksum(): bool
|
||||
+get_format(): str
|
||||
}
|
||||
|
||||
class PreprocessedImage {
|
||||
-preprocessed_id: UUID
|
||||
-frame_id: UUID
|
||||
-preprocessing_steps: list[str]
|
||||
-storage_reference: str
|
||||
-width: int
|
||||
-height: int
|
||||
-created_at: datetime
|
||||
--
|
||||
+get_image_data(): bytes
|
||||
+get_dimensions(): tuple[int, int]
|
||||
+applied_steps(): list[str]
|
||||
}
|
||||
|
||||
class AnglePrediction {
|
||||
-prediction_id: UUID
|
||||
-job_id: UUID
|
||||
-step_id: UUID
|
||||
-angle_class: str
|
||||
-confidence: float
|
||||
-metadata: dict
|
||||
--
|
||||
+get_class(): str
|
||||
+get_confidence(): float
|
||||
+is_confident(threshold: float): bool
|
||||
}
|
||||
|
||||
class InflammationPrediction {
|
||||
-prediction_id: UUID
|
||||
-job_id: UUID
|
||||
-step_id: UUID
|
||||
-detected: bool
|
||||
-confidence: float
|
||||
--
|
||||
+is_detected(): bool
|
||||
+get_confidence(): float
|
||||
}
|
||||
|
||||
class SegmentationMask {
|
||||
-mask_id: UUID
|
||||
-job_id: UUID
|
||||
-step_id: UUID
|
||||
-storage_reference: str
|
||||
-overlay_reference: str
|
||||
-color_legend: dict
|
||||
-metadata: dict
|
||||
--
|
||||
+get_mask_data(): bytes
|
||||
+get_overlay_reference(): str
|
||||
+get_color_legend(): dict
|
||||
+get_classes(): list[str]
|
||||
}
|
||||
|
||||
class Measurement {
|
||||
-measurement_id: UUID
|
||||
-job_id: UUID
|
||||
-step_id: UUID
|
||||
-thickness_mm: float | None
|
||||
-pixel_to_mm_ratio: float
|
||||
-roi_specification: dict
|
||||
-created_at: datetime
|
||||
--
|
||||
+get_thickness_mm(): float | None
|
||||
+get_pixel_to_mm_ratio(): float
|
||||
+calculate_area(pixels: int): float
|
||||
+get_roi_specification(): dict
|
||||
}
|
||||
|
||||
class SynovitisGrade {
|
||||
-grade_id: UUID
|
||||
-job_id: UUID
|
||||
-step_id: UUID
|
||||
-level: int
|
||||
-label: str
|
||||
-combined_score: float | None
|
||||
-confidence: float | None
|
||||
--
|
||||
+get_level(): int
|
||||
+get_label(): str
|
||||
+get_combined_score(): float | None
|
||||
+get_confidence(): float | None
|
||||
+is_severe(): bool
|
||||
}
|
||||
|
||||
class ReviewDecision {
|
||||
-decision_id: UUID
|
||||
-session_id: UUID
|
||||
-job_id: UUID
|
||||
-reviewer_id: UUID
|
||||
-decision_type: str
|
||||
-justification: str | None
|
||||
-created_at: datetime
|
||||
--
|
||||
+is_approved(): bool
|
||||
+is_corrected(): bool
|
||||
+is_rejected(): bool
|
||||
+get_justification(): str | None
|
||||
}
|
||||
|
||||
class ArtifactReference {
|
||||
-reference_id: UUID
|
||||
-artifact_type: str
|
||||
-associated_entity_id: UUID
|
||||
-storage_key: str
|
||||
-content_type: str
|
||||
-created_at: datetime
|
||||
--
|
||||
+get_storage_key(): str
|
||||
+get_content_type(): str
|
||||
+get_entity_id(): UUID
|
||||
}
|
||||
|
||||
class AuditLedgerEntry {
|
||||
-entry_id: UUID
|
||||
-entity_type: str
|
||||
-entity_id: UUID
|
||||
-action: str
|
||||
-user_id: UUID | None
|
||||
-checksum: str
|
||||
-metadata: dict
|
||||
-timestamp: datetime
|
||||
--
|
||||
+get_action(): str
|
||||
+get_entity(): str
|
||||
+verify_checksum(payload: dict): bool
|
||||
+to_immutable(): AuditLedgerEntry
|
||||
}
|
||||
}
|
||||
|
||||
package "Agents / Services" {
|
||||
class DICOMIngestAgent {
|
||||
+ingest(source: UploadFile): ScanFrame
|
||||
+validate_dicom(data: bytes): bool
|
||||
+extract_metadata(data: bytes): dict
|
||||
+extract_frames(data: bytes): List[bytes]
|
||||
}
|
||||
|
||||
class ImageUploadIngestAgent {
|
||||
+ingest(source: UploadFile): ScanFrame
|
||||
+validate_image(data: bytes): bool
|
||||
+extract_metadata(data: bytes): dict
|
||||
}
|
||||
|
||||
class FramePreprocessor {
|
||||
+preprocess(frame: ScanFrame): PreprocessedImage
|
||||
+apply_clahe(image: bytes): bytes
|
||||
+normalize(image: bytes): bytes
|
||||
+resize(image: bytes, size: tuple[int, int]): bytes
|
||||
}
|
||||
|
||||
class AngleValidatorAgent {
|
||||
+validate(prediction: AnglePrediction): AnglePrediction
|
||||
+adjust_confidence(prediction: AnglePrediction, adjustment: float): AnglePrediction
|
||||
+check_clinical_rules(angle_class: str): bool
|
||||
}
|
||||
|
||||
class ROICropperAgent {
|
||||
+crop_for_inflammation(image: PreprocessedImage): PreprocessedImage
|
||||
+crop_for_segmentation(image: PreprocessedImage, angle: str): PreprocessedImage
|
||||
+extract_bounding_box(image: bytes): dict
|
||||
}
|
||||
|
||||
class VisionPipelineAgent {
|
||||
+run_pipeline(session: DiagnosticSession, frames: List[ScanFrame]): AnalysisJob
|
||||
+coordinate_models(frames: List[ScanFrame], models: dict): dict
|
||||
+should_apply_inflammation(angle: str): bool
|
||||
+should_apply_segmentation(angle: str): bool
|
||||
}
|
||||
|
||||
class InferenceRunner {
|
||||
+infer(model: ModelReference, image: bytes): dict
|
||||
+load_model(model_id: str, version: str): void
|
||||
+unload_model(model_id: str): void
|
||||
+get_model_status(model_id: str): str
|
||||
}
|
||||
|
||||
class MeasurementAgent {
|
||||
+measure(mask: SegmentationMask, calibration: Calibration): Measurement
|
||||
+calculate_thickness(mask: bytes, ratio: float): float
|
||||
+calculate_roi(mask: bytes): dict
|
||||
+validate_measurement(measurement: Measurement): bool
|
||||
}
|
||||
|
||||
class SeverityScorerAgent {
|
||||
+score(measurement: Measurement, inflammation: InflammationPrediction): SynovitisGrade
|
||||
+calculate_combined_score(thickness: float, detected: bool): float
|
||||
+get_grade_label(score: float): str
|
||||
+validate_grade(grade: SynovitisGrade): bool
|
||||
}
|
||||
|
||||
class ModelRegistryAgent {
|
||||
+register_model(entry: ModelRegistryEntry, artifact: ModelArtifact): ModelRegistryEntry
|
||||
+get_model(task: str, version: str = "latest"): ModelReference
|
||||
+list_models(): List[ModelRegistryEntry]
|
||||
+activate_model(model_id: str): void
|
||||
+deactivate_model(model_id: str): void
|
||||
+verify_artifact(model_id: str, checksum: str): bool
|
||||
}
|
||||
|
||||
class ArtifactStoreAgent {
|
||||
+store_artifact(artifact_id: UUID, data: bytes, content_type: str): str
|
||||
+retrieve_artifact(storage_key: str): bytes
|
||||
+delete_artifact(storage_key: str): void
|
||||
+generate_presigned_url(storage_key: str, expires_in: int = 3600): str
|
||||
+verify_integrity(storage_key: str, checksum: str): bool
|
||||
}
|
||||
|
||||
class LedgerWriterAgent {
|
||||
+write(event_type: str, entity_type: str, entity_id: UUID, payload: dict, user_id: UUID | None = None): AuditLedgerEntry
|
||||
+verify_integrity(entry: AuditLedgerEntry): bool
|
||||
+query_by_entity(entity_type: str, entity_id: UUID): List[AuditLedgerEntry]
|
||||
}
|
||||
}
|
||||
|
||||
package "Adapters" {
|
||||
class FrameStorageAdapter {
|
||||
{abstract} +store_frame(frame_id: UUID, data: bytes, content_type: str) -> str
|
||||
{abstract} +generate_presigned_url(storage_key: str, expires_in: int) -> str
|
||||
{abstract} +delete_frame(storage_key: str) -> None
|
||||
}
|
||||
|
||||
class ArtifactStorageAdapter {
|
||||
{abstract} +store_artifact(artifact_id: UUID, data: bytes, content_type: str) -> str
|
||||
{abstract} +retrieve_artifact(storage_key: str) -> bytes
|
||||
}
|
||||
|
||||
class InferenceAdapter {
|
||||
{abstract} +load_model(model_reference: str) -> None
|
||||
{abstract} +infer(input_data: bytes) -> dict
|
||||
{abstract} +unload_model(model_reference: str) -> None
|
||||
}
|
||||
|
||||
class PyTorchAdapter {
|
||||
+load_model(model_reference: str) -> None
|
||||
+infer(input_data: bytes) -> dict
|
||||
+unload_model(model_reference: str) -> None
|
||||
}
|
||||
|
||||
class TritonAdapter {
|
||||
+load_model(model_reference: str) -> None
|
||||
+infer(input_data: bytes) -> dict
|
||||
+unload_model(model_reference: str) -> None
|
||||
}
|
||||
|
||||
class MockAdapter {
|
||||
+load_model(model_reference: str) -> None
|
||||
+infer(input_data: bytes) -> dict
|
||||
+unload_model(model_reference: str) -> None
|
||||
}
|
||||
}
|
||||
|
||||
' Relationships: Domain Objects
|
||||
PatientCase "1" --> "*" DiagnosticSession : has
|
||||
DiagnosticSession "1" --> "*" ScanFrame : contains
|
||||
DiagnosticSession "1" --> "*" AnalysisJob : initiated
|
||||
DiagnosticSession "1" --> "*" ReviewDecision : reviewed_by
|
||||
DiagnosticSession "1" --> "*" Calibration : has
|
||||
DiagnosticSession "*" --> "1" ClinicianUser : conducted_by
|
||||
ScanFrame "1" --> "1" ImageAsset : stored_as
|
||||
ScanFrame "1" --> "1" PreprocessedImage : becomes
|
||||
AnalysisJob "1" --> "*" PipelineStep : consists_of
|
||||
AnalysisJob "1" --> "*" AnglePrediction : produces
|
||||
AnalysisJob "1" --> "*" InflammationPrediction : produces
|
||||
AnalysisJob "1" --> "*" SegmentationMask : produces
|
||||
AnalysisJob "1" --> "*" Measurement : produces
|
||||
AnalysisJob "1" --> "*" SynovitisGrade : produces
|
||||
ModelRegistryEntry "1" --> "*" ModelArtifact : has
|
||||
PipelineStep "*" --> "1" ModelRegistryEntry : uses
|
||||
ArtifactReference "1" --> "1" ScanFrame : references
|
||||
|
||||
' Relationships: Agents depend on Adapters
|
||||
DICOMIngestAgent --> FrameStorageAdapter : uses
|
||||
ImageUploadIngestAgent --> FrameStorageAdapter : uses
|
||||
ArtifactStoreAgent --> FrameStorageAdapter : uses
|
||||
ArtifactStoreAgent --> ArtifactStorageAdapter : uses
|
||||
InferenceRunner --> InferenceAdapter : uses
|
||||
ModelRegistryAgent --> ArtifactStorageAdapter : uses
|
||||
|
||||
' Relationships: Agents operate on Domain Objects
|
||||
DICOMIngestAgent --> ScanFrame : creates
|
||||
DICOMIngestAgent --> ImageAsset : creates
|
||||
ImageUploadIngestAgent --> ScanFrame : creates
|
||||
ImageUploadIngestAgent --> ImageAsset : creates
|
||||
FramePreprocessor --> ScanFrame : reads
|
||||
FramePreprocessor --> PreprocessedImage : creates
|
||||
AngleValidatorAgent --> AnglePrediction : validates
|
||||
ROICropperAgent --> PreprocessedImage : modifies
|
||||
VisionPipelineAgent --> AnalysisJob : orchestrates
|
||||
InferenceRunner --> AnalysisJob : populates
|
||||
MeasurementAgent --> SegmentationMask : reads
|
||||
MeasurementAgent --> Calibration : uses
|
||||
MeasurementAgent --> Measurement : creates
|
||||
SeverityScorerAgent --> InflammationPrediction : reads
|
||||
SeverityScorerAgent --> Measurement : reads
|
||||
SeverityScorerAgent --> SynovitisGrade : creates
|
||||
ModelRegistryAgent --> ModelRegistryEntry : manages
|
||||
ModelRegistryAgent --> ModelArtifact : manages
|
||||
LedgerWriterAgent --> AuditLedgerEntry : creates
|
||||
|
||||
' Relationships: Adapters
|
||||
PyTorchAdapter ..|> InferenceAdapter
|
||||
TritonAdapter ..|> InferenceAdapter
|
||||
MockAdapter ..|> InferenceAdapter
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
The diagram above shows:
|
||||
- **19 Domain Objects** with their attributes, methods, and relationships
|
||||
- **12 Agents/Services** with their interface methods and collaborators
|
||||
- **3 Adapter hierarchies** (Storage and Inference) showing abstraction relationships
|
||||
- **Dependency arrows** showing what objects depend on what adapters or other objects
|
||||
|
||||
Key relationships:
|
||||
- `PatientCase` 1→* `DiagnosticSession` (case has many sessions)
|
||||
- `DiagnosticSession` 1→* `ScanFrame` (session has many frames)
|
||||
- `AnalysisJob` 1→* `PipelineStep` (job has many steps)
|
||||
- All prediction/measurement objects belong to exactly one `AnalysisJob`
|
||||
- Agents depend on adapters (e.g., `DICOMIngestAgent` uses `FrameStorageAdapter`)
|
||||
|
||||
Legend:
|
||||
- `-->` : Association (uses or references)
|
||||
- `..|>` : Realization (implements interface)
|
||||
- Package groupings: Domain Objects, Agents/Services, Adapters
|
||||
89
workspace/sprint_1_2/CODEBASE/data/spec/schemas/__init__.py
Normal file
89
workspace/sprint_1_2/CODEBASE/data/spec/schemas/__init__.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from .auth_schemas import Token, TokenPayload, LoginRequest, UserProfile, UserUpdateRequest, RefreshRequest
|
||||
from .patient_schemas import Patient, PatientCreate, PatientListResponse, DemographicInfo
|
||||
from .session_schemas import (
|
||||
Session, SessionCreate, SessionDetail, SessionPatchReview,
|
||||
FrameMetadata, PersistResult, ExportResult, ScrubResult,
|
||||
)
|
||||
from .analysis_schemas import (
|
||||
AnalysisJobSubmit, AnalysisJobSyncSubmit, JobStatus, PipelineStep,
|
||||
StepEvent, JobResult, ModelRegistryEntry, ModelCatalog,
|
||||
ModelRegistrationResult,
|
||||
)
|
||||
from .telemetry_schemas import CorrectionSubmit, CorrectionRecord, AnomalyReport, AnomalyRecord
|
||||
from .report_schemas import ReportCreate, ReportSignRequest, ReportSyncEMRRequest, SyncResult
|
||||
from .safety_schemas import (
|
||||
GradCAMRequest, HeatmapResult, RationaleRequest, RationaleResult,
|
||||
CircuitBreakerRequest, ChatStreamRequest, ChatEvent, ChatResponse,
|
||||
DriftCheckResult, RAGEvidenceRequest, EvidenceList, ActivationMeta,
|
||||
AnnotationArtifact, GroundTruthLabel, EscalationRequest, EscalationTicket,
|
||||
MorphologyAnnotation, GuardrailCheckRequest, GuardrailResult,
|
||||
)
|
||||
from .notification_schemas import NotificationItem, NotificationPreferences
|
||||
from .settings_schemas import UserSettings, SettingsUpdate
|
||||
from .ingestion_schemas import IngestionRecord, RecordDetail
|
||||
from .common_schemas import HealthStatus, ErrorResponse
|
||||
|
||||
__all__ = [
|
||||
"Token",
|
||||
"TokenPayload",
|
||||
"LoginRequest",
|
||||
"UserProfile",
|
||||
"UserUpdateRequest",
|
||||
"RefreshRequest",
|
||||
"Patient",
|
||||
"PatientCreate",
|
||||
"PatientListResponse",
|
||||
"DemographicInfo",
|
||||
"Session",
|
||||
"SessionCreate",
|
||||
"SessionDetail",
|
||||
"SessionPatchReview",
|
||||
"FrameMetadata",
|
||||
"PersistResult",
|
||||
"ExportResult",
|
||||
"ScrubResult",
|
||||
"AnalysisJobSubmit",
|
||||
"AnalysisJobSyncSubmit",
|
||||
"JobStatus",
|
||||
"PipelineStep",
|
||||
"StepEvent",
|
||||
"JobResult",
|
||||
"ModelRegistryEntry",
|
||||
"ModelCatalog",
|
||||
"ModelRegistrationResult",
|
||||
"CorrectionSubmit",
|
||||
"CorrectionRecord",
|
||||
"AnomalyReport",
|
||||
"AnomalyRecord",
|
||||
"ReportCreate",
|
||||
"ReportSignRequest",
|
||||
"ReportSyncEMRRequest",
|
||||
"SyncResult",
|
||||
"GradCAMRequest",
|
||||
"HeatmapResult",
|
||||
"RationaleRequest",
|
||||
"RationaleResult",
|
||||
"CircuitBreakerRequest",
|
||||
"ChatStreamRequest",
|
||||
"ChatEvent",
|
||||
"ChatResponse",
|
||||
"DriftCheckResult",
|
||||
"RAGEvidenceRequest",
|
||||
"EvidenceList",
|
||||
"ActivationMeta",
|
||||
"AnnotationArtifact",
|
||||
"GroundTruthLabel",
|
||||
"EscalationRequest",
|
||||
"EscalationTicket",
|
||||
"MorphologyAnnotation",
|
||||
"GuardrailCheckRequest",
|
||||
"GuardrailResult",
|
||||
"NotificationItem",
|
||||
"NotificationPreferences",
|
||||
"UserSettings",
|
||||
"SettingsUpdate",
|
||||
"IngestionRecord",
|
||||
"RecordDetail",
|
||||
"HealthStatus",
|
||||
"ErrorResponse",
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AnalysisJobSubmit(BaseModel):
|
||||
session_id: str
|
||||
params: dict[str, Any] | None = None
|
||||
model_versions: dict[str, str] | None = None
|
||||
|
||||
|
||||
class AnalysisJobSyncSubmit(BaseModel):
|
||||
session_id: str
|
||||
params: dict[str, Any] | None = None
|
||||
model_versions: dict[str, str] | None = None
|
||||
|
||||
|
||||
class PipelineStep(BaseModel):
|
||||
step_id: str
|
||||
job_id: str
|
||||
task_type: str
|
||||
status: str
|
||||
output: dict | None = None
|
||||
duration_ms: int | None = None
|
||||
started_at: datetime | None = None
|
||||
completed_at: datetime | None = None
|
||||
|
||||
|
||||
class JobStatus(BaseModel):
|
||||
job_id: str
|
||||
session_id: str
|
||||
status: str
|
||||
result: dict | None = None
|
||||
steps: list[PipelineStep] | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class StepEvent(BaseModel):
|
||||
step_id: str
|
||||
job_id: str
|
||||
event_type: str
|
||||
task_type: str
|
||||
status: str
|
||||
data: dict | None = None
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
class JobResult(BaseModel):
|
||||
job_id: str
|
||||
session_id: str
|
||||
status: str
|
||||
result: dict | None = None
|
||||
duration_ms: int | None = None
|
||||
|
||||
|
||||
class ModelRegistryEntry(BaseModel):
|
||||
model_id: str
|
||||
name: str
|
||||
task_type: str
|
||||
version: str
|
||||
description: str
|
||||
framework: str
|
||||
labels: list[str]
|
||||
registered_at: datetime
|
||||
is_active: bool
|
||||
|
||||
|
||||
class ModelCatalog(BaseModel):
|
||||
models: list[ModelRegistryEntry]
|
||||
total: int
|
||||
|
||||
|
||||
class ModelRegistrationResult(BaseModel):
|
||||
model_id: str
|
||||
status: str
|
||||
s3_key: str
|
||||
registered_at: datetime
|
||||
@@ -0,0 +1,37 @@
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
sub: str
|
||||
exp: int
|
||||
role: str = "clinician"
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
username: str = Field(..., min_length=3, max_length=50)
|
||||
password: str = Field(..., min_length=6)
|
||||
|
||||
|
||||
class UserProfile(BaseModel):
|
||||
user_id: str
|
||||
username: str
|
||||
name: str
|
||||
role: str
|
||||
credentials: dict | None = None
|
||||
specialization: str | None = None
|
||||
|
||||
|
||||
class UserUpdateRequest(BaseModel):
|
||||
name: str | None = None
|
||||
specialization: str | None = None
|
||||
credentials: dict | None = None
|
||||
|
||||
|
||||
class RefreshRequest(BaseModel):
|
||||
refresh_token: str
|
||||
@@ -0,0 +1,14 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Any
|
||||
|
||||
|
||||
class HealthStatus(BaseModel):
|
||||
status: str
|
||||
version: str
|
||||
dependencies: dict[str, str]
|
||||
uptime_seconds: float
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
detail: str
|
||||
code: str | None = None
|
||||
@@ -0,0 +1,22 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class IngestionRecord(BaseModel):
|
||||
record_id: str
|
||||
user_id: str
|
||||
patient_id: str
|
||||
session_id: str | None = None
|
||||
filename: str
|
||||
file_type: str
|
||||
size_bytes: int
|
||||
status: str
|
||||
created_at: datetime
|
||||
metadata: dict | None = None
|
||||
|
||||
|
||||
class RecordDetail(IngestionRecord):
|
||||
s3_key: str
|
||||
checksum: str
|
||||
frame_count: int | None = None
|
||||
@@ -0,0 +1,22 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class NotificationPreferences(BaseModel):
|
||||
user_id: str
|
||||
email_enabled: bool = True
|
||||
push_enabled: bool = True
|
||||
in_app_enabled: bool = True
|
||||
categories: dict[str, bool] | None = None
|
||||
|
||||
|
||||
class NotificationItem(BaseModel):
|
||||
notification_id: str
|
||||
user_id: str
|
||||
title: str
|
||||
message: str
|
||||
category: str
|
||||
is_read: bool = False
|
||||
created_at: datetime
|
||||
metadata: dict | None = None
|
||||
@@ -0,0 +1,27 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DemographicInfo(BaseModel):
|
||||
age: int | None = None
|
||||
sex: str | None = None
|
||||
|
||||
|
||||
class PatientCreate(BaseModel):
|
||||
patient_identifier: str = Field(..., max_length=100)
|
||||
demographic_info: dict | None = None
|
||||
medical_history_summary: dict | None = None
|
||||
|
||||
|
||||
class Patient(BaseModel):
|
||||
case_id: str
|
||||
patient_identifier: str
|
||||
demographic_info: dict | None = None
|
||||
medical_history_summary: dict | None = None
|
||||
created_by: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class PatientListResponse(BaseModel):
|
||||
items: list[Patient]
|
||||
total: int
|
||||
@@ -0,0 +1,24 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ReportCreate(BaseModel):
|
||||
session_id: str
|
||||
payload: dict[str, Any]
|
||||
|
||||
|
||||
class ReportSignRequest(BaseModel):
|
||||
report_id: str
|
||||
signature: dict[str, Any]
|
||||
|
||||
|
||||
class ReportSyncEMRRequest(BaseModel):
|
||||
report_id: str
|
||||
|
||||
|
||||
class SyncResult(BaseModel):
|
||||
report_id: str
|
||||
emr_status: str
|
||||
emr_reference: str | None = None
|
||||
synced_at: datetime | None = None
|
||||
@@ -0,0 +1,120 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class GradCAMRequest(BaseModel):
|
||||
session_id: str
|
||||
layer_name: str | None = None
|
||||
|
||||
|
||||
class HeatmapResult(BaseModel):
|
||||
session_id: str
|
||||
heatmap_url: str
|
||||
overlay_url: str | None = None
|
||||
metadata: dict | None = None
|
||||
|
||||
|
||||
class RationaleRequest(BaseModel):
|
||||
session_id: str
|
||||
prompt: str | None = None
|
||||
|
||||
|
||||
class RationaleResult(BaseModel):
|
||||
session_id: str
|
||||
text: str
|
||||
confidence: float | None = None
|
||||
|
||||
|
||||
class CircuitBreakerRequest(BaseModel):
|
||||
session_id: str
|
||||
flag: bool
|
||||
|
||||
|
||||
class ChatStreamRequest(BaseModel):
|
||||
session_id: str
|
||||
prompt: str
|
||||
context: dict | None = None
|
||||
|
||||
|
||||
class ChatEvent(BaseModel):
|
||||
session_id: str
|
||||
event_type: str
|
||||
content: str
|
||||
is_final: bool = False
|
||||
metadata: dict | None = None
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
session_id: str
|
||||
response: str
|
||||
metadata: dict | None = None
|
||||
|
||||
|
||||
class DriftCheckResult(BaseModel):
|
||||
session_id: str
|
||||
drift_detected: bool
|
||||
drift_score: float
|
||||
threshold: float
|
||||
details: dict | None = None
|
||||
|
||||
|
||||
class RAGEvidenceRequest(BaseModel):
|
||||
session_id: str
|
||||
query: str | None = None
|
||||
|
||||
|
||||
class EvidenceList(BaseModel):
|
||||
session_id: str
|
||||
items: list[dict[str, Any]]
|
||||
|
||||
|
||||
class ActivationMeta(BaseModel):
|
||||
session_id: str
|
||||
activations: dict[str, Any]
|
||||
layer_info: dict | None = None
|
||||
|
||||
|
||||
class AnnotationArtifact(BaseModel):
|
||||
artifact_id: str
|
||||
session_id: str
|
||||
storage_key: str
|
||||
content_type: str
|
||||
size_bytes: int
|
||||
annotation_type: str
|
||||
|
||||
|
||||
class GroundTruthLabel(BaseModel):
|
||||
session_id: str
|
||||
label: dict[str, Any]
|
||||
|
||||
|
||||
class EscalationRequest(BaseModel):
|
||||
session_id: str
|
||||
reason: str
|
||||
|
||||
|
||||
class EscalationTicket(BaseModel):
|
||||
ticket_id: str
|
||||
session_id: str
|
||||
reason: str
|
||||
status: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class MorphologyAnnotation(BaseModel):
|
||||
session_id: str
|
||||
annotation: dict[str, Any]
|
||||
|
||||
|
||||
class GuardrailCheckRequest(BaseModel):
|
||||
session_id: str
|
||||
prompt: str
|
||||
score: float
|
||||
|
||||
|
||||
class GuardrailResult(BaseModel):
|
||||
session_id: str
|
||||
passed: bool
|
||||
flags: list[str]
|
||||
recommendations: list[str] | None = None
|
||||
@@ -0,0 +1,59 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SessionCreate(BaseModel):
|
||||
case_id: str
|
||||
patient_id: str
|
||||
|
||||
|
||||
class Session(BaseModel):
|
||||
session_id: str
|
||||
case_id: str
|
||||
clinician_id: str
|
||||
patient_id: str | None = None
|
||||
status: str = "created"
|
||||
calibration: dict | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class SessionDetail(Session):
|
||||
frame_count: int = 0
|
||||
job_count: int = 0
|
||||
|
||||
|
||||
class SessionPatchReview(BaseModel):
|
||||
status: str = Field(..., pattern="^(reviewed|completed|flagged)$")
|
||||
notes: str | None = None
|
||||
decisions: list[dict[str, Any]] | None = None
|
||||
|
||||
|
||||
class FrameMetadata(BaseModel):
|
||||
frame_id: str
|
||||
session_id: str
|
||||
storage_reference: str
|
||||
original_format: str
|
||||
frame_number: int | None = None
|
||||
metadata: dict | None = None
|
||||
checksum: str
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class PersistResult(BaseModel):
|
||||
session_id: str
|
||||
status: str
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ExportResult(BaseModel):
|
||||
session_id: str
|
||||
export_url: str
|
||||
expires_at: datetime
|
||||
|
||||
|
||||
class ScrubResult(BaseModel):
|
||||
session_id: str
|
||||
scrubbed_fields: list[str]
|
||||
phi_removed: bool
|
||||
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SettingsUpdate(BaseModel):
|
||||
updates: dict[str, Any]
|
||||
|
||||
|
||||
class UserSettings(BaseModel):
|
||||
user_id: str
|
||||
settings: dict[str, Any]
|
||||
updated_at: str | None = None
|
||||
@@ -0,0 +1,39 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
|
||||
class CorrectionSubmit(BaseModel):
|
||||
session_id: str
|
||||
quadrant: Literal["Q2", "Q3", "Q4"]
|
||||
event_type: str = Field(..., pattern="^(grade_override|mask_correction|ground_truth_commit|socratic_response|manual_morphology_annotation|drift_impass|artifact_isolation)$")
|
||||
clinician_correction: dict[str, Any] | None = None
|
||||
behavioral_telemetry: dict[str, Any] | None = None
|
||||
raw_artifacts: dict[str, Any] | None = None
|
||||
trust_level: Literal["high", "medium", "low"] | None = None
|
||||
|
||||
|
||||
class CorrectionRecord(BaseModel):
|
||||
correction_id: str
|
||||
session_id: str
|
||||
quadrant: str
|
||||
event_type: str
|
||||
status: str
|
||||
audit_trail_id: str
|
||||
queued_for_retraining: bool
|
||||
submitted_at: datetime
|
||||
|
||||
|
||||
class AnomalyReport(BaseModel):
|
||||
session_id: str
|
||||
data: dict[str, Any]
|
||||
|
||||
|
||||
class AnomalyRecord(BaseModel):
|
||||
anomaly_id: str
|
||||
session_id: str
|
||||
anomaly_type: str
|
||||
severity: str
|
||||
description: str
|
||||
metadata: dict | None = None
|
||||
reported_at: datetime
|
||||
Reference in New Issue
Block a user