Changes between Initial Version and Version 1 of AdvancedDatabaseDevelopment


Ignore:
Timestamp:
08/30/26 00:26:27 (12 days ago)
Author:
236021
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedDatabaseDevelopment

    v1 v1  
     1= Advanced Database Development =
     2
     3
     4----
     5
     6== Allergy–prescription safety enforcement ==
     7
     8A patient should never be prescribed a medication that conflicts with one of their documented allergies. This requirement blocks the unsafe insert outright, at the database level before it can happen: a new **prescription_medical_records** row is rejected if the prescription is linked, via **allergy_prescription_restrictions** → **prescription_restriction** to any allergy already on that patient's medical record.
     9
     10=== Custom domains ===
     11
     12{{{
     13CREATE DOMAIN non_negative_currency AS DECIMAL(12,2)
     14    CHECK (VALUE >= 0);
     15}}}
     16
     17=== Triggers ===
     18
     19{{{
     20CREATE TABLE IF NOT EXISTS prescription_allergy_conflicts_log (
     21    log_id BIGSERIAL PRIMARY KEY,
     22    record_id BIGINT NOT NULL REFERENCES medical_records(record_id),
     23    prescription_id BIGINT NOT NULL REFERENCES prescriptions(prescription_id),
     24    allergy_id BIGINT NOT NULL REFERENCES allergies(allergy_id),
     25    conflict_type TEXT CHECK (conflict_type IN ('ACTIVE', 'RESOLVED')),
     26    detected_date TIMESTAMP DEFAULT NOW(),
     27    resolution_notes TEXT,
     28    resolved_date TIMESTAMP
     29);
     30
     31CREATE OR REPLACE FUNCTION t1_prescription_allergy_check()
     32RETURNS TRIGGER
     33LANGUAGE plpgsql
     34AS $$
     35DECLARE
     36    v_patient_id BIGINT;
     37    v_conflict_allergy_id BIGINT;
     38    v_allergy_name TEXT;
     39BEGIN
     40    SELECT patient_id INTO v_patient_id
     41    FROM medical_records
     42    WHERE record_id = NEW.record_id;
     43
     44    IF v_patient_id IS NULL THEN
     45        RAISE EXCEPTION 'Medical record % not found', NEW.record_id;
     46    END IF;
     47
     48    SELECT apr.allergy_id, a.name
     49    INTO v_conflict_allergy_id, v_allergy_name
     50    FROM prescription_restriction pr_rest
     51    JOIN allergy_prescription_restrictions apr ON apr.restriction_id = pr_rest.restriction_id
     52    JOIN allergies a ON apr.allergy_id = a.allergy_id
     53    JOIN medical_record_allergies mra ON a.allergy_id = mra.allergy_id
     54    WHERE pr_rest.prescription_id = NEW.prescription_id
     55      AND mra.record_id = NEW.record_id
     56    LIMIT 1;
     57
     58    IF v_conflict_allergy_id IS NOT NULL THEN
     59        RAISE EXCEPTION 'PRESCRIPTION_ALLERGY_CONFLICT: Prescription % conflicts with allergy % (%) in patient''s record %',
     60            NEW.prescription_id, v_conflict_allergy_id, v_allergy_name, NEW.record_id;
     61    END IF;
     62
     63    RETURN NEW;
     64END;
     65$$;
     66
     67DROP TRIGGER IF EXISTS trg_prescription_allergy_check ON prescription_medical_records;
     68CREATE TRIGGER trg_prescription_allergy_check
     69    BEFORE INSERT
     70    ON prescription_medical_records
     71    FOR EACH ROW
     72    EXECUTE FUNCTION t1_prescription_allergy_check();
     73}}}
     74
     75=== Views ===
     76
     77{{{
     78CREATE OR REPLACE VIEW v_prescription_allergy_conflicts AS
     79SELECT
     80    mr.record_id,
     81    p.patient_id,
     82    p.first_name,
     83    p.last_name,
     84    pmr.prescription_id,
     85    pr.medication_name,
     86    a.allergy_id,
     87    a.name AS allergy_name,
     88    a.allergy_severity,
     89    apr.restriction_id,
     90    pr_rest.description AS restriction_description,
     91    CASE
     92        WHEN mra.record_id IS NOT NULL THEN 'ACTIVE_CONFLICT'
     93        ELSE 'ARCHIVED'
     94    END AS conflict_status
     95FROM prescription_medical_records pmr
     96JOIN medical_records mr ON pmr.record_id = mr.record_id
     97JOIN patients p ON mr.patient_id = p.patient_id
     98JOIN prescriptions pr ON pmr.prescription_id = pr.prescription_id
     99JOIN prescription_restriction pr_rest ON pr.prescription_id = pr_rest.prescription_id
     100JOIN allergy_prescription_restrictions apr ON pr_rest.restriction_id = apr.restriction_id
     101JOIN allergies a ON apr.allergy_id = a.allergy_id
     102LEFT JOIN medical_record_allergies mra ON mr.record_id = mra.record_id
     103    AND a.allergy_id = mra.allergy_id
     104WHERE a.allergy_severity IN ('HIGH', 'CRITICAL')
     105ORDER BY p.patient_id, a.allergy_severity DESC;
     106}}}
     107
     108----
     109
     110== Appointment scheduling integrity ==
     111
     112Appointments must obey real scheduling constraints so no scheduling in the past, no doublebooking the same doctor or patient in an overlapping time, no marking an appointment `COMPLETED` before its scheduled time, and valid status transitions only (`SCHEDULED → IN_PROGRESS/COMPLETED/CANCELLED`, `IN_PROGRESS → COMPLETED/CANCELLED`, nothing further out of `COMPLETED`/`CANCELLED`/`NO_SHOW`). A background job autotransitions appointments that remain `SCHEDULED` if they are 45+ minutes past their time to `NO_SHOW`.
     113
     114=== Schema change  ===
     115
     116{{{
     117
     118ALTER TABLE appointments DROP CONSTRAINT appointments_status_chk;
     119ALTER TABLE appointments ADD CONSTRAINT appointments_status_chk
     120    CHECK (status IN ('SCHEDULED','COMPLETED','CANCELLED','IN_PROGRESS','NO_SHOW'));
     121}}}
     122
     123=== Triggers ===
     124
     125{{{
     126CREATE OR REPLACE FUNCTION is_valid_appointment_transition(p_old TEXT, p_new TEXT)
     127RETURNS BOOLEAN
     128LANGUAGE sql
     129IMMUTABLE
     130AS $$
     131SELECT CASE
     132           WHEN p_old = p_new THEN TRUE
     133           WHEN p_old = 'SCHEDULED'    AND p_new IN ('IN_PROGRESS', 'COMPLETED', 'CANCELLED') THEN TRUE
     134           WHEN p_old = 'IN_PROGRESS'  AND p_new IN ('COMPLETED', 'CANCELLED') THEN TRUE
     135           ELSE FALSE
     136           END;
     137$$;
     138
     139CREATE OR REPLACE FUNCTION trigger_appointments_enforce()
     140RETURNS TRIGGER
     141LANGUAGE plpgsql
     142AS $$
     143DECLARE
     144    v_combined_datetime TIMESTAMP;
     145BEGIN
     146    v_combined_datetime := NEW.appointment_date::TIMESTAMP + NEW.appointment_time;
     147
     148    IF TG_OP = 'INSERT' AND v_combined_datetime < NOW() THEN
     149        RAISE EXCEPTION 'Cannot schedule appointment in the past (appointment_date=%, appointment_time=%)',
     150            NEW.appointment_date, NEW.appointment_time;
     151    END IF;
     152
     153    IF TG_OP = 'UPDATE' THEN
     154        IF NOT is_valid_appointment_transition(OLD.status, NEW.status) THEN
     155            RAISE EXCEPTION 'Appointment status cannot transition from % to %',
     156                OLD.status, NEW.status;
     157        END IF;
     158
     159        IF NEW.status = 'COMPLETED' AND v_combined_datetime > NOW() THEN
     160            RAISE EXCEPTION 'Cannot mark appointment COMPLETED before its scheduled time (scheduled for %)',
     161                v_combined_datetime;
     162        END IF;
     163    END IF;
     164
     165    RETURN NEW;
     166END;
     167$$;
     168
     169DROP TRIGGER IF EXISTS trigger_appointments_enforce ON appointments;
     170CREATE TRIGGER trigger_appointments_enforce
     171    BEFORE INSERT OR UPDATE
     172    ON appointments
     173    FOR EACH ROW
     174    EXECUTE FUNCTION trigger_appointments_enforce();
     175
     176CREATE OR REPLACE FUNCTION t1_appointments_no_overlap()
     177RETURNS TRIGGER
     178LANGUAGE plpgsql
     179AS $$
     180DECLARE
     181    v_new_start TIMESTAMP;
     182    v_new_end TIMESTAMP;
     183BEGIN
     184    IF NEW.status NOT IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED') THEN
     185        RETURN NEW;
     186    END IF;
     187
     188    v_new_start := NEW.appointment_date::TIMESTAMP + NEW.appointment_time;
     189    v_new_end := v_new_start + INTERVAL '30 minutes';
     190
     191    IF EXISTS (
     192        SELECT 1 FROM appointments a
     193        WHERE a.doctor_id = NEW.doctor_id
     194          AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED')
     195          AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end
     196          AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes')
     197          AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
     198    ) THEN
     199        RAISE EXCEPTION 'Doctor % has overlapping appointment at %', NEW.doctor_id, NEW.appointment_date;
     200    END IF;
     201
     202    IF EXISTS (
     203        SELECT 1 FROM appointments a
     204        WHERE a.patient_id = NEW.patient_id
     205          AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED')
     206          AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end
     207          AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes')
     208          AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
     209    ) THEN
     210        RAISE EXCEPTION 'Patient % has overlapping appointment at %', NEW.patient_id, NEW.appointment_date;
     211    END IF;
     212
     213    RETURN NEW;
     214END;
     215$$;
     216
     217DROP TRIGGER IF EXISTS trigger_appointments_no_overlap ON appointments;
     218CREATE TRIGGER trigger_appointments_no_overlap
     219    BEFORE INSERT OR UPDATE
     220    ON appointments
     221    FOR EACH ROW
     222    EXECUTE FUNCTION t1_appointments_no_overlap();
     223}}}
     224
     225=== Stored procedures/functions ===
     226
     227{{{
     228CREATE OR REPLACE PROCEDURE job_mark_no_show()
     229LANGUAGE plpgsql
     230AS $$
     231BEGIN
     232    UPDATE appointments
     233    SET status = 'NO_SHOW'
     234    WHERE status = 'SCHEDULED'
     235      AND (appointment_date::TIMESTAMP + appointment_time) < (NOW() - INTERVAL '45 minutes');
     236END;
     237$$;
     238}}}
     239
     240=== Views ===
     241
     242{{{
     243CREATE OR REPLACE VIEW v_overdue_appointments AS
     244SELECT
     245    a.appointment_id, a.patient_id, p.first_name, p.last_name,
     246    a.doctor_id, d.first_name AS doctor_first_name, d.last_name AS doctor_last_name,
     247    a.appointment_date, a.appointment_time, a.status,
     248    NOW() - (a.appointment_date::TIMESTAMP + a.appointment_time) AS time_overdue
     249FROM appointments a
     250    JOIN patients p ON a.patient_id = p.patient_id
     251    JOIN doctors d ON a.doctor_id = d.doctor_id
     252WHERE a.status = 'SCHEDULED'
     253  AND (a.appointment_date::TIMESTAMP + a.appointment_time) < (NOW() - INTERVAL '45 minutes')
     254ORDER BY time_overdue DESC;
     255}}}
     256
     257----
     258
     259== Medical record consistency ==
     260
     261**Diagnoses, procedures, and referrals** can be created independently but must stay consistent with the patient they actually serve as a mismatch like a diagnosis for one patient attached to a different patient's record, a doctor referring a patient to themselves, is both an **integrity problem** and a clinical **safety risk**.
     262
     263=== Triggers ===
     264
     265{{{
     266CREATE OR REPLACE FUNCTION t1_diagnosis_record_consistency()
     267RETURNS TRIGGER
     268LANGUAGE plpgsql
     269AS $$
     270DECLARE
     271    v_record_patient BIGINT;
     272    v_diagnosis_patient BIGINT;
     273BEGIN
     274    SELECT patient_id INTO v_record_patient FROM medical_records WHERE record_id = NEW.record_id;
     275    SELECT patient_id INTO v_diagnosis_patient FROM diagnosis WHERE diagnosis_id = NEW.diagnosis_id;
     276
     277    IF v_record_patient IS NULL THEN
     278        RAISE EXCEPTION 'Medical record % not found', NEW.record_id;
     279    END IF;
     280
     281    IF v_diagnosis_patient IS NULL THEN
     282        RAISE EXCEPTION 'Diagnosis % not found', NEW.diagnosis_id;
     283    END IF;
     284
     285    IF v_record_patient <> v_diagnosis_patient THEN
     286        RAISE EXCEPTION 'Diagnosis % belongs to patient %, but medical record % belongs to patient %',
     287            NEW.diagnosis_id, v_diagnosis_patient, NEW.record_id, v_record_patient;
     288    END IF;
     289
     290    RETURN NEW;
     291END;
     292$$;
     293
     294DROP TRIGGER IF EXISTS trg_diagnosis_record_consistency ON diagnosis_medical_records;
     295CREATE TRIGGER trg_diagnosis_record_consistency
     296    BEFORE INSERT ON diagnosis_medical_records
     297    FOR EACH ROW EXECUTE FUNCTION t1_diagnosis_record_consistency();
     298
     299CREATE OR REPLACE FUNCTION t2_procedure_diagnosis_consistency()
     300RETURNS TRIGGER
     301LANGUAGE plpgsql
     302AS $$
     303DECLARE
     304    v_diagnosis_patient BIGINT;
     305BEGIN
     306    IF NEW.diagnosis_id IS NOT NULL THEN
     307        SELECT patient_id INTO v_diagnosis_patient FROM diagnosis WHERE diagnosis_id = NEW.diagnosis_id;
     308
     309        IF v_diagnosis_patient IS NULL THEN
     310            RAISE EXCEPTION 'Diagnosis % not found', NEW.diagnosis_id;
     311        END IF;
     312
     313        IF NEW.patient_id <> v_diagnosis_patient THEN
     314            RAISE EXCEPTION 'Procedure belongs to patient %, but diagnosis % belongs to patient %',
     315                NEW.patient_id, NEW.diagnosis_id, v_diagnosis_patient;
     316        END IF;
     317    END IF;
     318
     319    RETURN NEW;
     320END;
     321$$;
     322
     323DROP TRIGGER IF EXISTS trg_procedure_diagnosis_consistency ON performed_procedures;
     324CREATE TRIGGER trg_procedure_diagnosis_consistency
     325    BEFORE INSERT OR UPDATE ON performed_procedures
     326    FOR EACH ROW EXECUTE FUNCTION t2_procedure_diagnosis_consistency();
     327
     328CREATE OR REPLACE FUNCTION t3_referral_consistency()
     329RETURNS TRIGGER
     330LANGUAGE plpgsql
     331AS $$
     332BEGIN
     333    IF NOT EXISTS (SELECT 1 FROM medical_records WHERE record_id = NEW.record_id) THEN
     334        RAISE EXCEPTION 'Medical record % not found', NEW.record_id;
     335    END IF;
     336
     337    IF NEW.from_doctor_id = NEW.to_doctor_id THEN
     338        RAISE EXCEPTION 'Doctor % cannot refer to themselves', NEW.from_doctor_id;
     339    END IF;
     340
     341    RETURN NEW;
     342END;
     343$$;
     344
     345DROP TRIGGER IF EXISTS trg_referral_consistency ON referrals;
     346CREATE TRIGGER trg_referral_consistency
     347    BEFORE INSERT ON referrals
     348    FOR EACH ROW EXECUTE FUNCTION t3_referral_consistency();
     349}}}
     350
     351=== Views ===
     352
     353{{{
     354CREATE OR REPLACE VIEW v_medical_record_overview AS
     355SELECT
     356    mr.record_id,
     357    p.patient_id, p.first_name, p.last_name, p.embg,
     358    COUNT(DISTINCT dmr.diagnosis_id) AS diagnosis_count,
     359    COUNT(DISTINCT CASE WHEN d.patient_id IS NOT NULL AND d.patient_id <> p.patient_id THEN dmr.diagnosis_id END) AS diagnosis_mismatches,
     360    COUNT(DISTINCT mrp.procedure_id) AS procedures_count,
     361    COUNT(DISTINCT mrl.result_id) AS lab_results_count,
     362    COUNT(DISTINCT ref.referral_id) AS referrals_count,
     363    COUNT(DISTINCT mra.allergy_id) AS allergies_count,
     364    COUNT(DISTINCT CASE WHEN ref.from_doctor_id = ref.to_doctor_id THEN ref.referral_id END) AS self_referrals_detected
     365FROM medical_records mr
     366    JOIN patients p ON mr.patient_id = p.patient_id
     367    LEFT JOIN diagnosis_medical_records dmr ON dmr.record_id = mr.record_id
     368    LEFT JOIN diagnosis d ON d.diagnosis_id = dmr.diagnosis_id
     369    LEFT JOIN medical_record_procedures mrp ON mrp.record_id = mr.record_id
     370    LEFT JOIN medical_record_lab_results mrl ON mrl.record_id = mr.record_id
     371    LEFT JOIN referrals ref ON mr.record_id = ref.record_id
     372    LEFT JOIN medical_record_allergies mra ON mr.record_id = mra.record_id
     373GROUP BY mr.record_id, p.patient_id, p.first_name, p.last_name, p.embg;
     374}}}
     375
     376----
     377
     378== Billing integrity ==
     379
     380Billing records must stay in sync with their line items and payment status must follow a valid state machine. **billing.total_cost** is automatically recalculated whenever a procedure or lab test is added to or removed from a bill, rather than relying on the application to keep it in sync manually. **payment_status** may only move `PENDING → PAID` or `PENDING → CANCELLED` but never backwards once finalized and every status change is written to an audit log.  Since **billing** originally had no "issued" date , a **created_at** column was added so outstanding `PENDING` bills can be tracked for follow-up.
     381
     382=== Schema change ===
     383
     384{{{
     385
     386ALTER TABLE billing ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT NOW();
     387
     388CREATE TABLE IF NOT EXISTS billing_audit_log (
     389    audit_id BIGSERIAL PRIMARY KEY,
     390    bill_id BIGINT NOT NULL REFERENCES billing(bill_id),
     391    old_amount DECIMAL(12,2),
     392    new_amount DECIMAL(12,2),
     393    old_status TEXT,
     394    new_status TEXT,
     395    change_type TEXT CHECK (change_type IN ('INSERT', 'UPDATE', 'LINE_ITEM_ADD', 'LINE_ITEM_REMOVE')),
     396    changed_at TIMESTAMP DEFAULT NOW()
     397);
     398}}}
     399
     400=== Stored procedures/functions ===
     401
     402{{{
     403
     404
     405CREATE OR REPLACE FUNCTION recalculate_billing_total(p_bill_id BIGINT, p_change_type TEXT)
     406RETURNS VOID
     407LANGUAGE plpgsql
     408AS $$
     409DECLARE
     410    v_procedure_total DECIMAL;
     411    v_lab_total DECIMAL;
     412    v_new_total DECIMAL;
     413BEGIN
     414    SELECT COALESCE(SUM(p.cost), 0) INTO v_procedure_total
     415    FROM billing_procedures bp
     416    JOIN procedures p ON p.procedure_id = bp.procedure_id
     417    WHERE bp.bill_id = p_bill_id;
     418
     419    SELECT COALESCE(SUM(lt.cost), 0) INTO v_lab_total
     420    FROM billing_lab_tests blt
     421    JOIN lab_tests lt ON lt.test_id = blt.test_id
     422    WHERE blt.bill_id = p_bill_id;
     423
     424    v_new_total := v_procedure_total + v_lab_total;
     425
     426    UPDATE billing SET total_cost = v_new_total WHERE bill_id = p_bill_id;
     427
     428    INSERT INTO billing_audit_log (bill_id, new_amount, change_type)
     429    VALUES (p_bill_id, v_new_total, p_change_type);
     430END;
     431$$;
     432
     433CREATE OR REPLACE FUNCTION is_valid_billing_transition(p_old TEXT, p_new TEXT)
     434RETURNS BOOLEAN
     435LANGUAGE sql
     436IMMUTABLE
     437AS $$
     438SELECT CASE
     439           WHEN p_old = p_new THEN TRUE
     440           WHEN p_old = 'PENDING' AND p_new IN ('PAID', 'CANCELLED') THEN TRUE
     441           ELSE FALSE
     442           END;
     443$$;
     444
     445CREATE OR REPLACE PROCEDURE job_billing_alerts()
     446LANGUAGE plpgsql
     447AS $$
     448DECLARE
     449    v_overdue_count INT;
     450BEGIN
     451    SELECT COUNT(*) INTO v_overdue_count FROM v_overdue_billings WHERE days_outstanding > 30;
     452    RAISE NOTICE 'Found % overdue billing records requiring follow-up', v_overdue_count;
     453END;
     454$$;
     455}}}
     456
     457=== Triggers ===
     458
     459{{{
     460-- one shared trigger function, reused for both billing_procedures and billing_lab_tests
     461-- (both tables have a bill_id column, so NEW.bill_id / OLD.bill_id works either way)
     462CREATE OR REPLACE FUNCTION t1_billing_line_item_changed()
     463RETURNS TRIGGER
     464LANGUAGE plpgsql
     465AS $$
     466BEGIN
     467    IF TG_OP = 'DELETE' THEN
     468        PERFORM recalculate_billing_total(OLD.bill_id, 'LINE_ITEM_REMOVE');
     469        RETURN OLD;
     470    ELSE
     471        PERFORM recalculate_billing_total(NEW.bill_id, 'LINE_ITEM_ADD');
     472        RETURN NEW;
     473    END IF;
     474END;
     475$$;
     476
     477DROP TRIGGER IF EXISTS trg_billing_procedures_update_total ON billing_procedures;
     478CREATE TRIGGER trg_billing_procedures_update_total
     479    AFTER INSERT OR DELETE ON billing_procedures
     480    FOR EACH ROW
     481    EXECUTE FUNCTION t1_billing_line_item_changed();
     482
     483DROP TRIGGER IF EXISTS trg_billing_lab_tests_update_total ON billing_lab_tests;
     484CREATE TRIGGER trg_billing_lab_tests_update_total
     485    AFTER INSERT OR DELETE ON billing_lab_tests
     486    FOR EACH ROW
     487    EXECUTE FUNCTION t1_billing_line_item_changed();
     488
     489CREATE OR REPLACE FUNCTION t2_billing_status_transition()
     490RETURNS TRIGGER
     491LANGUAGE plpgsql
     492AS $$
     493BEGIN
     494    IF NOT is_valid_billing_transition(OLD.payment_status, NEW.payment_status) THEN
     495        RAISE EXCEPTION 'Cannot transition billing status from % to %',
     496            OLD.payment_status, NEW.payment_status;
     497    END IF;
     498
     499    IF OLD.payment_status <> NEW.payment_status THEN
     500        INSERT INTO billing_audit_log (bill_id, old_status, new_status, change_type)
     501        VALUES (NEW.bill_id, OLD.payment_status, NEW.payment_status, 'UPDATE');
     502    END IF;
     503
     504    RETURN NEW;
     505END;
     506$$;
     507
     508DROP TRIGGER IF EXISTS trg_billing_status_transition ON billing;
     509CREATE TRIGGER trg_billing_status_transition
     510    BEFORE UPDATE ON billing
     511    FOR EACH ROW
     512    EXECUTE FUNCTION t2_billing_status_transition();
     513}}}
     514
     515=== Views ===
     516
     517{{{
     518CREATE OR REPLACE VIEW v_overdue_billings AS
     519SELECT
     520    b.bill_id,
     521    p.patient_id, p.first_name, p.last_name,
     522    b.total_cost, b.payment_status, b.created_at,
     523    CURRENT_DATE - b.created_at::DATE AS days_outstanding,
     524    CASE
     525        WHEN CURRENT_DATE - b.created_at::DATE > 60 THEN 'CRITICAL'
     526        WHEN CURRENT_DATE - b.created_at::DATE > 30 THEN 'OVERDUE'
     527        ELSE 'PENDING'
     528    END AS urgency
     529FROM billing b
     530JOIN medical_records mr ON b.record_id = mr.record_id
     531JOIN patients p ON mr.patient_id = p.patient_id
     532WHERE b.payment_status = 'PENDING'
     533  AND CURRENT_DATE - b.created_at::DATE >= 30
     534ORDER BY days_outstanding DESC;
     535}}}
     536
     537----
     538
     539== Custom domains for EMBG and Phone number formats ==
     540
     541Custom domains replace one-off column checks with reusable types. **embg_format, email_format, and phone_number_format** are new or stricter than before; **non_negative_cost** consolidates a rule already repeated on three tables. non_negative_cost can be applied immediately, since it matches an existing rule. The other three need existing data checked first, since a stricter domain fails if any row doesn't already conform.
     542
     543=== Custom domains ===
     544
     545{{{
     546CREATE DOMAIN embg_format AS TEXT
     547    CHECK (VALUE ~ '^\d{13}$');
     548
     549CREATE DOMAIN email_format AS TEXT
     550    CHECK (
     551        VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
     552        AND LENGTH(VALUE) <= 254
     553    );
     554
     555CREATE DOMAIN phone_number_format AS TEXT
     556    CHECK (VALUE ~ '^\+?[\d\s\-().]{7,20}$');
     557
     558CREATE DOMAIN non_negative_cost AS DECIMAL(12,2)
     559    CHECK (VALUE >= 0);
     560
     561
     562ALTER TABLE procedures ALTER COLUMN cost TYPE non_negative_cost USING cost::non_negative_cost;
     563ALTER TABLE lab_tests ALTER COLUMN cost TYPE non_negative_cost USING cost::non_negative_cost;
     564ALTER TABLE billing ALTER COLUMN total_cost TYPE non_negative_cost USING total_cost::non_negative_cost;
     565
     566-- run these BEFORE altering embg / email / phone columns — every one of these
     567-- should return ZERO rows, otherwise the corresponding ALTER will fail:
     568--   SELECT patient_id, embg FROM patients WHERE embg !~ '^\d{13}$';
     569--   SELECT doctor_id, email_address FROM doctors WHERE email_address !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
     570--   SELECT patient_id, email_address FROM patients WHERE email_address IS NOT NULL AND email_address !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
     571--   SELECT patient_id, phone_number FROM patients WHERE phone_number IS NOT NULL AND phone_number !~ '^\+?[\d\s\-().]{7,20}$';
     572}}}
     573
     574----
     575
     576== Revenue reporting (materialized view and background refresh job) ==
     577
     578Revenue reports need expensive joins across **billing, procedures, and lab tests**, so computing them fresh on every request is wasteful. This materialized view precomputes monthly revenue, split by procedure vs lab test. Procedure revenue is attributed down to department, since procedures carries a **doctor_id** directly. Lab revenue can't be: lab_tests has no doctor or department reference, and **billing_lab_tests** links only to the catalog **test_id**, not a specific performed instance so lab rows are reported to the clinic only.The refresh procedure was verified by manual invocation.
     579
     580=== Views ===
     581
     582{{{
     583CREATE MATERIALIZED VIEW mv_revenue_monthly AS
     584SELECT
     585    DATE_TRUNC('month', b.payment_date)::DATE AS month,
     586    dept.department_id,
     587    dept.department_name,
     588    'PROCEDURE' AS revenue_type,
     589    SUM(p.cost) AS revenue,
     590    COUNT(DISTINCT b.bill_id) AS transaction_count
     591FROM billing b
     592    JOIN billing_procedures bp ON bp.bill_id = b.bill_id
     593    JOIN procedures p ON p.procedure_id = bp.procedure_id
     594    JOIN doctors doc ON doc.doctor_id = p.doctor_id
     595    JOIN departments dept ON dept.department_id = doc.department_id
     596WHERE b.payment_status = 'PAID'
     597GROUP BY DATE_TRUNC('month', b.payment_date), dept.department_id, dept.department_name
     598
     599UNION ALL
     600
     601SELECT
     602    DATE_TRUNC('month', b.payment_date)::DATE AS month,
     603    NULL AS department_id,
     604    NULL AS department_name,
     605    'LAB' AS revenue_type,
     606    SUM(lt.cost) AS revenue,
     607    COUNT(DISTINCT b.bill_id) AS transaction_count
     608FROM billing b
     609    JOIN billing_lab_tests blt ON blt.bill_id = b.bill_id
     610    JOIN lab_tests lt ON lt.test_id = blt.test_id
     611WHERE b.payment_status = 'PAID'
     612GROUP BY DATE_TRUNC('month', b.payment_date);
     613
     614CREATE INDEX idx_mv_revenue_monthly_month ON mv_revenue_monthly (month, revenue_type);
     615
     616CREATE OR REPLACE VIEW v_current_month_revenue AS
     617SELECT month, department_id, department_name, revenue_type, revenue, transaction_count
     618FROM mv_revenue_monthly
     619WHERE month = DATE_TRUNC('month', CURRENT_DATE)::DATE
     620ORDER BY revenue_type, revenue DESC;
     621}}}
     622
     623=== Stored procedures/functions ===
     624
     625{{{
     626CREATE OR REPLACE PROCEDURE medora_job_refresh_revenue_view()
     627    LANGUAGE plpgsql
     628AS $$
     629BEGIN
     630    REFRESH MATERIALIZED VIEW mv_revenue_monthly;
     631END;
     632$$;
     633}}}