A SQL + Power BI analysis of synthetic clinic encounter data, identifying which encounter types, days, and hours are associated with the longest visit durations and the most schedule strain i.e., where patient flow is bottlenecking.
A clinic wants to know where its schedule is actually straining: which visit types
consistently run long, whether certain days/hours compound the problem (high volume
and high duration at the same time), and what a schedule-template change could do
about it. This project treats "bottleneck" as an operational definition, not a vibe: a
(encounter class, day, hour) combination only counts if it ranks poorly on duration
and carries real visit volume, since a slow slot with three visits a week isn't a
scheduling problem worth acting on.
Synthea synthetic patient records:
1,171 patients, 53,346 encounters, 5,855 providers, 1,119 organizations. This is
fully synthetic data (no real PHI), which is why it's usable for a public portfolio
project at all. Key file: encounters.csv (one row per visit, with START/STOP
timestamps and ENCOUNTERCLASS).
Raw CSVs are included in data/raw/ in this repo (~18MB total, well under GitHub's
size limits) so the SQL runs immediately after cloning, with no separate download or
Synthea generation step required.
- DuckDB: chosen over Postgres/SQLite for this project because the entire task is analytical (aggregate, group, rank) with no need for a running server or multi-user access. DuckDB queries CSV files directly with no separate import step.
- Power BI (web/service): built entirely in the browser, no Desktop install. Free-tier "My workspace" is sufficient since this is a personal portfolio piece, not a shared team report.
.
├── README.md
├── sql/
│ ├── 03_data_quality_profile.sql # nulls, date range, duration distribution, outliers
│ ├── 04_core_aggregation.sql # avg/median duration + visit count by class/day/hour
│ ├── 05_ranked_bottlenecks.sql # RANK() window function, worst slot per class
│ └── 06_export_for_powerbi.sql # exports the aggregated (not raw) result to CSV
├── data/
│ ├── raw/ # Synthea source CSVs, included in this repo
│ └── processed/ # aggregated output, tracked; this is the actual deliverable
└── screenshots/ # Power BI visuals (see below)
File numbering follows the project's stage numbering (stages 1-2 were problem framing and data acquisition, no SQL produced there).
- Clone this repo (the Synthea CSVs are already included in
data/raw/) - Install DuckDB (e.g.
brew install duckdbon macOS). - Run the SQL from the repo root (paths are relative to it):
duckdb < sql/03_data_quality_profile.sql duckdb < sql/04_core_aggregation.sql duckdb < sql/05_ranked_bottlenecks.sql duckdb < sql/06_export_for_powerbi.sql - Upload
data/processed/encounter_bottleneck_summary.csvto Power BI (workspace → New item → Semantic model → CSV → Upload file → Create a report) to rebuild the visuals described below.
(Want a fresh/different Synthea dataset instead? Generate one with the
Synthea generator, e.g. run_synthea -p 1200,
and drop the four CSVs into data/raw/ in place of the included ones. Synthea
generates new random data every run, so exact numbers won't match this repo's
findings, but the SQL logic will run correctly against any Synthea-shaped export.)
Across nearly all classes, the "worst" ranked day/hour slot turned out to be a small-
sample artifact or (for urgentcare) impossible to detect at all, since every visit
in this dataset is a fixed 15 minutes. The one credible, actionable pattern:
Wellness visits on Saturday afternoon (~4pm) show both above-average volume (177 visits vs. a typical 101) and above-average duration (26.2 min vs. a 22.4 min class average). Recommendation: extend the default wellness slot length specifically for that block, or cap/redistribute bookings in it.
Honest caveats: this is synthetic data, and at least one strong-looking statistical pattern turned out to be a data-generation artifact rather than a real finding.
the day x hour Matrix heatmap (colored by average
duration, with an encounter-class slicer)
weighted-average bar chart by
encounter class, both built in Power BI's web report editor.*