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:

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