Investment Plans workspace
Open raw ↗

# HAD Digital - Database Schema

Version: 1.0.0 Date: 5 September 2026 Database: SQLite (WAL mode) File: data/had.db


Entity Relationship Diagram (Conceptual)

patients (1) ──── (N) episodes
    │                   │
    │                   │
    ├── (N) treatment_plans
    │                   │
    ├── (N) toxicity_reports ──── (N) toxicity_grades ──── (N) alerts
    │                   │
    ├── (N) messages
    │                   │
    └── (N) timeline_events

users ──── (N) toxicity_reports (as reporter)
users ──── (N) alerts (as assigned_to/acknowledged_by)
users ──── (N) messages (as sender/recipient)

Tables

users

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique user ID
usernameTEXTUNIQUE NOT NULLLogin username
password_hashTEXTNOT NULLscrypt hash of password
saltTEXTNOT NULLSalt for password hashing
roleTEXTNOT NULL, CHECKUser role (see below)
display_nameTEXTNOT NULLFull display name
emailTEXTEmail address
patient_idINTEGERFOREIGN KEY → patients(id)Linked patient (for patient/caregiver roles)
activeINTEGERNOT NULL DEFAULT 1Account active flag
failed_attemptsINTEGERNOT NULL DEFAULT 0Failed login attempts
locked_untilTEXTAccount lock expiry timestamp
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Valid Roles:


patients

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique patient ID
mrnTEXTUNIQUE NOT NULLMedical Record Number
first_nameTEXTNOT NULLFirst name
last_nameTEXTNOT NULLLast name
date_of_birthTEXTNOT NULLDate of birth (YYYY-MM-DD)
genderTEXTCHECK ('M', 'F', 'X')Gender
phoneTEXTPhone number
addressTEXTHome address
emergency_contactTEXTEmergency contact info
primary_oncologist_idINTEGERFOREIGN KEY → users(id)Primary oncologist
cancer_typeTEXTCancer diagnosis
cancer_stageTEXTCancer stage
diagnosis_dateTEXTDate of diagnosis
statusTEXTNOT NULL DEFAULT 'active', CHECKPatient status
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Valid Statuses:


episodes

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique episode ID
patient_idINTEGERNOT NULL, FOREIGN KEY → patients(id)Patient reference
episode_typeTEXTNOT NULL, CHECKType of care episode
start_dateTEXTNOT NULLEpisode start date
end_dateTEXTEpisode end date
statusTEXTNOT NULL DEFAULT 'active', CHECKEpisode status
notesTEXTAdditional notes
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Valid Episode Types:

Valid Statuses:


treatment_plans

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique plan ID
patient_idINTEGERNOT NULL, FOREIGN KEY → patients(id)Patient reference
episode_idINTEGERFOREIGN KEY → episodes(id)Linked episode
protocol_nameTEXTNOT NULLTreatment protocol name
regimenTEXTNOT NULLRegimen description
cycle_countINTEGERTotal number of cycles
current_cycleINTEGERDEFAULT 0Current cycle number
cycle_length_daysINTEGERDays per cycle
drugsTEXTNOT NULLJSON array of drugs
start_dateTEXTNOT NULLTreatment start date
end_dateTEXTTreatment end date
statusTEXTNOT NULL DEFAULT 'active', CHECKPlan status
notesTEXTAdditional notes
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Valid Statuses:


toxicity_reports

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique report ID
patient_idINTEGERNOT NULL, FOREIGN KEY → patients(id)Patient reference
episode_idINTEGERFOREIGN KEY → episodes(id)Linked episode
treatment_plan_idINTEGERFOREIGN KEY → treatment_plans(id)Linked treatment plan
reporter_idINTEGERNOT NULL, FOREIGN KEY → users(id)User who submitted report
symptom_idTEXTNOT NULLCTCAE symptom term
symptom_categoryTEXTNOT NULLCTCAE domain (GI, hematologic, etc.)
report_dateTEXTNOT NULL DEFAULT datetime('now')Report submission date
severity_scoreREALRaw severity score
lab_valuesTEXTJSON of lab values
notesTEXTAdditional notes
statusTEXTNOT NULL DEFAULT 'pending', CHECKReport status
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Valid Statuses:


toxicity_grades

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique grade ID
report_idINTEGERNOT NULL, FOREIGN KEY → toxicity_reports(id)Linked report
patient_idINTEGERNOT NULL, FOREIGN KEY → patients(id)Patient reference
symptom_idTEXTNOT NULLCTCAE symptom term
gradeINTEGERNOT NULL, CHECK (1-5)CTCAE grade
criteriaTEXTNOT NULLGrading criteria applied
interventionTEXTRecommended intervention
provisionalINTEGERNOT NULL DEFAULT 1Provisional flag (1=yes, 0=no)
confirmed_byINTEGERFOREIGN KEY → users(id)Clinician who confirmed
confirmed_atTEXTConfirmation timestamp
grading_engine_versionTEXTEngine version used
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Grade Meanings:


alerts

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique alert ID
patient_idINTEGERNOT NULL, FOREIGN KEY → patients(id)Patient reference
grade_idINTEGERFOREIGN KEY → toxicity_grades(id)Linked grade
report_idINTEGERFOREIGN KEY → toxicity_reports(id)Linked report
alert_typeTEXTNOT NULL, CHECKAlert tier
severityTEXTNOT NULL, CHECKAlert severity level
titleTEXTNOT NULLAlert title
messageTEXTNOT NULLAlert message
assigned_toINTEGERFOREIGN KEY → users(id)Assigned user
assigned_roleTEXTAssigned role
statusTEXTNOT NULL DEFAULT 'pending', CHECKAlert status
acknowledged_byINTEGERFOREIGN KEY → users(id)User who acknowledged
acknowledged_atTEXTAcknowledgment timestamp
resolved_atTEXTResolution timestamp
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp
updated_atTEXTNOT NULL DEFAULT datetime('now')Last update timestamp

Valid Alert Types:

Valid Severities:

Valid Statuses:


messages

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique message ID
sender_idINTEGERNOT NULL, FOREIGN KEY → users(id)Message sender
recipient_idINTEGERFOREIGN KEY → users(id)Direct recipient
recipient_roleTEXTRole-based recipient
patient_idINTEGERFOREIGN KEY → patients(id)Linked patient
subjectTEXTMessage subject
bodyTEXTNOT NULLMessage body
message_typeTEXTNOT NULL DEFAULT 'direct', CHECKMessage type
readINTEGERNOT NULL DEFAULT 0Read flag (1=yes, 0=no)
read_atTEXTRead timestamp
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp

Valid Message Types:


timeline_events

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique event ID
patient_idINTEGERNOT NULL, FOREIGN KEY → patients(id)Patient reference
episode_idINTEGERFOREIGN KEY → episodes(id)Linked episode
event_typeTEXTNOT NULL, CHECKEvent type
event_dateTEXTNOT NULL DEFAULT datetime('now')Event date/time
titleTEXTNOT NULLEvent title
descriptionTEXTEvent description
reference_idINTEGERID of referenced entity
reference_tableTEXTTable of referenced entity
created_byINTEGERFOREIGN KEY → users(id)User who created event
created_atTEXTNOT NULL DEFAULT datetime('now')Creation timestamp

Valid Event Types:

ote - Clinical note


audit_log

ColumnTypeConstraintsDescription
idINTEGERPRIMARY KEY AUTOINCREMENTUnique entry ID
timestampTEXTNOT NULL DEFAULT datetime('now')Action timestamp
user_idINTEGERFOREIGN KEY → users(id)User who performed action
usernameTEXTUsername (for deleted users)
actionTEXTNOT NULLAction performed
resource_typeTEXTType of resource affected
resource_idINTEGERID of resource affected
detailsTEXTAdditional details
ip_addressTEXTClient IP address
user_agentTEXTClient user agent

Common Actions:


Indexes

The following indexes are recommended for production:

CREATE INDEX idx_patients_mrn ON patients(mrn);
CREATE INDEX idx_patients_status ON patients(status);
CREATE INDEX idx_episodes_patient ON episodes(patient_id);
CREATE INDEX idx_episodes_status ON episodes(status);
CREATE INDEX idx_treatment_plans_patient ON treatment_plans(patient_id);
CREATE INDEX idx_treatment_plans_status ON treatment_plans(status);
CREATE INDEX idx_toxicity_reports_patient ON toxicity_reports(patient_id);
CREATE INDEX idx_toxicity_reports_date ON toxicity_reports(report_date);
CREATE INDEX idx_toxicity_grades_report ON toxicity_grades(report_id);
CREATE INDEX idx_toxicity_grades_patient ON toxicity_grades(patient_id);
CREATE INDEX idx_alerts_patient ON alerts(patient_id);
CREATE INDEX idx_alerts_status ON alerts(status);
CREATE INDEX idx_alerts_type ON alerts(alert_type);
CREATE INDEX idx_messages_sender ON messages(sender_id);
CREATE INDEX idx_messages_recipient ON messages(recipient_id);
CREATE INDEX idx_messages_patient ON messages(patient_id);
CREATE INDEX idx_timeline_patient ON timeline_events(patient_id);
CREATE INDEX idx_timeline_date ON timeline_events(event_date);
CREATE INDEX idx_timeline_type ON timeline_events(event_type);
CREATE INDEX idx_audit_log_user ON audit_log(user_id);
CREATE INDEX idx_audit_log_action ON audit_log(action);
CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp);

Data Integrity

  1. Foreign Keys: All foreign key constraints are enforced. Deleting a parent record will fail if child records exist.
  1. Check Constraints: All CHECK constraints are enforced (roles, statuses, grades, etc.).
  1. Not Null: Required fields are marked NOT NULL.
  1. Timestamps: All tables use ISO 8601 format timestamps (UTC).
  1. Audit Trail: The audit_log table is append-only and cannot be modified or deleted through the API.

Migration Notes

The current schema is designed for SQLite but is structured for future PostgreSQL migration:

  1. All INTEGER PRIMARY KEY AUTOINCREMENT can become SERIAL PRIMARY KEY in PostgreSQL.
  1. All TEXT fields can become VARCHAR or TEXT in PostgreSQL.
  1. All datetime('now') defaults can become NOW() in PostgreSQL.
  1. CHECK constraints are compatible with PostgreSQL.
  1. JSON fields (lab_values, drugs) can use PostgreSQL's JSONB type.