Patient Portal & Mobile App Data Specifications

Patient Portal & Mobile App Data Specifications

Shared Entity References

This module references the following shared entities defined in their owning modules. It does not redefine these tables — it uses foreign-key references only.

Shared Entity Owning Module Table(s) FK Used Here
Patients ehr-patient-mgmt patients patients.patient_id
Patient Demographics ehr-patient-mgmt patient_demographics patient_demographics.patient_id
Patient Identifiers ehr-patient-mgmt patient_identifiers patient_identifiers.patient_id
Providers ehr-patient-mgmt providers, provider_credentials providers.provider_id
Users & Auth ehr-patient-mgmt users, roles, permissions users.user_id
Facilities ehr-patient-mgmt facilities, departments, locations facilities.facility_id
Encounters scheduling encounters, encounter_details encounters.encounter_id
Appointments scheduling appointments appointments.appointment_id
Patient Allergies ehr-patient-mgmt patient_allergies patient_allergies.allergy_id
Patient Problems ehr-patient-mgmt patient_problems patient_problems.problem_id
Patient Documents ehr-patient-mgmt patient_documents patient_documents.document_id
Patient Consents ehr-patient-mgmt patient_consents patient_consents.consent_id
Lab Results lis lab_results lab_results.result_id
Radiology Reports ris radiology_reports radiology_reports.report_id
Pharmacy Orders pis pharmacy_orders pharmacy_orders.order_id
Patient Invoices billing-claims patient_invoices patient_invoices.invoice_id
Payers policy-contract-mgmt payers, insurance_plans payers.payer_id

Entity Relationship Diagram

erDiagram patients ||--o{ portal_accounts : "has" users ||--o{ portal_accounts : "auth user" portal_accounts ||--o{ portal_sessions : "creates" portal_accounts ||--o{ portal_messages : "patient sender/recipient" providers ||--o{ portal_messages : "provider sender/recipient" encounters ||--o{ portal_messages : "context" portal_accounts ||--o{ portal_notifications : "receives" portal_accounts ||--o{ portal_preferences : "has" portal_accounts ||--o{ telehealth_sessions : "joins as patient" providers ||--o{ telehealth_sessions : "joins as provider" appointments ||--o{ telehealth_sessions : "for" encounters ||--o{ telehealth_sessions : "documented as" portal_accounts ||--o{ patient_submitted_forms : "submits" patients ||--o{ patient_submitted_forms : "about" portal_accounts ||--o{ portal_feedback : "submits" encounters ||--o{ portal_feedback : "about" patients ||--o{ proxy_access_grants : "grantor" patients ||--o{ proxy_access_grants : "dependent" portal_accounts ||--o{ proxy_access_grants : "proxy account" portal_accounts { bigint account_id PK bigint patient_id FK bigint user_id FK } portal_sessions { bigint session_id PK bigint account_id FK } portal_messages { bigint message_id PK bigint sender_account_id FK bigint recipient_provider_id FK bigint recipient_account_id FK bigint encounter_id FK } portal_notifications { bigint notification_id PK bigint account_id FK } portal_preferences { bigint preference_id PK bigint account_id FK } telehealth_sessions { bigint session_id PK bigint appointment_id FK bigint patient_account_id FK bigint provider_id FK bigint encounter_id FK } patient_submitted_forms { bigint form_id PK bigint account_id FK bigint patient_id FK bigint processed_by FK } portal_feedback { bigint feedback_id PK bigint account_id FK bigint encounter_id FK } proxy_access_grants { bigint grant_id PK bigint grantor_patient_id FK bigint proxy_account_id FK bigint dependent_patient_id FK }

Table Definitions

1. portal_accounts

Purpose
Stores patient-facing portal accounts for web and mobile access. Links to the core patient record and user authentication record. Supports UAE Pass linkage, bilingual preferences, and MFA configuration.

Field Specification

Field Type Nullable Default Constraint Description
account_id BIGINT NO IDENTITY PK Portal account identifier
patient_id BIGINT NO FK → patients.patient_id Linked patient
user_id BIGINT NO FK → users.user_id Auth user record
email VARCHAR(254) NO UNIQUE Login / notification email
phone VARCHAR(20) YES NULL Mobile number (+9715XXXXXXXX)
activation_status VARCHAR(20) NO 'pending' IN ('pending','active','locked','disabled') Account lifecycle state
activation_date TIMESTAMP YES NULL When first activated
last_login TIMESTAMP YES NULL Last successful login
mfa_method VARCHAR(30) YES NULL IN ('sms_otp','email_otp','auth_app','biometric') Preferred MFA
uae_pass_linked BOOLEAN NO FALSE UAE Pass SSO linked
uae_pass_sub VARCHAR(100) YES NULL UAE Pass subject identifier
language_preference VARCHAR(10) NO 'en' IN ('en','ar') UI language
time_zone VARCHAR(50) NO 'Asia/Dubai' For reminders, display
is_active BOOLEAN NO TRUE Soft delete flag
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation
created_by BIGINT YES NULL FK → users.user_id Creator (staff or system)
updated_at TIMESTAMP NO CURRENT_TIMESTAMP Last update
updated_by BIGINT YES NULL FK → users.user_id Last updater

SQL DDL

SQL
CREATE TABLE portal_accounts (
    account_id           BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    patient_id           BIGINT NOT NULL,
    user_id              BIGINT NOT NULL,
    email                VARCHAR(254) NOT NULL,
    phone                VARCHAR(20),
    activation_status    VARCHAR(20) NOT NULL DEFAULT 'pending',
    activation_date      TIMESTAMP,
    last_login           TIMESTAMP,
    mfa_method           VARCHAR(30),
    uae_pass_linked      BOOLEAN NOT NULL DEFAULT FALSE,
    uae_pass_sub         VARCHAR(100),
    language_preference  VARCHAR(10) NOT NULL DEFAULT 'en',
    time_zone            VARCHAR(50) NOT NULL DEFAULT 'Asia/Dubai',
    is_active            BOOLEAN NOT NULL DEFAULT TRUE,
    created_at           TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_by           BIGINT,
    updated_at           TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_by           BIGINT,
    CONSTRAINT uq_portal_accounts_email UNIQUE (email),
    CONSTRAINT fk_portal_accounts_patient
        FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_portal_accounts_user
        FOREIGN KEY (user_id) REFERENCES users(user_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_portal_accounts_created_by
        FOREIGN KEY (created_by) REFERENCES users(user_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT fk_portal_accounts_updated_by
        FOREIGN KEY (updated_by) REFERENCES users(user_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT chk_portal_accounts_activation_status
        CHECK (activation_status IN ('pending','active','locked','disabled')),
    CONSTRAINT chk_portal_accounts_language
        CHECK (language_preference IN ('en','ar')),
    CONSTRAINT chk_portal_accounts_mfa_method
        CHECK (mfa_method IS NULL OR mfa_method IN ('sms_otp','email_otp','auth_app','biometric'))
);

CREATE INDEX idx_portal_accounts_patient
    ON portal_accounts(patient_id);

CREATE INDEX idx_portal_accounts_user
    ON portal_accounts(user_id);

CREATE INDEX idx_portal_accounts_status
    ON portal_accounts(activation_status)
    WHERE is_active = TRUE;

COMMENT ON TABLE portal_accounts IS 'Patient portal user accounts for web/mobile access (UAE PDPL-compliant).';
COMMENT ON COLUMN portal_accounts.uae_pass_sub IS 'Opaque subject identifier from UAE Pass (OAuth2/OIDC).';

Terminology Bindings

Field Terminology Example Value
language_preference BCP-47 language tags (facility subset) en, ar
time_zone IANA Time Zone Database Asia/Dubai

FHIR Resource Mapping

Table Field FHIR Resource FHIR Path
account_id Patient (extension) Patient.extension[url='.../portal-account-id'].valueIdentifier.value
patient_id Patient Patient.id
email Patient Patient.telecom[system='email'].value
phone Patient Patient.telecom[system='phone'].value
language_preference Patient Patient.communication.language.coding.code
uae_pass_sub Patient (extension) Patient.extension[url='.../uae-pass-sub'].valueString

2. portal_sessions

Purpose
Tracks login sessions for auditing, cybersecurity (ADHICS / NABIDH), and KPI calculation (MAU).

Field Specification

Field Type Nullable Default Constraint Description
session_id BIGINT NO IDENTITY PK Session identifier
account_id BIGINT NO FK → portal_accounts.account_id Portal account
login_datetime TIMESTAMP NO CURRENT_TIMESTAMP Login time
logout_datetime TIMESTAMP YES NULL login_datetime Logout time
device_type VARCHAR(30) YES NULL IN ('web','ios','android','other') Client type
device_os VARCHAR(100) YES NULL OS/version
ip_address INET YES NULL Client IP (per TDRA/NESA)
auth_method VARCHAR(30) YES NULL IN ('password','uae_pass','biometric') Primary auth
mfa_used BOOLEAN NO FALSE Whether MFA used
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation

SQL DDL

SQL
CREATE TABLE portal_sessions (
    session_id       BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id       BIGINT NOT NULL,
    login_datetime   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    logout_datetime  TIMESTAMP,
    device_type      VARCHAR(30),
    device_os        VARCHAR(100),
    ip_address       INET,
    auth_method      VARCHAR(30),
    mfa_used         BOOLEAN NOT NULL DEFAULT FALSE,
    created_at       TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_portal_sessions_account
        FOREIGN KEY (account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT chk_portal_sessions_device_type
        CHECK (device_type IS NULL OR device_type IN ('web','ios','android','other')),
    CONSTRAINT chk_portal_sessions_auth_method
        CHECK (auth_method IS NULL OR auth_method IN ('password','uae_pass','biometric')),
    CONSTRAINT chk_portal_sessions_logout_after_login
        CHECK (logout_datetime IS NULL OR logout_datetime >= login_datetime)
);

CREATE INDEX idx_portal_sessions_account
    ON portal_sessions(account_id);

CREATE INDEX idx_portal_sessions_login_datetime
    ON portal_sessions(login_datetime);

CREATE INDEX idx_portal_sessions_recent_active
    ON portal_sessions(account_id, login_datetime)
    WHERE logout_datetime IS NULL;

Terminology Bindings

Internal enumerations only.

FHIR Resource Mapping

Audit-related; not a core clinical resource. When exposed via FHIR AuditEvent:

Table Field FHIR Resource FHIR Path
session_id AuditEvent AuditEvent.id
account_id AuditEvent AuditEvent.agent.who.reference (Patient/RelatedPerson)
login_datetime AuditEvent AuditEvent.recorded
ip_address AuditEvent AuditEvent.source.observer.extension[ip]

3. portal_messages

Purpose
Stores secure, non-emergency messaging between patients and providers/clinical teams. Messages are part of the medico-legal record and must be retained per UAE regulations.

Field Specification

Field Type Nullable Default Constraint Description
message_id BIGINT NO IDENTITY PK Message identifier
thread_id BIGINT NO Conversation/thread grouping
sender_account_id BIGINT YES NULL FK → portal_accounts.account_id Patient sender (if patient)
sender_type VARCHAR(20) NO IN ('patient','provider','system') Sender role
recipient_provider_id BIGINT YES NULL FK → providers.provider_id Provider recipient (if provider)
recipient_account_id BIGINT YES NULL FK → portal_accounts.account_id Patient recipient (if patient)
recipient_type VARCHAR(20) NO IN ('patient','provider','team') Recipient role
subject VARCHAR(255) NO Message subject
body TEXT NO Message content
attachments JSONB YES NULL Array of attachment metadata
sent_datetime TIMESTAMP NO CURRENT_TIMESTAMP When sent
read_datetime TIMESTAMP YES NULL sent_datetime When first read
is_urgent BOOLEAN NO FALSE Marked urgent (with warnings)
encounter_id BIGINT YES NULL FK → encounters.encounter_id Related encounter
created_at TIMESTAMP NO CURRENT_TIMESTAMP Record creation

Attachment JSON structure (example):

JSON
[
  {
    "document_id": 12345,
    "file_name": "bp_readings.pdf",
    "content_type": "application/pdf",
    "size_bytes": 45231
  }
]

SQL DDL

SQL
CREATE TABLE portal_messages (
    message_id            BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    thread_id             BIGINT NOT NULL,
    sender_account_id     BIGINT,
    sender_type           VARCHAR(20) NOT NULL,
    recipient_provider_id BIGINT,
    recipient_account_id  BIGINT,
    recipient_type        VARCHAR(20) NOT NULL,
    subject               VARCHAR(255) NOT NULL,
    body                  TEXT NOT NULL,
    attachments           JSONB,
    sent_datetime         TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    read_datetime         TIMESTAMP,
    is_urgent             BOOLEAN NOT NULL DEFAULT FALSE,
    encounter_id          BIGINT,
    created_at            TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_portal_messages_sender_account
        FOREIGN KEY (sender_account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT fk_portal_messages_recipient_account
        FOREIGN KEY (recipient_account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT fk_portal_messages_recipient_provider
        FOREIGN KEY (recipient_provider_id) REFERENCES providers(provider_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT fk_portal_messages_encounter
        FOREIGN KEY (encounter_id) REFERENCES encounters(encounter_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT chk_portal_messages_sender_type
        CHECK (sender_type IN ('patient','provider','system')),
    CONSTRAINT chk_portal_messages_recipient_type
        CHECK (recipient_type IN ('patient','provider','team')),
    CONSTRAINT chk_portal_messages_read_after_sent
        CHECK (read_datetime IS NULL OR read_datetime >= sent_datetime),
    CONSTRAINT chk_portal_messages_sender_recipient
        CHECK (
            -- at least one recipient
            (recipient_provider_id IS NOT NULL OR recipient_account_id IS NOT NULL)
        )
);

CREATE INDEX idx_portal_messages_thread
    ON portal_messages(thread_id, sent_datetime);

CREATE INDEX idx_portal_messages_recipient_provider_unread
    ON portal_messages(recipient_provider_id, sent_datetime)
    WHERE read_datetime IS NULL;

CREATE INDEX idx_portal_messages_recipient_account_unread
    ON portal_messages(recipient_account_id, sent_datetime)
    WHERE read_datetime IS NULL;

CREATE INDEX idx_portal_messages_encounter
    ON portal_messages(encounter_id);

Terminology Bindings

Internal enumerations only.

FHIR Resource Mapping

Maps to FHIR Communication:

Table Field FHIR Resource FHIR Path
message_id Communication Communication.id
sender_account_id Communication Communication.sender (Reference to Patient/RelatedPerson)
recipient_provider_id Communication Communication.recipient (Practitioner)
subject Communication Communication.topic.text
body Communication Communication.payload.contentString
attachments Communication Communication.payload.contentAttachment (via patient_documents)
sent_datetime Communication Communication.sent
read_datetime Communication Communication.received
encounter_id Communication Communication.encounter

4. portal_notifications

Purpose
Tracks notifications (push, SMS, email, in-app) sent to portal users for appointments, results, messages, billing, and security events. Supports audit and troubleshooting.

Field Specification

Field Type Nullable Default Constraint Description
notification_id BIGINT NO IDENTITY PK Notification identifier
account_id BIGINT NO FK → portal_accounts.account_id Recipient account
notification_type VARCHAR(50) NO e.g. appointment, result, billing, security Category
channel VARCHAR(20) NO IN ('in_app','email','sms','push') Delivery channel
subject VARCHAR(255) NO Notification title
body TEXT NO Notification body (localized)
payload JSONB YES NULL Structured metadata (e.g. appointment_id)
sent_datetime TIMESTAMP NO CURRENT_TIMESTAMP When sent
read_datetime TIMESTAMP YES NULL sent_datetime When opened (in-app)
action_url VARCHAR(500) YES NULL Deep link / URL
status VARCHAR(20) NO 'pending' IN ('pending','sent','failed','read') Delivery status
error_message VARCHAR(500) YES NULL Last error if failed

SQL DDL

SQL
CREATE TABLE portal_notifications (
    notification_id    BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id         BIGINT NOT NULL,
    notification_type  VARCHAR(50) NOT NULL,
    channel            VARCHAR(20) NOT NULL,
    subject            VARCHAR(255) NOT NULL,
    body               TEXT NOT NULL,
    payload            JSONB,
    sent_datetime      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    read_datetime      TIMESTAMP,
    action_url         VARCHAR(500),
    status             VARCHAR(20) NOT NULL DEFAULT 'pending',
    error_message      VARCHAR(500),
    CONSTRAINT fk_portal_notifications_account
        FOREIGN KEY (account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT chk_portal_notifications_channel
        CHECK (channel IN ('in_app','email','sms','push')),
    CONSTRAINT chk_portal_notifications_status
        CHECK (status IN ('pending','sent','failed','read')),
    CONSTRAINT chk_portal_notifications_read_after_sent
        CHECK (read_datetime IS NULL OR read_datetime >= sent_datetime)
);

CREATE INDEX idx_portal_notifications_account
    ON portal_notifications(account_id, sent_datetime);

CREATE INDEX idx_portal_notifications_status
    ON portal_notifications(status);

CREATE INDEX idx_portal_notifications_unread_inapp
    ON portal_notifications(account_id, sent_datetime)
    WHERE channel = 'in_app' AND read_datetime IS NULL;

Terminology Bindings

Internal enumerations only.

FHIR Resource Mapping

No direct core mapping; when exposed, can be represented as Communication or Task depending on type. Example:

Table Field FHIR Resource FHIR Path
notification_id Communication Communication.id
notification_type Communication Communication.category
sent_datetime Communication Communication.sent

5. portal_preferences

Purpose
Stores patient-specific communication and display preferences, including notification channels, language, and result release preferences (within bounds of facility policy and UAE PDPL consent).

Field Specification

Field Type Nullable Default Constraint Description
preference_id BIGINT NO IDENTITY PK Preference record
account_id BIGINT NO FK → portal_accounts.account_id Owner account
notification_email BOOLEAN NO TRUE Allow email notifications
notification_sms BOOLEAN NO FALSE Allow SMS notifications
notification_push BOOLEAN NO TRUE Allow push notifications
language VARCHAR(10) NO 'en' IN ('en','ar') Preferred language
display_theme VARCHAR(20) NO 'light' IN ('light','dark','system') UI theme
result_release_preference VARCHAR(20) YES NULL IN ('immediate','delayed') Patient preference (subject to policy)
marketing_opt_in BOOLEAN NO FALSE Non-clinical communications consent
created_at TIMESTAMP NO CURRENT_TIMESTAMP Creation time
updated_at TIMESTAMP NO CURRENT_TIMESTAMP Last update

SQL DDL

SQL
CREATE TABLE portal_preferences (
    preference_id             BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id                BIGINT NOT NULL UNIQUE,
    notification_email        BOOLEAN NOT NULL DEFAULT TRUE,
    notification_sms          BOOLEAN NOT NULL DEFAULT FALSE,
    notification_push         BOOLEAN NOT NULL DEFAULT TRUE,
    language                  VARCHAR(10) NOT NULL DEFAULT 'en',
    display_theme             VARCHAR(20) NOT NULL DEFAULT 'light',
    result_release_preference VARCHAR(20),
    marketing_opt_in          BOOLEAN NOT NULL DEFAULT FALSE,
    created_at                TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at                TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_portal_preferences_account
        FOREIGN KEY (account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT chk_portal_preferences_language
        CHECK (language IN ('en','ar')),
    CONSTRAINT chk_portal_preferences_theme
        CHECK (display_theme IN ('light','dark','system')),
    CONSTRAINT chk_portal_preferences_result_release
        CHECK (result_release_preference IS NULL OR result_release_preference IN ('immediate','delayed'))
);

CREATE INDEX idx_portal_preferences_language
    ON portal_preferences(language);

Terminology Bindings

Field Terminology Example Value
language BCP-47 en, ar

FHIR Resource Mapping

Maps to Patient.communication and extensions:

Table Field FHIR Resource FHIR Path
language Patient Patient.communication.language.coding.code
notification_email Patient (extension) Patient.extension[url='.../notify-email'].valueBoolean
notification_sms Patient (extension) Patient.extension[url='.../notify-sms'].valueBoolean

6. telehealth_sessions

Purpose
Tracks telehealth video sessions initiated from portal/mobile, including join times, platform, connection quality, and linkage to appointments and encounters. Supports billing, quality metrics, and PDPL-compliant audit.

Field Specification

Field Type Nullable Default Constraint Description
session_id BIGINT NO IDENTITY PK Telehealth session identifier
appointment_id BIGINT NO FK → appointments.appointment_id Scheduled appointment
patient_account_id BIGINT NO FK → portal_accounts.account_id Patient portal account
provider_id BIGINT NO FK → providers.provider_id Consulting provider
scheduled_datetime TIMESTAMP NO Scheduled start time
join_datetime_patient TIMESTAMP YES NULL scheduled_datetime Patient join time
join_datetime_provider TIMESTAMP YES NULL scheduled_datetime Provider join time
end_datetime TIMESTAMP YES NULL ≥ join times Session end time
duration_minutes INT YES NULL ≥ 0 Calculated duration
connection_quality VARCHAR(20) YES NULL IN ('excellent','good','fair','poor') Overall quality
platform VARCHAR(30) NO IN ('webrtc','native_ios','native_android','other') Technology used
recording_consent BOOLEAN NO FALSE Patient consent for recording
recording_stored BOOLEAN NO FALSE Whether recording stored
encounter_id BIGINT YES NULL FK → encounters.encounter_id Clinical encounter record
created_at TIMESTAMP NO CURRENT_TIMESTAMP Creation time

SQL DDL

SQL
CREATE TABLE telehealth_sessions (
    session_id             BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    appointment_id         BIGINT NOT NULL,
    patient_account_id     BIGINT NOT NULL,
    provider_id            BIGINT NOT NULL,
    scheduled_datetime     TIMESTAMP NOT NULL,
    join_datetime_patient  TIMESTAMP,
    join_datetime_provider TIMESTAMP,
    end_datetime           TIMESTAMP,
    duration_minutes       INT,
    connection_quality     VARCHAR(20),
    platform               VARCHAR(30) NOT NULL,
    recording_consent      BOOLEAN NOT NULL DEFAULT FALSE,
    recording_stored       BOOLEAN NOT NULL DEFAULT FALSE,
    encounter_id           BIGINT,
    created_at             TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_telehealth_sessions_appointment
        FOREIGN KEY (appointment_id) REFERENCES appointments(appointment_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_telehealth_sessions_patient_account
        FOREIGN KEY (patient_account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_telehealth_sessions_provider
        FOREIGN KEY (provider_id) REFERENCES providers(provider_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_telehealth_sessions_encounter
        FOREIGN KEY (encounter_id) REFERENCES encounters(encounter_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT chk_telehealth_sessions_platform
        CHECK (platform IN ('webrtc','native_ios','native_android','other')),
    CONSTRAINT chk_telehealth_sessions_quality
        CHECK (connection_quality IS NULL OR connection_quality IN ('excellent','good','fair','poor')),
    CONSTRAINT chk_telehealth_sessions_times
        CHECK (
            (join_datetime_patient IS NULL OR join_datetime_patient >= scheduled_datetime) AND
            (join_datetime_provider IS NULL OR join_datetime_provider >= scheduled_datetime) AND
            (end_datetime IS NULL OR
                (join_datetime_patient IS NULL OR end_datetime >= join_datetime_patient) AND
                (join_datetime_provider IS NULL OR end_datetime >= join_datetime_provider)
            )
        ),
    CONSTRAINT chk_telehealth_sessions_duration
        CHECK (duration_minutes IS NULL OR duration_minutes >= 0)
);

CREATE INDEX idx_telehealth_sessions_appointment
    ON telehealth_sessions(appointment_id);

CREATE INDEX idx_telehealth_sessions_provider
    ON telehealth_sessions(provider_id, scheduled_datetime);

CREATE INDEX idx_telehealth_sessions_patient
    ON telehealth_sessions(patient_account_id, scheduled_datetime);

Terminology Bindings

Internal enumerations only.

FHIR Resource Mapping

Maps primarily to Encounter (telehealth) and Appointment:

Table Field FHIR Resource FHIR Path
session_id Encounter (extension) Encounter.extension[url='.../telehealth-session-id'].valueIdentifier.value
appointment_id Appointment Appointment.id
provider_id Encounter Encounter.participant.individual (Practitioner)
scheduled_datetime Appointment Appointment.start
join_datetime_patient Encounter Encounter.actualPeriod.start
end_datetime Encounter Encounter.actualPeriod.end

7. patient_submitted_forms

Purpose
Stores forms and consents submitted by patients via portal/mobile (pre-registration, medical history, PDPL consent, clinical questionnaires). Data is JSON to support dynamic templates; key fields are propagated into core EHR modules.

Field Specification

Field Type Nullable Default Constraint Description
form_id BIGINT NO IDENTITY PK Submitted form identifier
account_id BIGINT NO FK → portal_accounts.account_id Submitting account
patient_id BIGINT NO FK → patients.patient_id Patient subject
form_type VARCHAR(100) NO e.g. pre_registration, consent_surgery Template key
form_version VARCHAR(20) YES NULL Template version
form_data_json JSONB NO Captured answers and signatures
submitted_datetime TIMESTAMP NO CURRENT_TIMESTAMP Submission time
processed BOOLEAN NO FALSE Whether integrated into EHR
processed_datetime TIMESTAMP YES NULL submitted_datetime Processing time
processed_by BIGINT YES NULL FK → users.user_id Staff/system user
target_module VARCHAR(50) NO e.g. ehr-patient-mgmt,billing-claims Downstream module
source_ip INET YES NULL Client IP
user_agent VARCHAR(255) YES NULL Browser/app agent

SQL DDL

SQL
CREATE TABLE patient_submitted_forms (
    form_id             BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id          BIGINT NOT NULL,
    patient_id          BIGINT NOT NULL,
    form_type           VARCHAR(100) NOT NULL,
    form_version        VARCHAR(20),
    form_data_json      JSONB NOT NULL,
    submitted_datetime  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    processed           BOOLEAN NOT NULL DEFAULT FALSE,
    processed_datetime  TIMESTAMP,
    processed_by        BIGINT,
    target_module       VARCHAR(50) NOT NULL,
    source_ip           INET,
    user_agent          VARCHAR(255),
    CONSTRAINT fk_patient_forms_account
        FOREIGN KEY (account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_patient_forms_patient
        FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_patient_forms_processed_by
        FOREIGN KEY (processed_by) REFERENCES users(user_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT chk_patient_forms_processed_time
        CHECK (processed_datetime IS NULL OR processed_datetime >= submitted_datetime)
);

CREATE INDEX idx_patient_forms_patient
    ON patient_submitted_forms(patient_id, submitted_datetime);

CREATE INDEX idx_patient_forms_type
    ON patient_submitted_forms(form_type, submitted_datetime);

CREATE INDEX idx_patient_forms_processed
    ON patient_submitted_forms(processed, target_module);

Terminology Bindings

Field Terminology Example Value
form_type Local form template catalog pre_registration, pdpl_consent, anesthesia_consent

FHIR Resource Mapping

Depending on form type, maps to different resources (e.g. QuestionnaireResponse, Consent):

Table Field FHIR Resource FHIR Path
form_id QuestionnaireResponse QuestionnaireResponse.id
patient_id QuestionnaireResponse QuestionnaireResponse.subject
form_type QuestionnaireResponse QuestionnaireResponse.questionnaire (canonical URL)
form_data_json QuestionnaireResponse QuestionnaireResponse.item (transformed)

For consent forms:

Table Field FHIR Resource FHIR Path
form_id Consent Consent.id
patient_id Consent Consent.patient
form_type Consent Consent.category

8. portal_feedback

Purpose
Captures patient satisfaction and experience feedback related to portal usage or specific encounters (including telehealth). Used for quality improvement and KPIs.

Field Specification

Field Type Nullable Default Constraint Description
feedback_id BIGINT NO IDENTITY PK Feedback identifier
account_id BIGINT NO FK → portal_accounts.account_id Submitting account
feedback_type VARCHAR(50) NO IN ('portal','telehealth','visit','billing') Context
encounter_id BIGINT YES NULL FK → encounters.encounter_id Related encounter (if any)
rating INT NO 1–5 Numeric rating
comments TEXT YES NULL Free-text comments
submitted_datetime TIMESTAMP NO CURRENT_TIMESTAMP Submission time
follow_up_required BOOLEAN NO FALSE Flag for service recovery
created_at TIMESTAMP NO CURRENT_TIMESTAMP Creation time

SQL DDL

SQL
CREATE TABLE portal_feedback (
    feedback_id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id          BIGINT NOT NULL,
    feedback_type       VARCHAR(50) NOT NULL,
    encounter_id        BIGINT,
    rating              INT NOT NULL,
    comments            TEXT,
    submitted_datetime  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    follow_up_required  BOOLEAN NOT NULL DEFAULT FALSE,
    created_at          TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_portal_feedback_account
        FOREIGN KEY (account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT fk_portal_feedback_encounter
        FOREIGN KEY (encounter_id) REFERENCES encounters(encounter_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT chk_portal_feedback_type
        CHECK (feedback_type IN ('portal','telehealth','visit','billing')),
    CONSTRAINT chk_portal_feedback_rating
        CHECK (rating BETWEEN 1 AND 5)
);

CREATE INDEX idx_portal_feedback_account
    ON portal_feedback(account_id, submitted_datetime);

CREATE INDEX idx_portal_feedback_type
    ON portal_feedback(feedback_type, submitted_datetime);

Terminology Bindings

Internal enumerations only.

FHIR Resource Mapping

Can be represented as QuestionnaireResponse or Observation (patient experience):

Table Field FHIR Resource FHIR Path
feedback_id QuestionnaireResponse QuestionnaireResponse.id
rating Observation Observation.valueInteger (patient satisfaction score)

9. proxy_access_grants

Purpose
Manages proxy access (e.g. parent/guardian) to dependent patient records, including relationship, access level, and expiry. Must comply with UAE guardianship rules and PDPL consent.

Field Specification

Field Type Nullable Default Constraint Description
grant_id BIGINT NO IDENTITY PK Proxy grant identifier
grantor_patient_id BIGINT NO FK → patients.patient_id Patient granting access (if capable) or legal owner
proxy_account_id BIGINT NO FK → portal_accounts.account_id Proxy portal account
dependent_patient_id BIGINT NO FK → patients.patient_id Dependent patient
relationship VARCHAR(50) NO e.g. parent, guardian, spouse Relationship type
access_level VARCHAR(20) NO 'full' IN ('full','limited','appointments_only') Scope of access
granted_datetime TIMESTAMP NO CURRENT_TIMESTAMP When granted
expiry_datetime TIMESTAMP YES NULL granted_datetime When access ends
is_active BOOLEAN NO TRUE Active flag
created_by BIGINT YES NULL FK → users.user_id Staff who configured (if not self-service)
created_at TIMESTAMP NO CURRENT_TIMESTAMP Creation time
revoked_datetime TIMESTAMP YES NULL granted_datetime When revoked
revoked_reason VARCHAR(255) YES NULL Reason for revocation

SQL DDL

SQL
CREATE TABLE proxy_access_grants (
    grant_id             BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    grantor_patient_id   BIGINT NOT NULL,
    proxy_account_id     BIGINT NOT NULL,
    dependent_patient_id BIGINT NOT NULL,
    relationship         VARCHAR(50) NOT NULL,
    access_level         VARCHAR(20) NOT NULL DEFAULT 'full',
    granted_datetime     TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    expiry_datetime      TIMESTAMP,
    is_active            BOOLEAN NOT NULL DEFAULT TRUE,
    created_by           BIGINT,
    created_at           TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    revoked_datetime     TIMESTAMP,
    revoked_reason       VARCHAR(255),
    CONSTRAINT fk_proxy_grants_grantor_patient
        FOREIGN KEY (grantor_patient_id) REFERENCES patients(patient_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_proxy_grants_proxy_account
        FOREIGN KEY (proxy_account_id) REFERENCES portal_accounts(account_id)
        ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT fk_proxy_grants_dependent_patient
        FOREIGN KEY (dependent_patient_id) REFERENCES patients(patient_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    CONSTRAINT fk_proxy_grants_created_by
        FOREIGN KEY (created_by) REFERENCES users(user_id)
        ON UPDATE CASCADE ON DELETE SET NULL,
    CONSTRAINT chk_proxy_grants_access_level
        CHECK (access_level IN ('full','limited','appointments_only')),
    CONSTRAINT chk_proxy_grants_expiry
        CHECK (expiry_datetime IS NULL OR expiry_datetime >= granted_datetime),
    CONSTRAINT chk_proxy_grants_revoked_time
        CHECK (revoked_datetime IS NULL OR revoked_datetime >= granted_datetime),
    CONSTRAINT uq_proxy_grants_unique
        UNIQUE (proxy_account_id, dependent_patient_id, access_level)
);

CREATE INDEX idx_proxy_grants_proxy
    ON proxy_access_grants(proxy_account_id, is_active);

CREATE INDEX idx_proxy_grants_dependent
    ON proxy_access_grants(dependent_patient_id, is_active);

Terminology Bindings

Field Terminology Example Value
relationship SNOMED CT / local parent, guardian, spouse

FHIR Resource Mapping

Maps to FHIR RelatedPerson and Consent:

Table Field FHIR Resource FHIR Path
proxy_account_id RelatedPerson RelatedPerson.id (linked to Patient)
dependent_patient_id RelatedPerson RelatedPerson.patient
relationship RelatedPerson RelatedPerson.relationship
access_level Consent Consent.provision.type / Consent.provision.class
expiry_datetime Consent Consent.period.end

Data Volume Estimates

Estimates are for a medium-to-large UAE hospital group (e.g. 300–500 beds, 250k active patients).

Table Initial Rows (Go-Live) Annual Growth Notes
portal_accounts 50,000 +30,000/year Adoption ramp; target ≥70% of active patients by year 2
portal_sessions 0 ~3,000,000/year Assume avg 5 logins/year per active account
portal_messages 0 ~400,000/year 1–2 threads per patient/year, multi-message threads
portal_notifications 0 ~5,000,000/year High volume due to reminders, results, billing
portal_preferences 50,000 +30,000/year 1:1 with portal_accounts
telehealth_sessions 0 ~25,000/year Assuming 10–15% of OP visits telehealth
patient_submitted_forms 0 ~300,000/year Pre-registration + consents + questionnaires
portal_feedback 0 ~80,000/year Post-visit and portal NPS prompts
proxy_access_grants 5,000 +3,000/year Paediatrics and dependent adults

Storage planning should consider 7–10 years of online data for high-volume tables (portal_sessions, portal_notifications), with older data archived to cheaper storage while maintaining PDPL-compliant access controls.


Data Retention Policy (UAE Context)

Retention must align with UAE federal and emirate-level health regulations, medical liability laws, and UAE PDPL (Federal Decree-Law No. 45/2021). Where specific durations are not prescribed, align with facility medical record retention (often 10+ years for adults, longer for paediatrics) and DOH/DHA guidance.

Table Recommended Retention Rationale / Notes
portal_accounts Retain for lifetime of patient record + 10 years after deactivation Part of identity and access history; supports medico-legal traceability and PDPL accountability.
portal_sessions Detailed logs online for 2 years; archive for minimum 10 years Security/audit logs per ADHICS/NESA; older data may be archived but must be retrievable for investigations.
portal_messages Same as core medical record (≥10 years after last encounter; longer for minors) Clinical communication is part of the legal health record. For paediatrics, retain at least until patient age 25 or per MOH/DOH/DHA policy.
portal_notifications 2 years online; archive or purge after 5 years Notifications are delivery artifacts; underlying clinical/billing data retained elsewhere. Keep enough for dispute resolution.
portal_preferences For lifetime of portal account + 2 years Non-clinical; retain while account exists to evidence PDPL consent choices.
telehealth_sessions Metadata: same as encounters (≥10 years); recordings (if any) per facility policy (often 5–10 years) Telehealth is equivalent to a visit; metadata must align with encounter retention. Recording retention must be clearly disclosed in PDPL consent.
patient_submitted_forms Same as associated clinical record (≥10 years; longer for minors) Forms and consents are part of the medical record; retention aligned with patient_consents and documentation policies.
portal_feedback 5 years Quality improvement and KPI analytics; not typically part of legal record unless escalated. Anonymisation/pseudonymisation recommended for long-term analytics.
proxy_access_grants For duration of proxy relationship + 10 years Evidence of who had access to which records and when; important for PDPL and medico-legal defence.

All retention and archival processes must:

  • Support data subject rights under UAE PDPL (access, rectification, restriction, objection, erasure where applicable).
  • Ensure secure deletion/archival with appropriate encryption and access controls (per ADHICS, NABIDH, Malaffi, TDRA/NESA).
  • Be documented in the facility’s Record of Processing Activities (RoPA) and data retention schedule.
content/portals/patient-portal/03-data-specifications.md Generated 2026-02-20 22:54