Shifa Clinics operations
Shifa Clinics (fictional) · 10 tables · 47,764 rows · version 1
Two years (2024–2025) of outpatient operations: clinics, service lines, physicians, about 2,500 synthetic patients, insurers, a service price list, ~15,000 appointments, the services delivered at each visit, invoices and insurance claim rejections. Built for operations analytics: no-show and cancellation rates, wait times, physician utilisation, revenue by service line and payer, and claim rejection analysis. Contains no diagnoses or medical records. Amounts are in SAR.
The documentation is open to everyone. Querying this dataset in the playground is included with Formula Quest membership; lessons that use it follow their own access.
Relationships
Sample queries
SELECT channel, status, count(*) AS appointments
FROM appointments
GROUP BY channel, status
ORDER BY channel, appointments DESC;SELECT c.clinic_name,
count(*) AS booked,
round(100.0 * count(*) FILTER (WHERE a.status = 'no_show') / count(*), 1) AS no_show_pct
FROM appointments a
JOIN clinics c ON c.clinic_id = a.clinic_id
WHERE a.channel <> 'walk_in' AND a.status IN ('completed', 'no_show')
GROUP BY c.clinic_name
ORDER BY no_show_pct DESC;SELECT c.clinic_name,
percentile_cont(0.5) WITHIN GROUP (ORDER BY extract(epoch FROM a.started_at - a.check_in_at) / 60) AS median_wait_min
FROM appointments a
JOIN clinics c ON c.clinic_id = a.clinic_id
WHERE a.status = 'completed' AND a.check_in_at IS NOT NULL
GROUP BY c.clinic_name
ORDER BY median_wait_min DESC;SELECT date_trunc('month', a.scheduled_at)::date AS month, sl.service_line_name,
sum(s.quantity * s.unit_price) AS revenue
FROM appointment_services s
JOIN appointments a ON a.appointment_id = s.appointment_id
JOIN services sv ON sv.service_code = s.service_code
JOIN service_lines sl ON sl.service_line_id = sv.service_line_id
GROUP BY 1, 2
ORDER BY 1, 3 DESC;SELECT ins.insurer_name, r.reason_code, count(*) AS rejections
FROM claim_rejections r
JOIN invoices i ON i.invoice_id = r.invoice_id
JOIN insurers ins ON ins.insurer_id = i.insurer_id
GROUP BY 1, 2
ORDER BY 1, 3 DESC;Data dictionary
clinics
6 rows · primary key (clinic_id)Clinic branches.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| clinic_id | integer | no | PK | Unique clinic number. |
| clinic_name | text | no | unique | Branch name. |
| city | text | no | City. | |
| opened_on | date | no | Date the branch opened (Abha opened during the data period). | |
| exam_rooms | integer | no | Number of examination rooms. |
service_lines
7 rows · primary key (service_line_id)Medical specialties offered (service lines).
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| service_line_id | integer | no | PK | Unique service line number. |
| service_line_code | text | no | unique | Short code (FM, PED, DEN, ...). |
| service_line_name | text | no | unique | Service line name. |
physicians
59 rows · primary key (physician_id)Physicians, one row per physician per clinic assignment (a transfer creates a new row).
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| physician_id | integer | no | PK | Unique physician record number. |
| clinic_id | integer | no | clinics.clinic_id | Clinic the physician works at. |
| service_line_id | integer | no | service_lines.service_line_id | Specialty. |
| first_name | text | no | First name (fictional). | |
| last_name | text | no | Family name (fictional). | |
| gender | char(2) | no | F or M. | |
| title | text | no | Consultant, Specialist or General Practitioner. | |
| start_date | date | no | First working day at this clinic. | |
| end_date | date | yes | Last working day. NULL = still working here. | |
| is_active | boolean | no | TRUE while the physician still works at this clinic. |
insurers
6 rows · primary key (insurer_id)Health insurers the clinics bill. Patients without an insurer are self-pay.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| insurer_id | integer | no | PK | Unique insurer number. |
| insurer_name | text | no | unique | Insurer name (fictional). |
| coverage_percent | numeric(5,2) | no | Share of the gross amount the insurer pays; the patient pays the rest. | |
| is_active | boolean | no | FALSE when the contract has ended (Basma Health, after 2025-06-30). |
patients
2,444 rows · primary key (patient_id)Registered patients (synthetic). Only minimal demographics are stored: no full date of birth and no medical information.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| patient_id | integer | no | PK | Unique patient record number. |
| patient_number | text | no | unique | Medical record number shown on cards (SH-######), ascending with registration date. |
| first_name | text | no | First name (fictional). | |
| last_name | text | no | Family name (fictional). | |
| gender | char(2) | no | F or M. | |
| birth_year | smallint | yes | Year of birth. NULL when not captured. | |
| city | text | yes | City of residence. NULL when not captured. | |
| insurer_id | integer | yes | insurers.insurer_id | Current insurer. NULL = self-pay. |
| registered_on | date | no | Date the patient record was created. |
services
36 rows · primary key (service_code)Price list of billable services. list_price is the current (2025) price.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| service_code | text | no | PK | Service code, prefixed with the service line code. |
| service_name | text | no | Service name. | |
| service_line_id | integer | no | service_lines.service_line_id | Service line that delivers it. |
| list_price | numeric(12,2) | no | Current list price in SAR (2024 prices were about 5% lower). | |
| standard_minutes | integer | no | Planned duration in minutes. | |
| is_active | boolean | no | FALSE for services no longer offered. |
appointments
14,645 rows · primary key (appointment_id)One row per booking or walk-in. A rescheduled booking keeps its row (status 'rescheduled') and points to the replacement appointment.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| appointment_id | integer | no | PK | Unique appointment number (ascending with booking time). |
| patient_id | integer | no | patients.patient_id | The patient. |
| physician_id | integer | no | physicians.physician_id | The physician the appointment is with (determines the service line). |
| clinic_id | integer | no | clinics.clinic_id | Where the appointment takes place. |
| booked_at | timestamp | no | When the booking was made. For walk-ins equal to scheduled_at. | |
| scheduled_at | timestamp | no | Appointment slot start (walk-ins: arrival time). | |
| status | text | no | completed, no_show (did not come), cancelled (by the patient or clinic) or rescheduled (moved to another appointment). | |
| cancelled_at | timestamp | yes | When the cancellation was recorded. NULL unless status = 'cancelled'. | |
| check_in_at | timestamp | yes | Reception check-in. NULL if the patient never arrived or reception skipped the check-in. | |
| started_at | timestamp | yes | When the physician started the consultation. Only for completed visits. | |
| ended_at | timestamp | yes | When the consultation ended. NULL if never arrived or not closed in the system. | |
| channel | text | no | How it was booked: app, phone or walk_in. | |
| rescheduled_to_appointment_id | integer | yes | appointments.appointment_id | For status 'rescheduled': the replacement appointment. NULL otherwise. |
appointment_services
18,578 rows · primary key (appointment_id, line_no)Services delivered and billed at a completed visit, one row per invoice line.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| appointment_id | integer | no | PK appointments.appointment_id | The visit. |
| line_no | smallint | no | PK | Line number within the visit (1, 2, ...). |
| service_code | text | no | services.service_code | The service delivered. |
| quantity | integer | no | Units delivered (e.g. number of fillings). | |
| unit_price | numeric(12,2) | no | Price charged per unit in SAR (the list price in force on the visit date). |
invoices
10,892 rows · primary key (invoice_id)One invoice per billed visit. For insured visits status tracks the insurance claim; for self-pay visits it tracks the patient's payment.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| invoice_id | integer | no | PK | Unique invoice number. |
| appointment_id | integer | no | appointments.appointment_idunique | The visit billed. |
| insurer_id | integer | yes | insurers.insurer_id | Insurer billed for this visit. NULL = self-pay (may differ from the patient's current insurer). |
| invoice_date | date | no | Date the invoice was issued (0–3 days after the visit). | |
| gross_amount | numeric(12,2) | no | Total of the visit's service lines, SAR. | |
| insurer_amount | numeric(12,2) | no | Share claimed from the insurer (gross × coverage_percent). 0 for self-pay. | |
| patient_amount | numeric(12,2) | no | Share paid by the patient (co-payment, or everything for self-pay). | |
| status | text | no | paid, pending (awaiting payment), rejected (claim finally rejected) or written_off. | |
| paid_on | date | yes | Date the invoice was settled. NULL unless status = 'paid'. |
claim_rejections
1,091 rows · primary key (rejection_id)Insurance claim rejections. An invoice can be rejected more than once; resubmitted claims may be paid later.
| Column | Type | Null? | Key | Description |
|---|---|---|---|---|
| rejection_id | integer | no | PK | Unique rejection number. |
| invoice_id | integer | no | invoices.invoice_id | The rejected invoice (claim). |
| rejected_on | date | no | Date the insurer rejected the claim. | |
| reason_code | text | no | missing_preauthorization, service_not_covered, coding_error, eligibility_expired, duplicate_claim or late_submission. | |
| resubmitted | boolean | no | TRUE if the clinic corrected and resubmitted the claim after this rejection. |