EHR & Patient Management Data Specifications

EHR & Patient Management Data Specifications

This document defines the complete data model for the EHR & Patient Management module. As the owning module for core shared entities (Patients, Providers, Users & Auth, Facilities), all canonical table definitions reside here. Other modules reference these tables via foreign keys.

UAE Regulatory Context: All data structures comply with UAE PDPL (Federal Decree-Law No. 45/2021) requirements for sensitive health data processing, including audit trail immutability, consent tracking, and data subject rights support. Field-level encryption is required for Emirates ID and other national identifiers per NESA/ADHICS standards.


Shared Entity References

This module owns the following shared entities referenced by all other modules:

Shared Entity Tables Referenced By (Other Modules)
Patients patients, patient_demographics, patient_identifiers All modules via FK to patients.patient_id
Providers providers, provider_credentials CPOE, RIS, LIS, PIS, Scheduling, Portals via FK to providers.provider_id
Users & Auth users, roles, permissions, role_permissions, user_roles All modules via FK to users.user_id
Facilities facilities, departments, locations All modules via FK to facilities.facility_id, departments.department_id, locations.location_id

This module references the following shared entities from other modules:

Shared Entity Owning Module FK Used
Encounters scheduling encounters.encounter_id
Payers policy-contract-mgmt payers.payer_id

Entity Relationship Diagram

erDiagram PATIENTS ||--o{ PATIENT_DEMOGRAPHICS : "has versions" PATIENTS ||--o{ PATIENT_IDENTIFIERS : "has" PATIENTS ||--o{ PATIENT_ALLERGIES : "has" PATIENTS ||--o{ PATIENT_PROBLEMS : "has" PATIENTS ||--o{ PATIENT_CONSENTS : "has" PATIENTS ||--o{ PATIENT_DOCUMENTS : "has" PATIENTS ||--o{ PATIENT_CONTACTS : "has" PATIENTS ||--o{ CLINICAL_NOTES : "has" PATIENTS ||--o{ DUPLICATE_SUSPECTS : "flagged as" PROVIDERS ||--o{ PROVIDER_CREDENTIALS : "holds" PROVIDERS ||--o{ CLINICAL_NOTES : "authors" USERS ||--o{ USER_ROLES : "assigned" ROLES ||--o{ USER_ROLES : "includes" ROLES ||--o{ ROLE_PERMISSIONS : "grants" PERMISSIONS ||--o{ ROLE_PERMISSIONS : "assigned to" USERS ||--o{ AUDIT_LOG : "generates" FACILITIES ||--o{ DEPARTMENTS : "contains" DEPARTMENTS ||--o{ LOCATIONS : "contains" PATIENTS { bigint patient_id PK bigint merged_into_patient_id FK } PROVIDERS { bigint provider_id PK } USERS { bigint user_id PK bigint provider_id FK bigint patient_id FK } FACILITIES { bigint facility_id PK } AUDIT_LOG { bigint audit_id PK bigint user_id FK }

Table Definitions

patients

Purpose: Core patient identity record. Single row per unique patient. Serves as the canonical patient reference for the entire HIS.

Field Name Data Type Length Nullable Default Validation Description
patient_id BIGINT NO AUTO_INCREMENT Primary key
mrn VARCHAR 20 NO UNIQUE; facility-prefix + sequence Medical Record Number
first_name_en VARCHAR 100 NO Non-empty Patient first/given name (English)
middle_name_en VARCHAR 100 YES NULL Middle name (English)
last_name_en VARCHAR 100 NO Non-empty Family/surname (English)
first_name_ar VARCHAR 100 YES NULL Arabic script validation First name (Arabic)
middle_name_ar VARCHAR 100 YES NULL Middle name (Arabic)
last_name_ar VARCHAR 100 YES NULL Family name (Arabic)
date_of_birth DATE NO <= CURRENT_DATE; plausibility check (not >150 years ago) Date of birth
gender VARCHAR 10 NO IN ('male', 'female', 'unknown') Administrative gender
nationality_code VARCHAR 3 YES NULL FK to ref_nationalities.nationality_code ISO 3166-1 alpha-3 nationality
is_vip BOOLEAN NO FALSE VIP flag (restricts access to authorized users only)
is_active BOOLEAN NO TRUE Active patient record (FALSE if merged or deceased)
is_deceased BOOLEAN NO FALSE Deceased indicator
deceased_datetime TIMESTAMP YES NULL Required if is_deceased = TRUE Date/time of death
merged_into_patient_id BIGINT YES NULL FK to patients.patient_id (self-ref) If merged, points to surviving record
blood_group VARCHAR 5 YES NULL IN ('A+','A-','B+','B-','AB+','AB-','O+','O-') ABO/Rh blood group
preferred_language VARCHAR 5 YES 'en' BCP-47 code Preferred communication language
photo_url VARCHAR 500 YES NULL Valid URL or storage path Patient photograph path
created_by BIGINT NO FK to users.user_id User who created record
updated_by BIGINT YES NULL FK to users.user_id User who last updated
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Record last update timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_patients_mrn ON patients(mrn);
CREATE INDEX idx_patients_name_en ON patients(last_name_en, first_name_en);
CREATE INDEX idx_patients_name_ar ON patients(last_name_ar, first_name_ar);
CREATE INDEX idx_patients_dob ON patients(date_of_birth);
CREATE INDEX idx_patients_active ON patients(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_patients_merged ON patients(merged_into_patient_id) WHERE merged_into_patient_id IS NOT NULL;

FHIR Mapping:

  • FHIR Resource: Patient
  • Key mappings:
  • patient_idid
  • mrnidentifier (type MR, system = facility OID)
  • first_name_en / last_name_enname[use=official].given / name.family
  • first_name_ar / last_name_arname[use=official, extension=language:ar]
  • date_of_birthbirthDate
  • gendergender
  • is_deceased / deceased_datetimedeceasedBoolean / deceasedDateTime
  • is_activeactive
  • merged_into_patient_idlink[type=replaced-by]
  • preferred_languagecommunication.language

patient_demographics

Purpose: Versioned demographic snapshots (SCD Type 2). Each update creates a new row; previous rows retained for audit history. Only one row per patient has is_current = TRUE.

Field Name Data Type Length Nullable Default Validation Description
demographic_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
is_current BOOLEAN NO TRUE Only one TRUE per patient_id Current active version
effective_from TIMESTAMP NO CURRENT_TIMESTAMP Version effective start
effective_to TIMESTAMP YES NULL NULL = current version Version effective end
address_line1_en VARCHAR 200 YES NULL Street address line 1 (English)
address_line2_en VARCHAR 200 YES NULL Street address line 2 (English)
address_line1_ar VARCHAR 200 YES NULL Street address line 1 (Arabic)
po_box VARCHAR 20 YES NULL PO Box number
emirate_code VARCHAR 3 YES NULL FK to ref_emirates_cities.emirate_code Emirate of residence
city VARCHAR 100 YES NULL City of residence
country_code VARCHAR 3 YES 'ARE' ISO 3166-1 alpha-3 Country of residence
mobile_phone VARCHAR 20 YES NULL E.164 format (+971XXXXXXXXX) Primary mobile number
home_phone VARCHAR 20 YES NULL E.164 format Home telephone
work_phone VARCHAR 20 YES NULL E.164 format Work telephone
email VARCHAR 200 YES NULL Valid email format Email address
marital_status VARCHAR 20 YES NULL IN ('single','married','divorced','widowed','separated','unknown') Marital status
occupation VARCHAR 100 YES NULL Current occupation
employer_name VARCHAR 200 YES NULL Employer name
religion VARCHAR 50 YES NULL Religion (optional, per patient consent)
created_by BIGINT NO FK to users.user_id User who created version
created_at TIMESTAMP NO CURRENT_TIMESTAMP Version creation timestamp

Indexes:

SQL
CREATE INDEX idx_patient_demo_patient ON patient_demographics(patient_id);
CREATE UNIQUE INDEX idx_patient_demo_current ON patient_demographics(patient_id) WHERE is_current = TRUE;
CREATE INDEX idx_patient_demo_emirate ON patient_demographics(emirate_code);
CREATE INDEX idx_patient_demo_mobile ON patient_demographics(mobile_phone);
CREATE INDEX idx_patient_demo_email ON patient_demographics(email);

FHIR Mapping:

  • Maps to Patient resource fields:
  • address_line1_en, city, emirate_code, country_codeaddress
  • mobile_phone, home_phone, emailtelecom
  • marital_statusmaritalStatus

patient_identifiers

Purpose: Stores all patient identifiers (MRN, Emirates ID, passport, visa number, insurance member ID). Supports multiple identifier types per patient with primary/secondary flags and expiry tracking.

Field Name Data Type Length Nullable Default Validation Description
identifier_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
identifier_type VARCHAR 30 NO IN ('MRN','EMIRATES_ID','PASSPORT','VISA','INSURANCE_MEMBER_ID','OLD_MRN','NATIONAL_ID','OTHER') Type of identifier
identifier_value VARCHAR 100 NO Non-empty Identifier value
identifier_system VARCHAR 200 YES NULL Issuing system/authority URI
is_primary BOOLEAN NO TRUE Primary identifier for this type
verification_status VARCHAR 20 NO 'unverified' IN ('verified','unverified','expired','invalid') Verification status
issue_date DATE YES NULL Date identifier was issued
expiry_date DATE YES NULL >= issue_date Expiry date (passports, visas, insurance)
issuing_authority VARCHAR 100 YES NULL Issuing authority (e.g., MOI, embassy)
created_by BIGINT NO FK to users.user_id User who created record
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update timestamp

Indexes:

SQL
CREATE INDEX idx_patient_idents_patient ON patient_identifiers(patient_id);
CREATE INDEX idx_patient_idents_type_value ON patient_identifiers(identifier_type, identifier_value);
CREATE UNIQUE INDEX idx_patient_idents_eid ON patient_identifiers(identifier_value) WHERE identifier_type = 'EMIRATES_ID' AND is_primary = TRUE;
CREATE INDEX idx_patient_idents_expiry ON patient_identifiers(expiry_date) WHERE expiry_date IS NOT NULL;

Constraints:

SQL
ALTER TABLE patient_identifiers
  ADD CONSTRAINT fk_ident_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  ADD CONSTRAINT chk_eid_format CHECK (
    identifier_type != 'EMIRATES_ID' OR identifier_value ~ '^784-[0-9]{4}-[0-9]{7}-[0-9]$'
  );

FHIR Mapping:

  • Maps to Patient.identifier[]:
  • identifier_typeidentifier.type.coding.code (MR, NI, PPN, etc.)
  • identifier_valueidentifier.value
  • identifier_systemidentifier.system
  • expiry_dateidentifier.period.end

patient_contacts

Purpose: Emergency contacts and next-of-kin for each patient.

Field Name Data Type Length Nullable Default Validation Description
contact_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
relationship VARCHAR 30 NO IN ('spouse','parent','child','sibling','guardian','friend','employer','other') Relationship to patient
contact_name_en VARCHAR 200 NO Non-empty Contact full name (English)
contact_name_ar VARCHAR 200 YES NULL Contact full name (Arabic)
mobile_phone VARCHAR 20 NO E.164 format Primary phone number
home_phone VARCHAR 20 YES NULL Home phone
email VARCHAR 200 YES NULL Valid email format Email address
is_emergency_contact BOOLEAN NO TRUE Is emergency contact
is_next_of_kin BOOLEAN NO FALSE Is legal next of kin
priority INT NO 1 >= 1 Contact priority order
created_by BIGINT NO FK to users.user_id Created by
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Updated timestamp

Indexes:

SQL
CREATE INDEX idx_patient_contacts_patient ON patient_contacts(patient_id);
CREATE INDEX idx_patient_contacts_emergency ON patient_contacts(patient_id, is_emergency_contact) WHERE is_emergency_contact = TRUE;

FHIR Mapping:

  • Maps to Patient.contact[]:
  • relationshipcontact.relationship.coding
  • contact_name_encontact.name
  • mobile_phonecontact.telecom

patient_allergies

Purpose: Stores patient allergy and adverse reaction records. Supports coded allergens (RxNorm for drugs, SNOMED for non-drug allergens), reaction types, and severity grading. NKA/NKDA status captured as special entries.

Field Name Data Type Length Nullable Default Validation Description
allergy_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
encounter_id BIGINT YES NULL FK to encounters.encounter_id Encounter when documented
allergen_type VARCHAR 20 NO IN ('medication','food','environmental','biologic','other') Category of allergen
allergen_description VARCHAR 300 NO Non-empty Free-text allergen name
allergen_code VARCHAR 20 YES NULL Coded allergen identifier
allergen_system VARCHAR 50 YES NULL IN ('RxNorm','SNOMED','NDFRT','FDB') when coded Coding system for allergen_code
reaction_description VARCHAR 500 YES NULL Description of reaction
reaction_code VARCHAR 20 YES NULL SNOMED CT code Coded reaction (e.g., urticaria, anaphylaxis)
severity VARCHAR 20 NO 'moderate' IN ('mild','moderate','severe','life-threatening') Reaction severity
criticality VARCHAR 10 YES NULL IN ('low','high','unable-to-assess') Clinical criticality assessment
status VARCHAR 20 NO 'active' IN ('active','inactive','resolved','entered-in-error','nka','nkda') Allergy record status
onset_date DATE YES NULL Approximate onset date
verification_status VARCHAR 20 NO 'unconfirmed' IN ('unconfirmed','confirmed','refuted','entered-in-error') Verification status
entered_by BIGINT NO FK to users.user_id Clinician who documented
verified_by BIGINT YES NULL FK to users.user_id Clinician who verified
verified_at TIMESTAMP YES NULL Verification timestamp
notes TEXT YES NULL Additional clinical notes
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update timestamp

Indexes:

SQL
CREATE INDEX idx_allergies_patient ON patient_allergies(patient_id);
CREATE INDEX idx_allergies_status ON patient_allergies(patient_id, status) WHERE status = 'active';
CREATE INDEX idx_allergies_allergen ON patient_allergies(allergen_code, allergen_system);
CREATE INDEX idx_allergies_encounter ON patient_allergies(encounter_id);

Constraints:

SQL
ALTER TABLE patient_allergies
  ADD CONSTRAINT fk_allergy_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  ADD CONSTRAINT fk_allergy_encounter FOREIGN KEY (encounter_id) REFERENCES encounters(encounter_id),
  ADD CONSTRAINT fk_allergy_entered_by FOREIGN KEY (entered_by) REFERENCES users(user_id),
  ADD CONSTRAINT chk_nka_no_allergen CHECK (
    status NOT IN ('nka','nkda') OR (allergen_code IS NULL AND allergen_description = status)
  );

Terminology Binding:

  • RxNorm: Drug allergens (allergen_code when allergen_system = 'RxNorm')
  • SNOMED CT: Non-drug allergens and reaction codes (allergen_code when allergen_system = 'SNOMED'; reaction_code)
  • FHIR ValueSet: http://hl7.org/fhir/ValueSet/allergyintolerance-code

FHIR Mapping:

  • FHIR Resource: AllergyIntolerance
  • Key mappings:
  • allergy_idid
  • patient_idpatient.reference
  • allergen_code + allergen_systemcode.coding
  • allergen_typecategory
  • severity / criticalityreaction.severity / criticality
  • statusclinicalStatus
  • verification_statusverificationStatus

patient_problems

Purpose: Patient problem list with ICD-10-AM and SNOMED CT coding. Tracks active, resolved, and chronic conditions with encounter linkage.

Field Name Data Type Length Nullable Default Validation Description
problem_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
encounter_id BIGINT YES NULL FK to encounters.encounter_id Encounter when documented
problem_description VARCHAR 500 NO Non-empty Free-text problem description
icd10_code VARCHAR 10 YES NULL Valid ICD-10-AM format ICD-10-AM diagnosis code
icd10_description VARCHAR 300 YES NULL ICD-10-AM code description
snomed_code VARCHAR 20 YES NULL Valid SNOMED CT concept ID SNOMED CT concept code
snomed_description VARCHAR 300 YES NULL SNOMED CT preferred term
problem_type VARCHAR 20 NO 'problem' IN ('problem','diagnosis','symptom','finding','complaint') Problem classification
status VARCHAR 20 NO 'active' IN ('active','resolved','inactive','recurrence','remission') Clinical status
severity VARCHAR 20 YES NULL IN ('mild','moderate','severe') Problem severity
onset_date DATE YES NULL Approximate onset date
resolved_date DATE YES NULL >= onset_date; required if status = 'resolved' Date problem resolved
is_chronic BOOLEAN NO FALSE Chronic condition flag
is_principal BOOLEAN NO FALSE Principal diagnosis for encounter
entered_by BIGINT NO FK to users.user_id Clinician who documented
verified_by BIGINT YES NULL FK to users.user_id Clinician who verified
notes TEXT YES NULL Additional clinical notes
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update timestamp

Indexes:

SQL
CREATE INDEX idx_problems_patient ON patient_problems(patient_id);
CREATE INDEX idx_problems_active ON patient_problems(patient_id, status) WHERE status = 'active';
CREATE INDEX idx_problems_icd10 ON patient_problems(icd10_code);
CREATE INDEX idx_problems_snomed ON patient_problems(snomed_code);
CREATE INDEX idx_problems_encounter ON patient_problems(encounter_id);
CREATE INDEX idx_problems_chronic ON patient_problems(patient_id, is_chronic) WHERE is_chronic = TRUE;

Terminology Binding:

  • ICD-10-AM (Australian Modification, adopted by UAE): Primary diagnosis coding (icd10_code)
  • SNOMED CT: Clinical terminology for problem description (snomed_code)
  • FHIR ValueSet: http://hl7.org/fhir/ValueSet/condition-code

FHIR Mapping:

  • FHIR Resource: Condition
  • Key mappings:
  • problem_idid
  • patient_idsubject.reference
  • icd10_codecode.coding[system=http://hl7.org/fhir/sid/icd-10]
  • snomed_codecode.coding[system=http://snomed.info/sct]
  • statusclinicalStatus
  • onset_dateonsetDateTime
  • resolved_dateabatementDateTime
  • is_principalcategory (encounter-diagnosis)

patient_consents

Purpose: Tracks all patient consents as required by UAE PDPL. Covers treatment consent, data processing, HIE sharing (NABIDH/Malaffi), research participation, and portal registration. Immutable once signed — amendments create new rows.

Field Name Data Type Length Nullable Default Validation Description
consent_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
encounter_id BIGINT YES NULL FK to encounters.encounter_id Encounter context (if applicable)
consent_type_code VARCHAR 50 NO FK to ref_consent_types.consent_type_code Type of consent
consent_template_id BIGINT YES NULL FK to ref_consent_templates.template_id Template version used
status VARCHAR 20 NO IN ('granted','declined','withdrawn','expired') Consent status
processing_legal_basis VARCHAR 30 NO IN ('explicit_consent','treatment_exemption','public_health','legal_obligation','vital_interest','contractual') UAE PDPL legal basis
granted_datetime TIMESTAMP YES NULL When consent was granted
withdrawn_datetime TIMESTAMP YES NULL >= granted_datetime When consent was withdrawn
expiry_datetime TIMESTAMP YES NULL >= granted_datetime Consent expiry (if time-limited)
signature_method VARCHAR 30 YES NULL IN ('digital_pad','portal_click','verbal_witnessed','paper_scanned') How consent was captured
signature_data TEXT YES NULL Digital signature blob or reference
document_id BIGINT YES NULL FK to patient_documents.document_id Signed consent document
witnessed_by BIGINT YES NULL FK to users.user_id Witness user ID
collected_by BIGINT NO FK to users.user_id User who collected consent
notes TEXT YES NULL Additional notes
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation timestamp

Indexes:

SQL
CREATE INDEX idx_consents_patient ON patient_consents(patient_id);
CREATE INDEX idx_consents_type ON patient_consents(patient_id, consent_type_code);
CREATE INDEX idx_consents_status ON patient_consents(status);
CREATE INDEX idx_consents_expiry ON patient_consents(expiry_datetime) WHERE expiry_datetime IS NOT NULL AND status = 'granted';

Constraints:

SQL
ALTER TABLE patient_consents
  ADD CONSTRAINT fk_consent_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  ADD CONSTRAINT fk_consent_encounter FOREIGN KEY (encounter_id) REFERENCES encounters(encounter_id),
  ADD CONSTRAINT chk_granted_datetime CHECK (status != 'granted' OR granted_datetime IS NOT NULL),
  ADD CONSTRAINT chk_withdrawn_datetime CHECK (status != 'withdrawn' OR withdrawn_datetime IS NOT NULL);

FHIR Mapping:

  • FHIR Resource: Consent
  • Key mappings:
  • consent_idid
  • patient_idpatient.reference
  • statusstatus (active | rejected | inactive)
  • consent_type_codecategory.coding
  • granted_datetime / expiry_datetimeprovision.period
  • processing_legal_basispolicyRule

patient_documents

Purpose: Metadata for all patient-related documents — scanned IDs, insurance cards, signed consents, clinical documents, discharge summaries. Actual file content stored in document management system (DMS); this table holds metadata and storage references.

Field Name Data Type Length Nullable Default Validation Description
document_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
encounter_id BIGINT YES NULL FK to encounters.encounter_id Associated encounter
document_type_code VARCHAR 50 NO FK to ref_document_types.document_type_code Document category
document_title VARCHAR 300 NO Non-empty Document title/description
mime_type VARCHAR 100 NO Valid MIME type File MIME type (application/pdf, image/jpeg, etc.)
file_size_bytes BIGINT YES NULL > 0 File size in bytes
storage_path VARCHAR 500 NO Non-empty DMS storage path or object key
storage_system VARCHAR 30 NO 'dms' IN ('dms','s3','azure_blob','local') Storage backend
status VARCHAR 20 NO 'active' IN ('active','archived','deleted','pending_scan') Document status
uploaded_by BIGINT NO FK to users.user_id User who uploaded
upload_datetime TIMESTAMP NO CURRENT_TIMESTAMP Upload timestamp
retention_expiry DATE YES NULL Retention period expiry (per UAE regulations)
checksum_sha256 VARCHAR 64 YES NULL File integrity hash
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update

Indexes:

SQL
CREATE INDEX idx_documents_patient ON patient_documents(patient_id);
CREATE INDEX idx_documents_encounter ON patient_documents(encounter_id);
CREATE INDEX idx_documents_type ON patient_documents(document_type_code);
CREATE INDEX idx_documents_status ON patient_documents(status);
CREATE INDEX idx_documents_retention ON patient_documents(retention_expiry) WHERE retention_expiry IS NOT NULL;

FHIR Mapping:

  • FHIR Resource: DocumentReference
  • Key mappings:
  • document_idid
  • patient_idsubject.reference
  • document_type_codetype.coding
  • statusstatus
  • mime_typecontent.attachment.contentType
  • storage_pathcontent.attachment.url

clinical_notes

Purpose: All clinical documentation — H&P notes, progress notes, discharge summaries, consultation notes, nursing assessments. Supports structured templates, co-signing, and addenda.

Field Name Data Type Length Nullable Default Validation Description
note_id BIGINT NO AUTO_INCREMENT Primary key
patient_id BIGINT NO FK to patients.patient_id Patient reference
encounter_id BIGINT NO FK to encounters.encounter_id Encounter context
note_type VARCHAR 30 NO IN ('H_AND_P','PROGRESS','DISCHARGE','CONSULT','NURSING_ASSESSMENT','PROCEDURE','OPERATIVE','ED','ADDENDUM') Note category
template_id BIGINT YES NULL FK to ref_note_templates.template_id Template used (if structured)
note_title VARCHAR 300 NO Non-empty Note title
note_body TEXT NO Non-empty Full text of clinical note
structured_data JSON YES NULL Structured fields from template
status VARCHAR 20 NO 'draft' IN ('draft','pending_cosign','signed','amended','entered_in_error') Note workflow status
author_provider_id BIGINT NO FK to providers.provider_id Authoring clinician
authored_datetime TIMESTAMP NO CURRENT_TIMESTAMP When note was authored
signed_by BIGINT YES NULL FK to providers.provider_id Signing provider
signed_datetime TIMESTAMP YES NULL >= authored_datetime When note was signed
cosigner_provider_id BIGINT YES NULL FK to providers.provider_id Co-signer (attendings for residents)
cosigned_datetime TIMESTAMP YES NULL >= signed_datetime When co-signed
parent_note_id BIGINT YES NULL FK to clinical_notes.note_id (self-ref) Parent note (for addenda)
specialty_code VARCHAR 20 YES NULL FK to ref_provider_specialties.specialty_code Clinical specialty
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update

Indexes:

SQL
CREATE INDEX idx_notes_patient ON clinical_notes(patient_id);
CREATE INDEX idx_notes_encounter ON clinical_notes(encounter_id);
CREATE INDEX idx_notes_author ON clinical_notes(author_provider_id);
CREATE INDEX idx_notes_status ON clinical_notes(status);
CREATE INDEX idx_notes_type ON clinical_notes(note_type);
CREATE INDEX idx_notes_pending ON clinical_notes(status, authored_datetime) WHERE status IN ('draft', 'pending_cosign');
CREATE INDEX idx_notes_parent ON clinical_notes(parent_note_id) WHERE parent_note_id IS NOT NULL;

FHIR Mapping:

  • FHIR Resource: DocumentReference (for metadata) / Composition (for structured notes)
  • Key mappings:
  • note_idid
  • patient_idsubject.reference
  • encounter_idcontext.encounter.reference
  • author_provider_idauthor.reference
  • note_typetype.coding (LOINC document type codes)
  • statusdocStatus
  • note_bodycontent.attachment

duplicate_suspects

Purpose: Tracks potential duplicate patient records identified by the MPI matching engine or manual flagging. Supports the duplicate resolution workflow (WF-EHRPATIENTMGMT-003).

Field Name Data Type Length Nullable Default Validation Description
suspect_id BIGINT NO AUTO_INCREMENT Primary key
patient_id_1 BIGINT NO FK to patients.patient_id First patient in suspected pair
patient_id_2 BIGINT NO FK to patients.patient_id; != patient_id_1 Second patient in suspected pair
match_score DECIMAL 5,2 NO 0.00–100.00 MPI match confidence score
match_method VARCHAR 30 NO IN ('deterministic','probabilistic','manual','registration_flag') How duplicate was detected
matching_fields JSON YES NULL Fields that matched (e.g., {"emirates_id": true, "dob": true, "name": 0.85})
status VARCHAR 20 NO 'new' IN ('new','in_review','pending_approval','resolved','rejected','auto_resolved') Resolution status
resolution VARCHAR 20 YES NULL IN ('merged','not_duplicate','deferred') when status = 'resolved' Resolution outcome
primary_patient_id BIGINT YES NULL FK to patients.patient_id Surviving patient after merge
flagged_at TIMESTAMP NO CURRENT_TIMESTAMP When duplicate was flagged
flagged_by BIGINT YES NULL FK to users.user_id (NULL if system-detected) User who flagged (if manual)
reviewed_by BIGINT YES NULL FK to users.user_id MRO who reviewed
reviewed_at TIMESTAMP YES NULL Review timestamp
approved_by BIGINT YES NULL FK to users.user_id HIM Supervisor who approved merge
approved_at TIMESTAMP YES NULL Approval timestamp
notes TEXT YES NULL Review notes
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update

Indexes:

SQL
CREATE INDEX idx_dupes_patient1 ON duplicate_suspects(patient_id_1);
CREATE INDEX idx_dupes_patient2 ON duplicate_suspects(patient_id_2);
CREATE INDEX idx_dupes_status ON duplicate_suspects(status);
CREATE UNIQUE INDEX idx_dupes_pair ON duplicate_suspects(
  LEAST(patient_id_1, patient_id_2), GREATEST(patient_id_1, patient_id_2)
) WHERE status NOT IN ('resolved','rejected');
CREATE INDEX idx_dupes_score ON duplicate_suspects(match_score DESC);

providers

Purpose: Registry of all clinical and administrative providers (physicians, nurses, pharmacists, technicians). Canonical definition — all modules reference via FK.

Field Name Data Type Length Nullable Default Validation Description
provider_id BIGINT NO AUTO_INCREMENT Primary key
staff_number VARCHAR 20 NO UNIQUE Internal staff/badge number
first_name_en VARCHAR 100 NO Non-empty First name (English)
middle_name_en VARCHAR 100 YES NULL Middle name (English)
last_name_en VARCHAR 100 NO Non-empty Last name (English)
first_name_ar VARCHAR 100 YES NULL First name (Arabic)
last_name_ar VARCHAR 100 YES NULL Last name (Arabic)
provider_type VARCHAR 30 NO IN ('physician','nurse','pharmacist','technician','therapist','admin','other') Provider category
specialty_code VARCHAR 20 YES NULL FK to ref_provider_specialties.specialty_code Primary specialty
subspecialty_code VARCHAR 20 YES NULL Subspecialty if applicable
license_number VARCHAR 50 NO Non-empty Healthcare professional license number
licensing_authority VARCHAR 10 NO IN ('DOH','DHA','MOH') Licensing body
license_expiry_date DATE NO >= CURRENT_DATE for active providers License expiry date
national_id VARCHAR 20 YES NULL Emirates ID or national ID
email VARCHAR 200 YES NULL Valid email Professional email
mobile_phone VARCHAR 20 YES NULL E.164 format Professional mobile
department_id BIGINT YES NULL FK to departments.department_id Primary department
facility_id BIGINT NO FK to facilities.facility_id Primary facility
is_active BOOLEAN NO TRUE Active provider flag
doh_provider_id VARCHAR 30 YES NULL DOH-assigned provider ID (Abu Dhabi)
dha_provider_id VARCHAR 30 YES NULL DHA-assigned provider ID (Dubai)
can_prescribe BOOLEAN NO FALSE Has prescribing authority
can_prescribe_controlled BOOLEAN NO FALSE Can prescribe UAE MOH controlled substances
digital_signature_cert VARCHAR 500 YES NULL Digital certificate reference for e-signing
created_by BIGINT NO FK to users.user_id Created by
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Updated timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_providers_staff ON providers(staff_number);
CREATE INDEX idx_providers_name ON providers(last_name_en, first_name_en);
CREATE INDEX idx_providers_specialty ON providers(specialty_code);
CREATE INDEX idx_providers_license ON providers(license_number, licensing_authority);
CREATE INDEX idx_providers_facility ON providers(facility_id);
CREATE INDEX idx_providers_department ON providers(department_id);
CREATE INDEX idx_providers_active ON providers(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_providers_license_expiry ON providers(license_expiry_date);

FHIR Mapping:

  • FHIR Resource: Practitioner / PractitionerRole
  • Key mappings:
  • provider_idPractitioner.id
  • staff_numberPractitioner.identifier
  • first_name_en / last_name_enPractitioner.name
  • license_numberPractitioner.qualification.identifier
  • specialty_codePractitionerRole.specialty
  • facility_idPractitionerRole.organization
  • department_idPractitionerRole.location

provider_credentials

Purpose: Tracks provider credentials, certifications, board certifications, and privileges with expiry dates for credentialing compliance.

Field Name Data Type Length Nullable Default Validation Description
credential_id BIGINT NO AUTO_INCREMENT Primary key
provider_id BIGINT NO FK to providers.provider_id Provider reference
credential_type VARCHAR 30 NO IN ('license','board_cert','degree','privilege','cpr_cert','malpractice_ins','other') Credential category
credential_name VARCHAR 200 NO Non-empty Credential description
credential_number VARCHAR 100 YES NULL Certificate/license number
issuing_authority VARCHAR 200 NO Issuing organization
issue_date DATE YES NULL Date issued
expiry_date DATE YES NULL >= issue_date Expiry date
status VARCHAR 20 NO 'active' IN ('active','expired','revoked','pending_renewal','suspended') Credential status
document_id BIGINT YES NULL FK to patient_documents.document_id Scanned credential document
verified_by BIGINT YES NULL FK to users.user_id User who verified
verified_at TIMESTAMP YES NULL Verification timestamp
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update

Indexes:

SQL
CREATE INDEX idx_credentials_provider ON provider_credentials(provider_id);
CREATE INDEX idx_credentials_type ON provider_credentials(credential_type);
CREATE INDEX idx_credentials_expiry ON provider_credentials(expiry_date) WHERE status = 'active';

users

Purpose: System user accounts for authentication and authorization. Links to providers for clinical users or patients for portal users. Canonical definition — all modules reference via FK.

Field Name Data Type Length Nullable Default Validation Description
user_id BIGINT NO AUTO_INCREMENT Primary key
username VARCHAR 50 NO UNIQUE; alphanumeric + underscore Login username
email VARCHAR 200 NO UNIQUE; valid email Email address
password_hash VARCHAR 255 NO bcrypt or argon2 hash Hashed password
provider_id BIGINT YES NULL FK to providers.provider_id Linked provider (clinical users)
patient_id BIGINT YES NULL FK to patients.patient_id Linked patient (portal users)
user_type VARCHAR 20 NO IN ('clinical','administrative','portal_patient','portal_provider','system','integration') User category
display_name VARCHAR 200 NO Non-empty Display name
preferred_language VARCHAR 5 YES 'en' BCP-47 code UI language preference
is_active BOOLEAN NO TRUE Account active flag
is_locked BOOLEAN NO FALSE Account locked (failed logins)
locked_at TIMESTAMP YES NULL When account was locked
last_login_at TIMESTAMP YES NULL Last successful login
last_login_ip VARCHAR 45 YES NULL Last login IP (IPv4/IPv6)
failed_login_count INT NO 0 >= 0 Consecutive failed login attempts
password_changed_at TIMESTAMP YES NULL Last password change
mfa_enabled BOOLEAN NO FALSE Multi-factor authentication enabled
mfa_secret VARCHAR 255 YES NULL Encrypted MFA secret (TOTP)
created_by BIGINT YES NULL FK to users.user_id (self-ref) Created by (NULL for system)
created_at TIMESTAMP NO CURRENT_TIMESTAMP Account creation
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Last update

Indexes:

SQL
CREATE UNIQUE INDEX idx_users_username ON users(username);
CREATE UNIQUE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_provider ON users(provider_id) WHERE provider_id IS NOT NULL;
CREATE INDEX idx_users_patient ON users(patient_id) WHERE patient_id IS NOT NULL;
CREATE INDEX idx_users_active ON users(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_users_type ON users(user_type);

Constraints:

SQL
ALTER TABLE users
  ADD CONSTRAINT fk_user_provider FOREIGN KEY (provider_id) REFERENCES providers(provider_id),
  ADD CONSTRAINT fk_user_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  ADD CONSTRAINT chk_user_link CHECK (
    (user_type = 'portal_patient' AND patient_id IS NOT NULL) OR
    (user_type IN ('clinical','portal_provider') AND provider_id IS NOT NULL) OR
    (user_type IN ('administrative','system','integration'))
  );

roles

Purpose: RBAC role definitions. Roles are assigned to users via user_roles and grant permissions via role_permissions.

Field Name Data Type Length Nullable Default Validation Description
role_id BIGINT NO AUTO_INCREMENT Primary key
role_code VARCHAR 50 NO UNIQUE; immutable once created Machine-readable role identifier
role_name_en VARCHAR 100 NO Non-empty Role display name (English)
role_name_ar VARCHAR 100 YES NULL Role display name (Arabic)
description TEXT YES NULL Role description and scope
role_category VARCHAR 30 NO IN ('clinical','administrative','portal','system','integration') Role category
is_system_role BOOLEAN NO FALSE System-managed role (cannot be deleted)
is_active BOOLEAN NO TRUE Active role flag
created_by BIGINT NO FK to users.user_id Created by
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Updated timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_roles_code ON roles(role_code);
CREATE INDEX idx_roles_category ON roles(role_category);
CREATE INDEX idx_roles_active ON roles(is_active) WHERE is_active = TRUE;

permissions

Purpose: Granular permission definitions for RBAC. Each permission represents one action on one resource within one module.

Field Name Data Type Length Nullable Default Validation Description
permission_id BIGINT NO AUTO_INCREMENT Primary key
permission_code VARCHAR 80 NO UNIQUE; format: module.resource.action Machine-readable permission ID
module VARCHAR 30 NO Module identifier Module this permission belongs to
resource VARCHAR 50 NO Resource being acted upon
action VARCHAR 20 NO IN ('create','read','update','delete','sign','approve','export','print') Action type
description TEXT YES NULL Permission description
is_sensitive BOOLEAN NO FALSE Requires elevated audit logging
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_permissions_code ON permissions(permission_code);
CREATE INDEX idx_permissions_module ON permissions(module);
CREATE UNIQUE INDEX idx_permissions_unique ON permissions(module, resource, action);

role_permissions

Purpose: Junction table linking roles to permissions.

Field Name Data Type Length Nullable Default Validation Description
role_id BIGINT NO FK to roles.role_id Role reference
permission_id BIGINT NO FK to permissions.permission_id Permission reference
granted_at TIMESTAMP NO CURRENT_TIMESTAMP When permission was assigned
granted_by BIGINT NO FK to users.user_id Who assigned it

Primary Key: (role_id, permission_id)

Indexes:

SQL
CREATE INDEX idx_role_perms_role ON role_permissions(role_id);
CREATE INDEX idx_role_perms_perm ON role_permissions(permission_id);

user_roles

Purpose: Junction table assigning roles to users, scoped to facility. Users can hold different roles at different facilities.

Field Name Data Type Length Nullable Default Validation Description
user_id BIGINT NO FK to users.user_id User reference
role_id BIGINT NO FK to roles.role_id Role reference
facility_id BIGINT YES NULL FK to facilities.facility_id; NULL = all facilities Facility scope
is_active BOOLEAN NO TRUE Assignment active flag
effective_from DATE NO CURRENT_DATE Assignment start date
effective_to DATE YES NULL >= effective_from Assignment end date
assigned_by BIGINT NO FK to users.user_id Who assigned role
assigned_at TIMESTAMP NO CURRENT_TIMESTAMP Assignment timestamp

Primary Key: (user_id, role_id, facility_id) — with COALESCE for NULL facility

Indexes:

SQL
CREATE INDEX idx_user_roles_user ON user_roles(user_id);
CREATE INDEX idx_user_roles_role ON user_roles(role_id);
CREATE INDEX idx_user_roles_facility ON user_roles(facility_id);
CREATE INDEX idx_user_roles_active ON user_roles(user_id, is_active) WHERE is_active = TRUE;

facilities

Purpose: Healthcare facility registry. Canonical definition — all modules reference via FK. Supports multi-emirate, multi-facility deployments.

Field Name Data Type Length Nullable Default Validation Description
facility_id BIGINT NO AUTO_INCREMENT Primary key
facility_code VARCHAR 20 NO UNIQUE Short code (e.g., HOSP-DXB-01)
facility_name_en VARCHAR 200 NO Non-empty Facility name (English)
facility_name_ar VARCHAR 200 YES NULL Facility name (Arabic)
facility_type VARCHAR 30 NO IN ('hospital','clinic','day_surgery','pharmacy','lab','diagnostic_center','rehabilitation','home_health') Facility category
licensing_authority VARCHAR 10 NO IN ('DOH','DHA','MOH') Licensing regulator
license_number VARCHAR 50 NO Non-empty Facility license number
license_expiry_date DATE NO License expiry
emirate_code VARCHAR 3 NO IN ('DXB','AUH','SHJ','AJM','RAK','UAQ','FUJ') Emirate location
city VARCHAR 100 YES NULL City
address_en VARCHAR 300 YES NULL Physical address (English)
address_ar VARCHAR 300 YES NULL Physical address (Arabic)
phone VARCHAR 20 YES NULL E.164 format Main phone number
nabidh_facility_id VARCHAR 30 YES NULL Required if emirate_code = 'DXB' NABIDH-assigned facility ID
malaffi_facility_id VARCHAR 30 YES NULL Required if emirate_code = 'AUH' Malaffi-assigned facility ID
mrn_prefix VARCHAR 10 YES NULL UNIQUE if set Facility-specific MRN prefix
timezone VARCHAR 50 NO 'Asia/Dubai' Valid IANA timezone Facility timezone
is_active BOOLEAN NO TRUE Active facility flag
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Updated timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_facilities_code ON facilities(facility_code);
CREATE INDEX idx_facilities_emirate ON facilities(emirate_code);
CREATE INDEX idx_facilities_type ON facilities(facility_type);
CREATE INDEX idx_facilities_authority ON facilities(licensing_authority);
CREATE INDEX idx_facilities_active ON facilities(is_active) WHERE is_active = TRUE;

FHIR Mapping:

  • FHIR Resource: Organization / Location
  • Key mappings:
  • facility_idOrganization.id
  • facility_codeOrganization.identifier
  • facility_name_enOrganization.name
  • facility_typeOrganization.type
  • license_numberOrganization.identifier[type=license]

departments

Purpose: Departments within facilities. Used for access scoping, order routing, and reporting.

Field Name Data Type Length Nullable Default Validation Description
department_id BIGINT NO AUTO_INCREMENT Primary key
facility_id BIGINT NO FK to facilities.facility_id Parent facility
department_code VARCHAR 20 NO UNIQUE within facility Department short code
department_name_en VARCHAR 200 NO Non-empty Department name (English)
department_name_ar VARCHAR 200 YES NULL Department name (Arabic)
department_type VARCHAR 30 NO IN ('clinical','administrative','support','ancillary') Category
parent_department_id BIGINT YES NULL FK to departments.department_id (self-ref) Parent department (for hierarchy)
cost_center_code VARCHAR 20 YES NULL Financial cost center
phone_extension VARCHAR 10 YES NULL Internal phone extension
is_active BOOLEAN NO TRUE Active flag
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Updated timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_depts_code ON departments(facility_id, department_code);
CREATE INDEX idx_depts_facility ON departments(facility_id);
CREATE INDEX idx_depts_parent ON departments(parent_department_id);
CREATE INDEX idx_depts_type ON departments(department_type);

locations

Purpose: Physical locations within departments — rooms, beds, stations, bays. Used by Scheduling for bed management and by all modules for patient location tracking.

Field Name Data Type Length Nullable Default Validation Description
location_id BIGINT NO AUTO_INCREMENT Primary key
department_id BIGINT NO FK to departments.department_id Parent department
facility_id BIGINT NO FK to facilities.facility_id Parent facility (denormalized for queries)
location_code VARCHAR 20 NO UNIQUE within facility Location short code
location_name VARCHAR 100 NO Non-empty Location display name
location_type VARCHAR 30 NO IN ('room','bed','station','bay','or_suite','exam_room','waiting_area','nursing_station','pharmacy','lab','other') Location category
floor VARCHAR 10 YES NULL Floor/level
wing VARCHAR 20 YES NULL Building wing
bed_type VARCHAR 20 YES NULL IN ('standard','icu','nicu','isolation','bariatric','pediatric') when location_type = 'bed' Bed category
is_active BOOLEAN NO TRUE Active flag
is_occupied BOOLEAN NO FALSE Current occupancy status
created_at TIMESTAMP NO CURRENT_TIMESTAMP Created timestamp
updated_at TIMESTAMP NO CURRENT_TIMESTAMP ON UPDATE Updated timestamp

Indexes:

SQL
CREATE UNIQUE INDEX idx_locations_code ON locations(facility_id, location_code);
CREATE INDEX idx_locations_dept ON locations(department_id);
CREATE INDEX idx_locations_facility ON locations(facility_id);
CREATE INDEX idx_locations_type ON locations(location_type);
CREATE INDEX idx_locations_available ON locations(location_type, is_active, is_occupied) WHERE location_type = 'bed' AND is_active = TRUE AND is_occupied = FALSE;

audit_log

Purpose: Immutable audit trail for all user actions across the HIS. Required by UAE PDPL, NESA/ADHICS, DOH, and DHA regulations. Supports break-the-glass (BTG) tracking, data access logging, and compliance reporting. This table is append-only — no UPDATE or DELETE operations permitted.

Field Name Data Type Length Nullable Default Validation Description
audit_id BIGINT NO AUTO_INCREMENT Primary key
user_id BIGINT NO FK to users.user_id Acting user
session_id VARCHAR 64 YES NULL User session identifier
action VARCHAR 30 NO FK to ref_audit_actions.action_code Action performed
resource_type VARCHAR 50 NO Resource/entity type (e.g., 'patients', 'clinical_notes', 'HL7_ADT')
resource_id BIGINT YES NULL Specific resource ID
patient_id BIGINT YES NULL FK to patients.patient_id Patient context (if applicable)
encounter_id BIGINT YES NULL FK to encounters.encounter_id Encounter context (if applicable)
module VARCHAR 30 YES NULL Module where action occurred
event_timestamp TIMESTAMP NO CURRENT_TIMESTAMP Event timestamp (UTC)
ip_address VARCHAR 45 YES NULL Client IP address (IPv4/IPv6)
user_agent VARCHAR 500 YES NULL Client user agent string
old_values JSON YES NULL Previous field values (for updates)
new_values JSON YES NULL New field values (for creates/updates)
is_btg BOOLEAN NO FALSE Break-the-glass access
btg_reason_code VARCHAR 30 YES NULL FK to ref_btg_reasons.btg_reason_code; required if is_btg = TRUE BTG reason
btg_justification TEXT YES NULL Free-text BTG justification
is_phi_access BOOLEAN NO FALSE Accessed protected health information
outcome VARCHAR 20 NO 'success' IN ('success','failure','denied','error') Action outcome
error_message TEXT YES NULL Error details if outcome != 'success'

Indexes:

SQL
CREATE INDEX idx_audit_user ON audit_log(user_id);
CREATE INDEX idx_audit_patient ON audit_log(patient_id);
CREATE INDEX idx_audit_resource ON audit_log(resource_type, resource_id);
CREATE INDEX idx_audit_timestamp ON audit_log(event_timestamp);
CREATE INDEX idx_audit_action ON audit_log(action);
CREATE INDEX idx_audit_btg ON audit_log(is_btg, event_timestamp) WHERE is_btg = TRUE;
CREATE INDEX idx_audit_module ON audit_log(module, event_timestamp);
CREATE INDEX idx_audit_outcome ON audit_log(outcome) WHERE outcome != 'success';

Partitioning:

SQL
-- Partition by month for query performance and archival
CREATE TABLE audit_log (
  ...
) PARTITION BY RANGE (EXTRACT(YEAR_MONTH FROM event_timestamp));

-- Example partitions
ALTER TABLE audit_log ADD PARTITION (
  PARTITION p202601 VALUES LESS THAN (202602),
  PARTITION p202602 VALUES LESS THAN (202603)
);

Regulatory Note: Audit log records must be retained for a minimum of 10 years per DOH/DHA requirements. Records must be immutable — implement via database triggers or application-level enforcement that prevents UPDATE/DELETE on this table. Consider write-once archival storage for partitions older than 1 year.


integration_message_log

Purpose: Tracks all inbound and outbound integration messages (HL7 v2.x, FHIR API calls, eligibility checks). Supports retry logic, dead-letter queue, and integration monitoring.

Field Name Data Type Length Nullable Default Validation Description
message_id BIGINT NO AUTO_INCREMENT Primary key
message_control_id VARCHAR 50 YES NULL HL7 MSH-10 message control ID
direction VARCHAR 10 NO IN ('outbound','inbound') Message direction
message_type VARCHAR 30 NO e.g., 'ADT_A04', 'FHIR_Patient', 'ELIGIBILITY' Message/event type
target_system VARCHAR 50 NO e.g., 'NABIDH', 'MALAFFI', 'SCHEDULING', 'BILLING' Target/source system
integration_endpoint VARCHAR 200 YES NULL Endpoint URL or connection ID
patient_id BIGINT YES NULL FK to patients.patient_id Patient context
status VARCHAR 20 NO 'queued' IN ('queued','sent','pending','acknowledged','error','failed','rejected') Message status
payload TEXT YES NULL Message payload (truncated for large messages)
response_payload TEXT YES NULL Response/ACK payload
ack_code VARCHAR 5 YES NULL IN ('AA','AE','AR') for HL7; HTTP status for FHIR Acknowledgment code
retry_count INT NO 0 >= 0 Number of retry attempts
max_retries INT NO 10 > 0 Maximum retry attempts
next_retry_at TIMESTAMP YES NULL Next scheduled retry
error_message TEXT YES NULL Error details
created_at TIMESTAMP NO CURRENT_TIMESTAMP Message creation
sent_at TIMESTAMP YES NULL When message was sent
completed_at TIMESTAMP YES NULL When final status reached

Indexes:

SQL
CREATE INDEX idx_intmsg_patient ON integration_message_log(patient_id);
CREATE INDEX idx_intmsg_status ON integration_message_log(status);
CREATE INDEX idx_intmsg_type ON integration_message_log(message_type);
CREATE INDEX idx_intmsg_target ON integration_message_log(target_system);
CREATE INDEX idx_intmsg_retry ON integration_message_log(next_retry_at) WHERE status IN ('queued','error') AND retry_count < max_retries;
CREATE INDEX idx_intmsg_deadletter ON integration_message_log(status, created_at) WHERE status IN ('failed','rejected');
CREATE INDEX idx_intmsg_control ON integration_message_log(message_control_id);

Reference Tables

The following reference/master data tables support the transactional tables above. Full definitions are in 06-master-data.md; summary provided here.

Reference Table Purpose Key Fields
ref_nationalities ISO nationalities with Arabic names nationality_code (PK), display_name_en, display_name_ar
ref_emirates_cities UAE emirates and cities emirate_code, city_code (composite PK)
ref_document_types Document category codes document_type_code (PK), category, default_retention_years
ref_note_templates Clinical note templates template_id (PK), note_type, specialty_code, content_structure_json
ref_consent_templates Consent form templates template_id (PK), consent_type_code, version, body_text
ref_consent_types Consent type definitions consent_type_code (PK), description, is_mandatory
ref_payers Insurance payer reference (local copy from policy-contract-mgmt) payer_id FK, payer_code, dha_payer_code, doh_payer_code
ref_provider_specialties Medical specialty codes specialty_code (PK), specialty_name_en, doh_code, dha_code
ref_allergens Common allergen master list allergen_id (PK), allergen_code, allergen_system, description
ref_snomed_terms SNOMED CT term subset snomed_code (PK), preferred_term, semantic_tag
ref_audit_actions Standardized audit action codes action_code (PK), action_description, module
ref_btg_reasons Break-the-glass reason codes btg_reason_code (PK), description, requires_free_text

Sample SQL DDL

SQL
-- ============================================================
-- EHR & Patient Management Module — Core Tables DDL
-- Target: PostgreSQL 15+ (syntax adaptable to MySQL 8+)
-- ============================================================

-- -------------------------------------------------------
-- 1. patients (Shared Entity — canonical definition)
-- -------------------------------------------------------
CREATE TABLE patients (
  patient_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  mrn VARCHAR(20) NOT NULL UNIQUE,
  first_name_en VARCHAR(100) NOT NULL,
  middle_name_en VARCHAR(100),
  last_name_en VARCHAR(100) NOT NULL,
  first_name_ar VARCHAR(100),
  middle_name_ar VARCHAR(100),
  last_name_ar VARCHAR(100),
  date_of_birth DATE NOT NULL,
  gender VARCHAR(10) NOT NULL,
  nationality_code VARCHAR(3),
  is_vip BOOLEAN NOT NULL DEFAULT FALSE,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  is_deceased BOOLEAN NOT NULL DEFAULT FALSE,
  deceased_datetime TIMESTAMP,
  merged_into_patient_id BIGINT,
  blood_group VARCHAR(5),
  preferred_language VARCHAR(5) DEFAULT 'en',
  photo_url VARCHAR(500),
  created_by BIGINT NOT NULL,
  updated_by BIGINT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_patient_merged FOREIGN KEY (merged_into_patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_gender CHECK (gender IN ('male', 'female', 'unknown')),
  CONSTRAINT chk_dob CHECK (date_of_birth <= CURRENT_DATE),
  CONSTRAINT chk_deceased CHECK (is_deceased = FALSE OR deceased_datetime IS NOT NULL),
  CONSTRAINT chk_blood_group CHECK (blood_group IS NULL OR blood_group IN ('A+','A-','B+','B-','AB+','AB-','O+','O-'))
);

-- -------------------------------------------------------
-- 2. patient_demographics (versioned — SCD Type 2)
-- -------------------------------------------------------
CREATE TABLE patient_demographics (
  demographic_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  is_current BOOLEAN NOT NULL DEFAULT TRUE,
  effective_from TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  effective_to TIMESTAMP,
  address_line1_en VARCHAR(200),
  address_line2_en VARCHAR(200),
  address_line1_ar VARCHAR(200),
  po_box VARCHAR(20),
  emirate_code VARCHAR(3),
  city VARCHAR(100),
  country_code VARCHAR(3) DEFAULT 'ARE',
  mobile_phone VARCHAR(20),
  home_phone VARCHAR(20),
  work_phone VARCHAR(20),
  email VARCHAR(200),
  marital_status VARCHAR(20),
  occupation VARCHAR(100),
  employer_name VARCHAR(200),
  religion VARCHAR(50),
  created_by BIGINT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_demo_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_marital CHECK (marital_status IS NULL OR marital_status IN ('single','married','divorced','widowed','separated','unknown'))
);

CREATE UNIQUE INDEX idx_patient_demo_current ON patient_demographics(patient_id) WHERE is_current = TRUE;

-- -------------------------------------------------------
-- 3. patient_identifiers
-- -------------------------------------------------------
CREATE TABLE patient_identifiers (
  identifier_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  identifier_type VARCHAR(30) NOT NULL,
  identifier_value VARCHAR(100) NOT NULL,
  identifier_system VARCHAR(200),
  is_primary BOOLEAN NOT NULL DEFAULT TRUE,
  verification_status VARCHAR(20) NOT NULL DEFAULT 'unverified',
  issue_date DATE,
  expiry_date DATE,
  issuing_authority VARCHAR(100),
  created_by BIGINT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_ident_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_ident_type CHECK (identifier_type IN ('MRN','EMIRATES_ID','PASSPORT','VISA','INSURANCE_MEMBER_ID','OLD_MRN','NATIONAL_ID','OTHER')),
  CONSTRAINT chk_ident_status CHECK (verification_status IN ('verified','unverified','expired','invalid')),
  CONSTRAINT chk_eid_format CHECK (identifier_type != 'EMIRATES_ID' OR identifier_value ~ '^784-[0-9]{4}-[0-9]{7}-[0-9]$')
);

-- -------------------------------------------------------
-- 4. facilities (Shared Entity — canonical definition)
-- -------------------------------------------------------
CREATE TABLE facilities (
  facility_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  facility_code VARCHAR(20) NOT NULL UNIQUE,
  facility_name_en VARCHAR(200) NOT NULL,
  facility_name_ar VARCHAR(200),
  facility_type VARCHAR(30) NOT NULL,
  licensing_authority VARCHAR(10) NOT NULL,
  license_number VARCHAR(50) NOT NULL,
  license_expiry_date DATE NOT NULL,
  emirate_code VARCHAR(3) NOT NULL,
  city VARCHAR(100),
  address_en VARCHAR(300),
  address_ar VARCHAR(300),
  phone VARCHAR(20),
  nabidh_facility_id VARCHAR(30),
  malaffi_facility_id VARCHAR(30),
  mrn_prefix VARCHAR(10) UNIQUE,
  timezone VARCHAR(50) NOT NULL DEFAULT 'Asia/Dubai',
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT chk_facility_type CHECK (facility_type IN ('hospital','clinic','day_surgery','pharmacy','lab','diagnostic_center','rehabilitation','home_health')),
  CONSTRAINT chk_licensing_auth CHECK (licensing_authority IN ('DOH','DHA','MOH')),
  CONSTRAINT chk_emirate CHECK (emirate_code IN ('DXB','AUH','SHJ','AJM','RAK','UAQ','FUJ'))
);

-- -------------------------------------------------------
-- 5. departments
-- -------------------------------------------------------
CREATE TABLE departments (
  department_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  facility_id BIGINT NOT NULL,
  department_code VARCHAR(20) NOT NULL,
  department_name_en VARCHAR(200) NOT NULL,
  department_name_ar VARCHAR(200),
  department_type VARCHAR(30) NOT NULL,
  parent_department_id BIGINT,
  cost_center_code VARCHAR(20),
  phone_extension VARCHAR(10),
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_dept_facility FOREIGN KEY (facility_id) REFERENCES facilities(facility_id),
  CONSTRAINT fk_dept_parent FOREIGN KEY (parent_department_id) REFERENCES departments(department_id),
  CONSTRAINT chk_dept_type CHECK (department_type IN ('clinical','administrative','support','ancillary')),
  CONSTRAINT uq_dept_code UNIQUE (facility_id, department_code)
);

-- -------------------------------------------------------
-- 6. locations
-- -------------------------------------------------------
CREATE TABLE locations (
  location_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  department_id BIGINT NOT NULL,
  facility_id BIGINT NOT NULL,
  location_code VARCHAR(20) NOT NULL,
  location_name VARCHAR(100) NOT NULL,
  location_type VARCHAR(30) NOT NULL,
  floor VARCHAR(10),
  wing VARCHAR(20),
  bed_type VARCHAR(20),
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  is_occupied BOOLEAN NOT NULL DEFAULT FALSE,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_loc_dept FOREIGN KEY (department_id) REFERENCES departments(department_id),
  CONSTRAINT fk_loc_facility FOREIGN KEY (facility_id) REFERENCES facilities(facility_id),
  CONSTRAINT chk_loc_type CHECK (location_type IN ('room','bed','station','bay','or_suite','exam_room','waiting_area','nursing_station','pharmacy','lab','other')),
  CONSTRAINT uq_loc_code UNIQUE (facility_id, location_code)
);

-- -------------------------------------------------------
-- 7. providers (Shared Entity — canonical definition)
-- -------------------------------------------------------
CREATE TABLE providers (
  provider_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  staff_number VARCHAR(20) NOT NULL UNIQUE,
  first_name_en VARCHAR(100) NOT NULL,
  middle_name_en VARCHAR(100),
  last_name_en VARCHAR(100) NOT NULL,
  first_name_ar VARCHAR(100),
  last_name_ar VARCHAR(100),
  provider_type VARCHAR(30) NOT NULL,
  specialty_code VARCHAR(20),
  subspecialty_code VARCHAR(20),
  license_number VARCHAR(50) NOT NULL,
  licensing_authority VARCHAR(10) NOT NULL,
  license_expiry_date DATE NOT NULL,
  national_id VARCHAR(20),
  email VARCHAR(200),
  mobile_phone VARCHAR(20),
  department_id BIGINT,
  facility_id BIGINT NOT NULL,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  doh_provider_id VARCHAR(30),
  dha_provider_id VARCHAR(30),
  can_prescribe BOOLEAN NOT NULL DEFAULT FALSE,
  can_prescribe_controlled BOOLEAN NOT NULL DEFAULT FALSE,
  digital_signature_cert VARCHAR(500),
  created_by BIGINT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_prov_dept FOREIGN KEY (department_id) REFERENCES departments(department_id),
  CONSTRAINT fk_prov_facility FOREIGN KEY (facility_id) REFERENCES facilities(facility_id),
  CONSTRAINT chk_prov_type CHECK (provider_type IN ('physician','nurse','pharmacist','technician','therapist','admin','other')),
  CONSTRAINT chk_prov_auth CHECK (licensing_authority IN ('DOH','DHA','MOH'))
);

-- -------------------------------------------------------
-- 8. users (Shared Entity — canonical definition)
-- -------------------------------------------------------
CREATE TABLE users (
  user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  username VARCHAR(50) NOT NULL UNIQUE,
  email VARCHAR(200) NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  provider_id BIGINT,
  patient_id BIGINT,
  user_type VARCHAR(20) NOT NULL,
  display_name VARCHAR(200) NOT NULL,
  preferred_language VARCHAR(5) DEFAULT 'en',
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  is_locked BOOLEAN NOT NULL DEFAULT FALSE,
  locked_at TIMESTAMP,
  last_login_at TIMESTAMP,
  last_login_ip VARCHAR(45),
  failed_login_count INT NOT NULL DEFAULT 0,
  password_changed_at TIMESTAMP,
  mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  mfa_secret VARCHAR(255),
  created_by BIGINT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_user_provider FOREIGN KEY (provider_id) REFERENCES providers(provider_id),
  CONSTRAINT fk_user_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_user_type CHECK (user_type IN ('clinical','administrative','portal_patient','portal_provider','system','integration'))
);

-- -------------------------------------------------------
-- 9. roles
-- -------------------------------------------------------
CREATE TABLE roles (
  role_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  role_code VARCHAR(50) NOT NULL UNIQUE,
  role_name_en VARCHAR(100) NOT NULL,
  role_name_ar VARCHAR(100),
  description TEXT,
  role_category VARCHAR(30) NOT NULL,
  is_system_role BOOLEAN NOT NULL DEFAULT FALSE,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  created_by BIGINT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT chk_role_cat CHECK (role_category IN ('clinical','administrative','portal','system','integration'))
);

-- -------------------------------------------------------
-- 10. permissions
-- -------------------------------------------------------
CREATE TABLE permissions (
  permission_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  permission_code VARCHAR(80) NOT NULL UNIQUE,
  module VARCHAR(30) NOT NULL,
  resource VARCHAR(50) NOT NULL,
  action VARCHAR(20) NOT NULL,
  description TEXT,
  is_sensitive BOOLEAN NOT NULL DEFAULT FALSE,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT chk_perm_action CHECK (action IN ('create','read','update','delete','sign','approve','export','print')),
  CONSTRAINT uq_perm UNIQUE (module, resource, action)
);

-- -------------------------------------------------------
-- 11. role_permissions (junction)
-- -------------------------------------------------------
CREATE TABLE role_permissions (
  role_id BIGINT NOT NULL,
  permission_id BIGINT NOT NULL,
  granted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  granted_by BIGINT NOT NULL,

  PRIMARY KEY (role_id, permission_id),
  CONSTRAINT fk_rp_role FOREIGN KEY (role_id) REFERENCES roles(role_id),
  CONSTRAINT fk_rp_perm FOREIGN KEY (permission_id) REFERENCES permissions(permission_id)
);

-- -------------------------------------------------------
-- 12. user_roles (junction, facility-scoped)
-- -------------------------------------------------------
CREATE TABLE user_roles (
  user_id BIGINT NOT NULL,
  role_id BIGINT NOT NULL,
  facility_id BIGINT,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
  effective_to DATE,
  assigned_by BIGINT NOT NULL,
  assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_ur_user FOREIGN KEY (user_id) REFERENCES users(user_id),
  CONSTRAINT fk_ur_role FOREIGN KEY (role_id) REFERENCES roles(role_id),
  CONSTRAINT fk_ur_facility FOREIGN KEY (facility_id) REFERENCES facilities(facility_id),
  CONSTRAINT chk_ur_dates CHECK (effective_to IS NULL OR effective_to >= effective_from)
);

-- -------------------------------------------------------
-- 13. audit_log (append-only, partitioned)
-- -------------------------------------------------------
CREATE TABLE audit_log (
  audit_id BIGINT GENERATED ALWAYS AS IDENTITY,
  user_id BIGINT NOT NULL,
  session_id VARCHAR(64),
  action VARCHAR(30) NOT NULL,
  resource_type VARCHAR(50) NOT NULL,
  resource_id BIGINT,
  patient_id BIGINT,
  encounter_id BIGINT,
  module VARCHAR(30),
  event_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  ip_address VARCHAR(45),
  user_agent VARCHAR(500),
  old_values JSONB,
  new_values JSONB,
  is_btg BOOLEAN NOT NULL DEFAULT FALSE,
  btg_reason_code VARCHAR(30),
  btg_justification TEXT,
  is_phi_access BOOLEAN NOT NULL DEFAULT FALSE,
  outcome VARCHAR(20) NOT NULL DEFAULT 'success',
  error_message TEXT,

  PRIMARY KEY (audit_id, event_timestamp),
  CONSTRAINT chk_outcome CHECK (outcome IN ('success','failure','denied','error')),
  CONSTRAINT chk_btg CHECK (is_btg = FALSE OR btg_reason_code IS NOT NULL)
) PARTITION BY RANGE (event_timestamp);

-- Create initial partitions
CREATE TABLE audit_log_2026_01 PARTITION OF audit_log
  FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE audit_log_2026_02 PARTITION OF audit_log
  FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE audit_log_2026_03 PARTITION OF audit_log
  FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- Prevent UPDATE/DELETE on audit_log
CREATE OR REPLACE FUNCTION prevent_audit_modification()
RETURNS TRIGGER AS $$
BEGIN
  RAISE EXCEPTION 'Audit log records are immutable. UPDATE and DELETE operations are not permitted.';
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_audit_no_update
  BEFORE UPDATE OR DELETE ON audit_log
  FOR EACH ROW EXECUTE FUNCTION prevent_audit_modification();

-- -------------------------------------------------------
-- 14. patient_allergies
-- -------------------------------------------------------
CREATE TABLE patient_allergies (
  allergy_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  encounter_id BIGINT,
  allergen_type VARCHAR(20) NOT NULL,
  allergen_description VARCHAR(300) NOT NULL,
  allergen_code VARCHAR(20),
  allergen_system VARCHAR(50),
  reaction_description VARCHAR(500),
  reaction_code VARCHAR(20),
  severity VARCHAR(20) NOT NULL DEFAULT 'moderate',
  criticality VARCHAR(10),
  status VARCHAR(20) NOT NULL DEFAULT 'active',
  onset_date DATE,
  verification_status VARCHAR(20) NOT NULL DEFAULT 'unconfirmed',
  entered_by BIGINT NOT NULL,
  verified_by BIGINT,
  verified_at TIMESTAMP,
  notes TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_allergy_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT fk_allergy_entered FOREIGN KEY (entered_by) REFERENCES users(user_id),
  CONSTRAINT chk_allergen_type CHECK (allergen_type IN ('medication','food','environmental','biologic','other')),
  CONSTRAINT chk_allergy_severity CHECK (severity IN ('mild','moderate','severe','life-threatening')),
  CONSTRAINT chk_allergy_status CHECK (status IN ('active','inactive','resolved','entered-in-error','nka','nkda')),
  CONSTRAINT chk_allergy_verif CHECK (verification_status IN ('unconfirmed','confirmed','refuted','entered-in-error'))
);

-- -------------------------------------------------------
-- 15. patient_problems
-- -------------------------------------------------------
CREATE TABLE patient_problems (
  problem_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  encounter_id BIGINT,
  problem_description VARCHAR(500) NOT NULL,
  icd10_code VARCHAR(10),
  icd10_description VARCHAR(300),
  snomed_code VARCHAR(20),
  snomed_description VARCHAR(300),
  problem_type VARCHAR(20) NOT NULL DEFAULT 'problem',
  status VARCHAR(20) NOT NULL DEFAULT 'active',
  severity VARCHAR(20),
  onset_date DATE,
  resolved_date DATE,
  is_chronic BOOLEAN NOT NULL DEFAULT FALSE,
  is_principal BOOLEAN NOT NULL DEFAULT FALSE,
  entered_by BIGINT NOT NULL,
  verified_by BIGINT,
  notes TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_problem_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT fk_problem_entered FOREIGN KEY (entered_by) REFERENCES users(user_id),
  CONSTRAINT chk_problem_type CHECK (problem_type IN ('problem','diagnosis','symptom','finding','complaint')),
  CONSTRAINT chk_problem_status CHECK (status IN ('active','resolved','inactive','recurrence','remission')),
  CONSTRAINT chk_resolved_date CHECK (resolved_date IS NULL OR resolved_date >= onset_date)
);

-- -------------------------------------------------------
-- 16. patient_consents
-- -------------------------------------------------------
CREATE TABLE patient_consents (
  consent_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  encounter_id BIGINT,
  consent_type_code VARCHAR(50) NOT NULL,
  consent_template_id BIGINT,
  status VARCHAR(20) NOT NULL,
  processing_legal_basis VARCHAR(30) NOT NULL,
  granted_datetime TIMESTAMP,
  withdrawn_datetime TIMESTAMP,
  expiry_datetime TIMESTAMP,
  signature_method VARCHAR(30),
  signature_data TEXT,
  document_id BIGINT,
  witnessed_by BIGINT,
  collected_by BIGINT NOT NULL,
  notes TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_consent_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_consent_status CHECK (status IN ('granted','declined','withdrawn','expired')),
  CONSTRAINT chk_consent_basis CHECK (processing_legal_basis IN ('explicit_consent','treatment_exemption','public_health','legal_obligation','vital_interest','contractual')),
  CONSTRAINT chk_granted CHECK (status != 'granted' OR granted_datetime IS NOT NULL),
  CONSTRAINT chk_withdrawn CHECK (status != 'withdrawn' OR withdrawn_datetime IS NOT NULL)
);

-- -------------------------------------------------------
-- 17. patient_documents
-- -------------------------------------------------------
CREATE TABLE patient_documents (
  document_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  encounter_id BIGINT,
  document_type_code VARCHAR(50) NOT NULL,
  document_title VARCHAR(300) NOT NULL,
  mime_type VARCHAR(100) NOT NULL,
  file_size_bytes BIGINT,
  storage_path VARCHAR(500) NOT NULL,
  storage_system VARCHAR(30) NOT NULL DEFAULT 'dms',
  status VARCHAR(20) NOT NULL DEFAULT 'active',
  uploaded_by BIGINT NOT NULL,
  upload_datetime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  retention_expiry DATE,
  checksum_sha256 VARCHAR(64),
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_doc_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT fk_doc_uploaded FOREIGN KEY (uploaded_by) REFERENCES users(user_id),
  CONSTRAINT chk_doc_status CHECK (status IN ('active','archived','deleted','pending_scan'))
);

-- -------------------------------------------------------
-- 18. clinical_notes
-- -------------------------------------------------------
CREATE TABLE clinical_notes (
  note_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  encounter_id BIGINT NOT NULL,
  note_type VARCHAR(30) NOT NULL,
  template_id BIGINT,
  note_title VARCHAR(300) NOT NULL,
  note_body TEXT NOT NULL,
  structured_data JSONB,
  status VARCHAR(20) NOT NULL DEFAULT 'draft',
  author_provider_id BIGINT NOT NULL,
  authored_datetime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  signed_by BIGINT,
  signed_datetime TIMESTAMP,
  cosigner_provider_id BIGINT,
  cosigned_datetime TIMESTAMP,
  parent_note_id BIGINT,
  specialty_code VARCHAR(20),
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_note_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT fk_note_author FOREIGN KEY (author_provider_id) REFERENCES providers(provider_id),
  CONSTRAINT fk_note_signer FOREIGN KEY (signed_by) REFERENCES providers(provider_id),
  CONSTRAINT fk_note_cosigner FOREIGN KEY (cosigner_provider_id) REFERENCES providers(provider_id),
  CONSTRAINT fk_note_parent FOREIGN KEY (parent_note_id) REFERENCES clinical_notes(note_id),
  CONSTRAINT chk_note_type CHECK (note_type IN ('H_AND_P','PROGRESS','DISCHARGE','CONSULT','NURSING_ASSESSMENT','PROCEDURE','OPERATIVE','ED','ADDENDUM')),
  CONSTRAINT chk_note_status CHECK (status IN ('draft','pending_cosign','signed','amended','entered_in_error'))
);

-- -------------------------------------------------------
-- 19. duplicate_suspects
-- -------------------------------------------------------
CREATE TABLE duplicate_suspects (
  suspect_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id_1 BIGINT NOT NULL,
  patient_id_2 BIGINT NOT NULL,
  match_score DECIMAL(5,2) NOT NULL,
  match_method VARCHAR(30) NOT NULL,
  matching_fields JSONB,
  status VARCHAR(20) NOT NULL DEFAULT 'new',
  resolution VARCHAR(20),
  primary_patient_id BIGINT,
  flagged_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  flagged_by BIGINT,
  reviewed_by BIGINT,
  reviewed_at TIMESTAMP,
  approved_by BIGINT,
  approved_at TIMESTAMP,
  notes TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_dupe_p1 FOREIGN KEY (patient_id_1) REFERENCES patients(patient_id),
  CONSTRAINT fk_dupe_p2 FOREIGN KEY (patient_id_2) REFERENCES patients(patient_id),
  CONSTRAINT chk_dupe_diff CHECK (patient_id_1 != patient_id_2),
  CONSTRAINT chk_dupe_status CHECK (status IN ('new','in_review','pending_approval','resolved','rejected','auto_resolved')),
  CONSTRAINT chk_dupe_method CHECK (match_method IN ('deterministic','probabilistic','manual','registration_flag')),
  CONSTRAINT chk_dupe_score CHECK (match_score BETWEEN 0.00 AND 100.00)
);

-- -------------------------------------------------------
-- 20. provider_credentials
-- -------------------------------------------------------
CREATE TABLE provider_credentials (
  credential_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  provider_id BIGINT NOT NULL,
  credential_type VARCHAR(30) NOT NULL,
  credential_name VARCHAR(200) NOT NULL,
  credential_number VARCHAR(100),
  issuing_authority VARCHAR(200) NOT NULL,
  issue_date DATE,
  expiry_date DATE,
  status VARCHAR(20) NOT NULL DEFAULT 'active',
  document_id BIGINT,
  verified_by BIGINT,
  verified_at TIMESTAMP,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_cred_provider FOREIGN KEY (provider_id) REFERENCES providers(provider_id),
  CONSTRAINT chk_cred_type CHECK (credential_type IN ('license','board_cert','degree','privilege','cpr_cert','malpractice_ins','other')),
  CONSTRAINT chk_cred_status CHECK (status IN ('active','expired','revoked','pending_renewal','suspended')),
  CONSTRAINT chk_cred_dates CHECK (expiry_date IS NULL OR expiry_date >= issue_date)
);

-- -------------------------------------------------------
-- 21. patient_contacts
-- -------------------------------------------------------
CREATE TABLE patient_contacts (
  contact_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  patient_id BIGINT NOT NULL,
  relationship VARCHAR(30) NOT NULL,
  contact_name_en VARCHAR(200) NOT NULL,
  contact_name_ar VARCHAR(200),
  mobile_phone VARCHAR(20) NOT NULL,
  home_phone VARCHAR(20),
  email VARCHAR(200),
  is_emergency_contact BOOLEAN NOT NULL DEFAULT TRUE,
  is_next_of_kin BOOLEAN NOT NULL DEFAULT FALSE,
  priority INT NOT NULL DEFAULT 1,
  created_by BIGINT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_contact_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_contact_rel CHECK (relationship IN ('spouse','parent','child','sibling','guardian','friend','employer','other')),
  CONSTRAINT chk_contact_priority CHECK (priority >= 1)
);

-- -------------------------------------------------------
-- 22. integration_message_log
-- -------------------------------------------------------
CREATE TABLE integration_message_log (
  message_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  message_control_id VARCHAR(50),
  direction VARCHAR(10) NOT NULL,
  message_type VARCHAR(30) NOT NULL,
  target_system VARCHAR(50) NOT NULL,
  integration_endpoint VARCHAR(200),
  patient_id BIGINT,
  status VARCHAR(20) NOT NULL DEFAULT 'queued',
  payload TEXT,
  response_payload TEXT,
  ack_code VARCHAR(5),
  retry_count INT NOT NULL DEFAULT 0,
  max_retries INT NOT NULL DEFAULT 10,
  next_retry_at TIMESTAMP,
  error_message TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  sent_at TIMESTAMP,
  completed_at TIMESTAMP,

  CONSTRAINT fk_intmsg_patient FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
  CONSTRAINT chk_intmsg_dir CHECK (direction IN ('outbound','inbound')),
  CONSTRAINT chk_intmsg_status CHECK (status IN ('queued','sent','pending','acknowledged','error','failed','rejected'))
);

Data Governance & UAE PDPL Compliance

Data Classification

Classification Examples Access Control Encryption Retention
Sensitive Health Data Clinical notes, allergies, problems, diagnoses Role-based + BTG At-rest (AES-256) + in-transit (TLS 1.2+) 10 years minimum (DOH/DHA)
Personal Identifiers Emirates ID, passport, MRN Role-based, field-level masking Field-level encryption (Emirates ID) Patient lifetime + 10 years
Administrative Facilities, departments, roles Role-based At-rest (volume level) System lifetime
Audit Audit log, BTG records Read-only (admin/compliance) At-rest, immutable 10 years minimum
Integration HL7 messages, FHIR payloads System/integration accounts In-transit (TLS/mTLS) 2 years (configurable)

Retention Periods

Data Category Minimum Retention Legal Basis Archival
Patient records (all clinical) 10 years from last encounter DOH Regulation; DHA Standards Cold storage after 5 years
Consent records Patient lifetime + 10 years UAE PDPL Art. 7 Never purge while patient active
Audit logs 10 years NESA/ADHICS; DOH/DHA Partitioned; archive monthly
Integration messages 2 years Operational Purge payload after 90 days; keep metadata 2 years
Scanned documents Same as parent record DOH/DHA Object storage lifecycle policy

Cross-Border Transfer Controls

Per UAE PDPL Art. 22, the following database fields may contain data subject to cross-border transfer restrictions:

  • patients.*, patient_demographics.*, patient_identifiers.* — all PII
  • patient_allergies.*, patient_problems.*, clinical_notes.* — sensitive health data
  • patient_consents.* — consent records themselves

Implementation: If the HIS uses cloud hosting, ensure UAE-region data centers (Azure UAE North, AWS me-south-1, or equivalent). Implement data_residency flags on facility configuration to enforce geographic restrictions.

content/clinical/ehr-patient-mgmt/03-data-specifications.md Generated 2026-02-20 22:54