omnidiag / docs /erd.md
yahyoha's picture
docs: add v2.0 roadmap, API docs, and .env.example
3e4503d
|
Raw
History Blame Contribute Delete
13.8 kB
# OmniDiag β€” Entity Relationship Diagram
## Overview
The OmniDiag database consists of **7 tables** supporting multi-disease clinical decision support with role-based access control, patient records, prediction history, expert review workflow, and immutable audit logging.
---
## Entity Relationship Diagram
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ roles β”‚ β”‚ user_roles β”‚ β”‚ users β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PK β”‚ id: Integer │◄──────│ FK β”‚ role_id: Integerβ”‚ β”‚ PK β”‚ id: UUID β”‚
β”‚ β”‚ name: String(50)β”‚ β”‚ FK β”‚ user_id: UUID │──────►│ β”‚ email: String(255) β”‚
β”‚ β”‚ description β”‚ β”‚ β”‚ assigned_at β”‚ β”‚ β”‚ hashed_password: Str β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ full_name: String(255)β”‚
β”‚ β”‚ is_active: Boolean β”‚
β”‚ β”‚ created_at: DateTime β”‚
β”‚ β”‚ updated_at: DateTime β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ β”‚
β–Ό β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ patients β”‚ β”‚ predictions β”‚ β”‚ audit_logs β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PK β”‚ id: UUID β”‚ β”‚ PK β”‚ id: UUID β”‚ β”‚ PK β”‚ id: BigInteger β”‚
β”‚ β”‚ mrn: String(50) │◄─────────────────────│ FK β”‚ patient_id: UUID β”‚ β”‚ FK β”‚ user_id: UUID β”‚
β”‚ β”‚ full_name: Str β”‚ β”‚ β”‚ disease: Str(100) β”‚ β”‚ β”‚ endpoint: Str(255) β”‚
β”‚ β”‚ date_of_birth β”‚ β”‚ β”‚ input_features: JSONβ”‚ β”‚ β”‚ method: String(10) β”‚
β”‚ β”‚ gender: Str(10) β”‚ β”‚ β”‚ prediction: Integer β”‚ β”‚ β”‚ status_code: Integer β”‚
β”‚ β”‚ contact_email β”‚ β”‚ β”‚ confidence: Float β”‚ β”‚ β”‚ ip_address: Str(45) β”‚
β”‚ FK β”‚ created_by: UUIDβ”‚ β”‚ β”‚ diagnosis: Str(100) β”‚ β”‚ β”‚ duration_ms: Float β”‚
β”‚ β”‚ created_at β”‚ β”‚ β”‚ shap_chart: JSON β”‚ β”‚ β”‚ created_at: DateTime β”‚
β”‚ β”‚ deleted_at β”‚ β”‚ FK β”‚ created_by: UUID β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ created_at: DateTimeβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ (1:1)
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ review_queue β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PK β”‚ id: UUID β”‚
β”‚ FK β”‚ prediction_id: UUID β”‚
β”‚ β”‚ uncertainty_score β”‚
β”‚ FK β”‚ reviewer_id: UUID β”‚
β”‚ β”‚ label: Integer β”‚
β”‚ β”‚ reviewed_at β”‚
β”‚ β”‚ status: String(20) β”‚
β”‚ β”‚ created_at: DateTimeβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
---
## Table Definitions
### 1. `roles`
Defines role-based access control levels.
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | `Integer` | `PK`, `autoincrement` | Auto-incrementing ID |
| `name` | `String(50)` | `UNIQUE`, `NOT NULL` | Role name: `super_admin`, `doctor`, `nurse`, `viewer` |
| `description` | `String(255)` | `nullable` | Human-readable description |
**Seed data:**
```sql
INSERT INTO roles (name, description) VALUES
('super_admin', 'Full system access β€” user management, audit review, model administration'),
('doctor', 'Clinical access β€” predict, explain, counterfactuals, patient records'),
('nurse', 'Limited clinical access β€” predict, view patient records'),
('viewer', 'Read-only access β€” view predictions and patient data');
```
---
### 2. `users`
System users (doctors, nurses, admins).
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | `UUID` | `PK`, `default=uuid4` | UUID primary key |
| `email` | `String(255)` | `UNIQUE`, `NOT NULL`, `INDEX` | Login email |
| `hashed_password` | `String(255)` | `NOT NULL` | bcrypt-hashed password |
| `full_name` | `String(255)` | `NOT NULL` | Display name |
| `is_active` | `Boolean` | `default=True` | Soft disable account |
| `created_at` | `DateTime(tz)` | `server_default=now()` | Account creation timestamp |
| `updated_at` | `DateTime(tz)` | `onupdate=now()` | Last profile update |
**Relationships:**
- `roles` (many-to-many via `user_roles` join table)
- `predictions` (one-to-many, as `created_by`)
- `patients` (one-to-many, as `created_by`)
- `audit_logs` (one-to-many)
- `review_queue` (one-to-many, as `reviewer_id`)
---
### 3. `user_roles`
Association (join) table linking users to roles (many-to-many).
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `user_id` | `UUID` | `FK β†’ users.id`, `PK (composite)` | Reference to user |
| `role_id` | `Integer` | `FK β†’ roles.id`, `PK (composite)` | Reference to role |
| `assigned_at` | `DateTime` | `server_default=now()` | When the role was assigned |
**Foreign Keys:**
- `user_id` β†’ `users.id` (`ON DELETE CASCADE`)
- `role_id` β†’ `roles.id` (`ON DELETE CASCADE`)
---
### 4. `patients`
Patient demographic records with soft-delete support.
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | `UUID` | `PK`, `default=uuid4` | UUID primary key |
| `mrn` | `String(50)` | `UNIQUE`, `NOT NULL`, `INDEX` | Medical Record Number |
| `full_name` | `String(255)` | `NOT NULL` | Patient's full name |
| `date_of_birth` | `Date` | `nullable` | Date of birth |
| `gender` | `String(10)` | `nullable` | Gender identity |
| `contact_email` | `String(255)` | `nullable` | Contact email |
| `created_by` | `UUID` | `FK β†’ users.id`, `nullable` | Who registered this patient |
| `created_at` | `DateTime(tz)` | `server_default=now()` | Registration timestamp |
| `deleted_at` | `DateTime` | `nullable` | Soft-delete timestamp (GDPR) |
**Foreign Keys:**
- `created_by` β†’ `users.id` (`SET NULL`)
**Relationships:**
- `predictions` (one-to-many)
---
### 5. `predictions`
Every prediction made by the system, linked to a patient (or anonymous).
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | `UUID` | `PK`, `default=uuid4` | UUID primary key |
| `patient_id` | `UUID` | `FK β†’ patients.id`, `nullable` | Patient (nullable for anonymous) |
| `disease` | `String(100)` | `NOT NULL`, `INDEX` | Disease key (e.g., `heart_disease`) |
| `input_features` | `JSON` | `NOT NULL` | Raw patient data dict sent to model |
| `prediction` | `Integer` | `NOT NULL` | Model output: `0` (negative) or `1` (positive) |
| `confidence` | `Float` | `NOT NULL` | Probability score `0.0`–`1.0` |
| `diagnosis` | `String(100)` | `nullable` | Human label: `"Positive"` / `"Negative"` |
| `shap_chart_data` | `JSON` | `nullable` | Stored SHAP values (if explain was called) |
| `created_by` | `UUID` | `FK β†’ users.id`, `nullable` | Who initiated the prediction |
| `created_at` | `DateTime(tz)` | `server_default=now()` | Prediction timestamp |
**Foreign Keys:**
- `patient_id` β†’ `patients.id` (`SET NULL`)
- `created_by` β†’ `users.id` (`SET NULL`)
**Relationships:**
- `patient` (many-to-one)
- `created_by` user (many-to-one)
- `review_queue` (one-to-one)
---
### 6. `review_queue`
Uncertain predictions (confidence 40–60%) flagged for expert review.
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | `UUID` | `PK`, `default=uuid4` | UUID primary key |
| `prediction_id` | `UUID` | `FK β†’ predictions.id`, `UNIQUE` | The uncertain prediction |
| `uncertainty_score` | `Float` | `nullable` | Entropy value |
| `reviewer_id` | `UUID` | `FK β†’ users.id`, `nullable` | Assigned reviewer |
| `label` | `Integer` | `nullable` | Expert annotation: `0` or `1` |
| `reviewed_at` | `DateTime` | `nullable` | When the review occurred |
| `status` | `String(20)` | `default="pending"` | `"pending"` \| `"reviewed"` \| `"skipped"` |
| `created_at` | `DateTime(tz)` | `server_default=now()` | When added to queue |
**Foreign Keys:**
- `prediction_id` β†’ `predictions.id` (`CASCADE`)
- `reviewer_id` β†’ `users.id` (`SET NULL`)
---
### 7. `audit_logs`
Immutable log of every authenticated API request for compliance.
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | `BigInteger` | `PK`, `autoincrement` | Auto-incrementing ID |
| `user_id` | `UUID` | `FK β†’ users.id`, `nullable` | Who made the request |
| `endpoint` | `String(255)` | `NOT NULL` | API path (e.g., `/api/v4/heart_disease/predict`) |
| `method` | `String(10)` | `NOT NULL` | HTTP method: `GET`, `POST`, etc. |
| `status_code` | `Integer` | `nullable` | HTTP response status code |
| `ip_address` | `String(45)` | `nullable` | Client IP (supports IPv6) |
| `duration_ms` | `Float` | `nullable` | Request duration in milliseconds |
| `created_at` | `DateTime(tz)` | `server_default=now()`, `INDEX` | Request timestamp |
**Foreign Keys:**
- `user_id` β†’ `users.id` (`SET NULL`)
---
## Referential Integrity Summary
| FK Constraint | From | To | On Delete |
|---------------|------|----|-----------|
| `user_roles.user_id` β†’ `users.id` | `user_roles` | `users` | `CASCADE` |
| `user_roles.role_id` β†’ `roles.id` | `user_roles` | `roles` | `CASCADE` |
| `patients.created_by` β†’ `users.id` | `patients` | `users` | `SET NULL` |
| `predictions.patient_id` β†’ `patients.id` | `predictions` | `patients` | `SET NULL` |
| `predictions.created_by` β†’ `users.id` | `predictions` | `users` | `SET NULL` |
| `review_queue.prediction_id` β†’ `predictions.id` | `review_queue` | `predictions` | `CASCADE` |
| `review_queue.reviewer_id` β†’ `users.id` | `review_queue` | `users` | `SET NULL` |
| `audit_logs.user_id` β†’ `users.id` | `audit_logs` | `users` | `SET NULL` |
---
## Indexes
| Table | Column(s) | Type | Purpose |
|-------|-----------|------|---------|
| `users` | `email` | Unique + B-tree | Fast login lookup |
| `patients` | `mrn` | Unique + B-tree | Fast MRN search |
| `predictions` | `disease` | B-tree | Filter predictions by disease |
| `predictions` | `patient_id` | B-tree | Lookup predictions by patient |
| `audit_logs` | `created_at` | B-tree | Time-range audit queries |
| `review_queue` | `prediction_id` | Unique + B-tree | One-to-one enforcement |
| `review_queue` | `status` | B-tree | Filter by review status |