Behnam Analytics

Work Power BI & Fabric

Referral-to-treatment waiting list semantic model

A Power BI semantic model written in TMDL for RTT waiting list reporting, with a weekly snapshot fact, a pathway fact, 18 DAX measures and the same KPIs reproduced in pandas on synthetic data.

Synthetic data Every record here is generated. No real patient or organisational data is used.

within 18 weeks at the latest snapshot, against a 92% standard
65.5%
52+ week waiters in four of the ten specialties
263 of 281
DAX measures in a TMDL model that loads cleanly with Microsoft's TOM library
18
what a plain SUM reports for September 2025, when the list was 5,494
21,838

A waiting list report looks simple: one number, one percentage, a list of long waiters. Underneath, the list is a stock that can’t be summed over time, the percentiles need individual waits, and the treatments that shrink the list are dated differently from the referrals that grow it. This project is the semantic model I’d build for it, written as TMDL text files so every table, relationship and measure can be read and reviewed in Git.

The data is synthetic: about 58,000 referral-to-treatment (RTT) pathways for an invented acute trust. A Python script generates it, writes the CSVs the model loads, and computes every KPI in pandas with the same definitions as the DAX, so the charts below show what the report would show.

uv run python projects/rtt-waiting-list-model/run.py

The reporting problem

An RTT clock starts when a referral for consultant-led care is received and stops at first definitive treatment, or when a decision is made that treatment isn’t needed. While the clock runs, the pathway is on the waiting list: an incomplete pathway. The NHS Constitution standard in England is that 92% of patients on incomplete pathways should have waited no more than 18 weeks from referral. Long waits past 52, 65 and 78 weeks are tracked separately, because that’s where the clinical risk and the scrutiny sit.

So the report has to answer four questions every week. How big is the list? What share is within 18 weeks? How many long waiters are there, and in which specialties? Is it getting better? Two definitions drive every number here:

  • Weeks waited are completed weeks from clock start to the snapshot date: days ÷ 7, rounded down.
  • Within 18 weeks means fewer than 18 completed weeks, so under 126 days.

The data

The generator runs a weekly simulation from January 2021. Each open pathway has a chance of a clock stop each week that depends on specialty, referral priority, whether it ends in an admission, and a per-pathway random “frailty” that gives waits their long right tail. The extract keeps the 5,505 pathways on the list on Sunday 2 April 2023 and the 52,545 clock starts from then to Sunday 28 September 2025: 58,050 pathways across ten specialties.

The story is planted, and the README in the code download lists every pattern:

  • Capacity falls to two thirds of normal between May 2023 and January 2024, so the list grows.
  • From mid-2024 six specialties recover to above-normal capacity and add a push on routine pathways past 40 weeks. Trauma & Orthopaedics, ENT, Gynaecology and Oral Surgery settle below normal and keep their long waiters.
  • Christmas and August dip, referrals grow 3% a year, and two validation sprints remove pathways in bulk.

The model design

Table Kind Grain Rows
Waiting List Snapshot periodic snapshot fact Sunday × specialty × priority × wait band 19,884
Pathways accumulating snapshot fact one pathway, with clock start and stop dates 58,050
Date dimension, marked as date table day, 2021 to 2025 1,826
Specialty dimension treatment function 10
Priority dimension routine, urgent, suspected cancer 3
Wait Band dimension weeks-wait band 10
Clock Stop Type dimension admitted, non-admitted, validation removal 3

All nine relationships are many-to-one and filter in one direction, from dimension to fact. Pathways meets Date twice: an active relationship on clock start date and an inactive one on clock stop date, switched on inside the clock-stop measures.

Why a snapshot fact

The waiting list on a date is the set of pathways with a clock start on or before it and no clock stop yet. DAX can compute that from the pathway fact, but it’s a range filter for every point on every chart, and a weekly trend by specialty is 1,310 of them. The snapshot fact does that work once, upstream of the model, at exactly the grain the report needs. It adds up across specialty, priority and wait band, and must never add up across time. A plain SUM over the four Sundays of September 2025 gives 21,838; the list on 28 September was 5,494.

Weekly is a natural grain: NHS England’s Waiting List Minimum Data Set is a weekly collection that sits alongside the monthly RTT statistics.

Why keep the pathway fact

A median can’t be read from band counts. The median and 92nd percentile waits need each pathway’s wait, so those two measures read the pathway fact. So do clock starts and clock stops, which are events, and any drill-through to individual pathways.

Wait bands on the KPI boundaries

The ten bands (0 to <6 weeks up to 104+ weeks) are cut so that 18, 52, 65 and 78 weeks are all band edges. Every KPI that counts pathways above or below a threshold is then an exact sum of whole bands, and the snapshot stays compact: 19,884 rows instead of one row per week waited.

TMDL excerpts

The model is a PBIP semantic model folder. Each table is one file, which is what makes a pull request readable.

RTT.SemanticModel/
├── definition.pbism
└── definition/
    ├── database.tmdl
    ├── model.tmdl
    ├── expressions.tmdl
    ├── relationships.tmdl
    └── tables/
        ├── Clock Stop Type.tmdl
        ├── Date.tmdl
        ├── Pathways.tmdl
        ├── Priority.tmdl
        ├── Specialty.tmdl
        ├── Wait Band.tmdl
        └── Waiting List Snapshot.tmdl

A column with a description, a hidden sort key and a sort order, from Wait Band.tmdl. The files use tab indentation, as Power BI Desktop writes it:

table 'Wait Band'

	column 'Wait Band Key'
		dataType: int64
		isHidden
		isKey
		formatString: 0
		summarizeBy: none
		sourceColumn: wait_band_key

	column 'Wait Band'
		dataType: string
		summarizeBy: none
		sourceColumn: wait_band
		sortByColumn: 'Wait Band Key'

	/// Lower edge of the band in completed weeks. The KPI measures filter on this column.
	column 'Min Weeks'
		dataType: int64
		isHidden
		formatString: 0
		summarizeBy: none
		sourceColumn: min_weeks

The CSV folder is a Power Query parameter in expressions.tmdl, and every partition builds its file path from it:

/// Folder holding the seven CSV files. Keep the trailing backslash.
expression CsvFolder = "C:\RTT\data\" meta [IsParameterQuery = true, Type = "Text", IsParameterQueryRequired = true]

	annotation PBI_ResultType = Text
	partition Pathways = m
		mode: import
		source =
				let
					Source = Csv.Document(File.Contents(CsvFolder & "fact_pathway.csv"), [Delimiter = ",", Encoding = 65001, QuoteStyle = QuoteStyle.Csv]),
					Promoted = Table.PromoteHeaders(Source, [PromoteAllScalars = true]),
					Blanks = Table.ReplaceValue(Promoted, "", null, Replacer.ReplaceValue, {"clock_stop_date", "stop_type_key"}),
					Typed = Table.TransformColumnTypes(
						Blanks,
						{
							{"pathway_id", Int64.Type},
							{"specialty_key", Int64.Type},
							{"priority_key", Int64.Type},
							{"clock_start_date", type date},
							{"clock_stop_date", type date},
							{"stop_type_key", Int64.Type}
						}
					)
				in
					Typed

And the inactive relationship, from relationships.tmdl. The GUID name follows the Power BI Desktop convention; the two column lines are what a reviewer reads:

relationship 4266e0c7-e6fe-5f23-805c-6d368d813243
	isActive: false
	fromColumn: Pathways.'Clock Stop Date'
	toColumn: Date.Date

The whole folder ships in the project code download below.

Key DAX measures

All 18 measures live in the two fact tables’ TMDL files, each with a /// description. run.py reads them back out of the TMDL and writes rtt-measures.dax, a DAX query you can paste into DAX query view.

The list size takes the last Sunday inside the current date filter. The date lookup removes the specialty, priority and wait band filters, so every row of a visual uses the same week:

Latest Snapshot Date =
CALCULATE (
    MAX ( 'Waiting List Snapshot'[Snapshot Date] ),
    REMOVEFILTERS ( Specialty ),
    REMOVEFILTERS ( Priority ),
    REMOVEFILTERS ( 'Wait Band' )
)

Waiting List =
VAR AsAt = [Latest Snapshot Date]
RETURN
    CALCULATE ( SUM ( 'Waiting List Snapshot'[Pathway Count] ), 'Date'[Date] = AsAt )

Without those REMOVEFILTERS, a specialty with nobody in a band this week falls back to the last Sunday it had someone there. For the 104+ weeks band that turns a correct total of 3 into rows that add up to 9.

The threshold measures filter on the band’s lower edge:

% Within 18 Weeks = DIVIDE ( [Within 18 Weeks], [Waiting List] )
Within 18 Weeks = CALCULATE ( [Waiting List], 'Wait Band'[Min Weeks] < 18 )
52+ Week Waiters = CALCULATE ( [Waiting List], 'Wait Band'[Min Weeks] >= 52 )

The median rebuilds the list on the snapshot date from the pathway fact, then takes completed weeks per pathway. The 92nd percentile is the same with PERCENTILEX.INC:

Median Wait (Weeks) =
VAR AsAt = [Latest Snapshot Date]
VAR OpenPathways =
    CALCULATETABLE (
        Pathways,
        REMOVEFILTERS ( 'Date' ),
        Pathways[Clock Start Date] <= AsAt,
        ISBLANK ( Pathways[Clock Stop Date] ) || Pathways[Clock Stop Date] > AsAt
    )
RETURN
    MEDIANX (
        OpenPathways,
        QUOTIENT ( DATEDIFF ( Pathways[Clock Start Date], AsAt, DAY ), 7 )
    )

Comparisons anchor on the snapshot date rather than on the date filter. 364 days is 52 weeks, so a Sunday lands on a Sunday:

Waiting List Same Week LY =
VAR AsAt = [Latest Snapshot Date]
RETURN
    CALCULATE ( [Waiting List], REMOVEFILTERS ( 'Date' ), 'Date'[Date] = AsAt - 364 )

Clock stops are events, so they suit the built-in functions. They switch on the inactive relationship:

Clock Stops Admitted =
CALCULATE (
    COUNTROWS ( Pathways ),
    USERELATIONSHIP ( Pathways[Clock Stop Date], 'Date'[Date] ),
    'Clock Stop Type'[Clock Stop Type] = "Admitted"
)

Treatments Same Period LY = CALCULATE ( [Treatments], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )

DAX for waiting-list KPIs walks through each pattern and the mistakes it avoids.

Results

The list grew from 5,505 on 2 April 2023 to a peak of 7,115 on 17 March 2024, then came back to 5,494 by 28 September 2025. The last week added 34 pathways; the same week a year earlier had 5,339.

Waiting list size, weekly snapshotIncomplete pathways every Sunday, with the same week a year earlier
Data table
Snapshot dateWaiting listSame week last year
2023-04-025,505
2023-04-095,538
2023-04-165,505
2023-04-235,462
2023-04-305,453
2023-05-075,439
2023-05-145,463
2023-05-215,474
2023-05-285,504
2023-06-045,518
2023-06-115,508
2023-06-185,555
2023-06-255,591
2023-07-025,594
2023-07-095,654
2023-07-165,692
2023-07-235,708
2023-07-305,734
2023-08-065,767
2023-08-135,745
2023-08-205,727
2023-08-275,722
2023-09-035,730
2023-09-105,765
2023-09-175,784
2023-09-245,856
2023-10-015,863
2023-10-085,879
2023-10-155,950
2023-10-225,998
2023-10-296,068
2023-11-056,058
2023-11-126,108
2023-11-196,186
2023-11-266,244
2023-12-036,287
2023-12-106,348
2023-12-176,443
2023-12-246,548
2023-12-316,611
2024-01-076,655
2024-01-146,715
2024-01-216,799
2024-01-286,922
2024-02-046,998
2024-02-116,982
2024-02-187,006
2024-02-257,078
2024-03-037,051
2024-03-107,061
2024-03-177,115
2024-03-247,029
2024-03-316,9555,505
2024-04-076,9525,538
2024-04-146,9285,505
2024-04-216,8875,462
2024-04-286,8515,453
2024-05-056,7885,439
2024-05-126,7545,463
2024-05-196,6655,474
2024-05-266,6185,504
2024-06-026,5675,518
2024-06-096,5145,508
2024-06-166,4175,555
2024-06-236,3175,591
2024-06-306,2825,594
2024-07-076,1935,654
2024-07-146,1215,692
2024-07-216,0765,708
2024-07-286,0525,734
2024-08-045,9755,767
2024-08-115,8195,745
2024-08-185,7395,727
2024-08-255,6765,722
2024-09-015,6125,730
2024-09-085,5285,765
2024-09-155,4485,784
2024-09-225,4285,856
2024-09-295,3395,863
2024-10-065,3385,879
2024-10-135,3695,950
2024-10-205,3695,998
2024-10-275,3886,068
2024-11-035,3796,058
2024-11-105,3646,108
2024-11-175,3666,186
2024-11-245,3786,244
2024-12-015,3936,287
2024-12-085,3956,348
2024-12-155,4586,443
2024-12-225,4786,548
2024-12-295,5106,611
2025-01-055,5286,655
2025-01-125,5496,715
2025-01-195,5886,799
2025-01-265,6236,922
2025-02-025,6086,998
2025-02-095,5746,982
2025-02-165,5887,006
2025-02-235,5397,078
2025-03-025,5077,051
2025-03-095,4917,061
2025-03-165,4877,115
2025-03-235,4857,029
2025-03-305,4756,955
2025-04-065,4416,952
2025-04-135,4446,928
2025-04-205,4346,887
2025-04-275,4436,851
2025-05-045,4076,788
2025-05-115,4366,754
2025-05-185,4426,665
2025-05-255,4676,618
2025-06-015,5176,567
2025-06-085,5206,514
2025-06-155,4976,417
2025-06-225,5066,317
2025-06-295,4706,282
2025-07-065,4506,193
2025-07-135,4626,121
2025-07-205,4806,076
2025-07-275,5126,052
2025-08-035,4755,975
2025-08-105,4785,819
2025-08-175,4625,739
2025-08-245,4325,676
2025-08-315,4085,612
2025-09-075,4335,528
2025-09-145,4515,448
2025-09-215,4605,428
2025-09-285,4945,339

Synthetic data. Source: projects/rtt-waiting-list-model.

The share within 18 weeks fell from 64.2% to a low of 57.9% on 21 April 2024 and recovered to 65.5%. At trust level that’s still 26.5 points short of the standard. The specialty lines show why a trust average hides the problem: Cardiology is at 89.0%, Trauma & Orthopaedics at 53.6%, slightly worse than where it started.

Share of the waiting list within 18 weeksTrust total and the specialties with the lowest and highest share now
Data table
Snapshot dateTrustTrauma & OrthopaedicsCardiology
2023-04-0264%54%77%
2023-04-0964%54%78%
2023-04-1664%54%77%
2023-04-2363%54%78%
2023-04-3064%54%79%
2023-05-0765%55%79%
2023-05-1465%54%78%
2023-05-2165%54%78%
2023-05-2865%54%77%
2023-06-0465%55%76%
2023-06-1165%55%77%
2023-06-1865%55%78%
2023-06-2565%54%78%
2023-07-0265%54%80%
2023-07-0965%54%80%
2023-07-1665%54%81%
2023-07-2365%53%80%
2023-07-3065%54%81%
2023-08-0665%53%82%
2023-08-1365%53%82%
2023-08-2064%53%82%
2023-08-2764%53%82%
2023-09-0364%54%84%
2023-09-1064%53%82%
2023-09-1764%54%82%
2023-09-2464%54%82%
2023-10-0164%54%83%
2023-10-0864%52%83%
2023-10-1564%54%84%
2023-10-2264%53%85%
2023-10-2964%53%85%
2023-11-0563%53%82%
2023-11-1263%52%80%
2023-11-1963%52%79%
2023-11-2663%53%81%
2023-12-0363%54%81%
2023-12-1063%53%80%
2023-12-1763%53%79%
2023-12-2463%53%78%
2023-12-3162%53%76%
2024-01-0762%53%76%
2024-01-1462%53%74%
2024-01-2162%53%75%
2024-01-2861%54%74%
2024-02-0461%53%73%
2024-02-1161%54%75%
2024-02-1861%53%74%
2024-02-2561%52%75%
2024-03-0360%52%74%
2024-03-1060%52%74%
2024-03-1760%52%75%
2024-03-2459%52%73%
2024-03-3158%51%73%
2024-04-0758%50%72%
2024-04-1458%50%73%
2024-04-2158%49%74%
2024-04-2858%49%77%
2024-05-0558%50%78%
2024-05-1259%50%78%
2024-05-1959%52%79%
2024-05-2659%52%80%
2024-06-0259%52%81%
2024-06-0959%51%80%
2024-06-1659%52%79%
2024-06-2359%52%79%
2024-06-3058%52%79%
2024-07-0759%53%80%
2024-07-1459%52%81%
2024-07-2160%53%82%
2024-07-2860%54%84%
2024-08-0461%55%83%
2024-08-1161%55%83%
2024-08-1862%56%85%
2024-08-2562%56%86%
2024-09-0163%57%85%
2024-09-0864%57%87%
2024-09-1565%57%89%
2024-09-2265%58%88%
2024-09-2966%59%88%
2024-10-0666%58%88%
2024-10-1367%59%86%
2024-10-2067%58%87%
2024-10-2767%58%89%
2024-11-0367%57%89%
2024-11-1067%57%90%
2024-11-1767%57%90%
2024-11-2467%56%89%
2024-12-0167%56%88%
2024-12-0867%56%89%
2024-12-1567%56%88%
2024-12-2267%57%85%
2024-12-2966%56%84%
2025-01-0566%55%84%
2025-01-1266%55%84%
2025-01-1966%56%85%
2025-01-2666%55%84%
2025-02-0265%55%83%
2025-02-0966%54%86%
2025-02-1665%54%86%
2025-02-2366%54%87%
2025-03-0265%54%87%
2025-03-0965%55%87%
2025-03-1665%56%86%
2025-03-2365%56%85%
2025-03-3065%56%85%
2025-04-0665%56%86%
2025-04-1365%56%86%
2025-04-2064%55%87%
2025-04-2764%54%87%
2025-05-0465%55%87%
2025-05-1166%56%88%
2025-05-1866%57%88%
2025-05-2566%57%88%
2025-06-0166%56%89%
2025-06-0867%57%90%
2025-06-1567%58%90%
2025-06-2268%58%92%
2025-06-2968%59%91%
2025-07-0668%58%91%
2025-07-1368%58%91%
2025-07-2068%57%91%
2025-07-2768%57%90%
2025-08-0367%56%88%
2025-08-1067%56%87%
2025-08-1766%55%88%
2025-08-2466%54%89%
2025-08-3166%55%90%
2025-09-0766%54%91%
2025-09-1466%54%89%
2025-09-2166%53%90%
2025-09-2866%54%89%

Synthetic data. Source: projects/rtt-waiting-list-model.

Long waits are now concentrated. Of the 281 pathways past 52 weeks, 263 are in four specialties: Trauma & Orthopaedics (108), Gynaecology (75), ENT (54) and Oral Surgery (26).

52+ week waiters by specialtySnapshot of 28 Sep 2025
Data table
Specialty52+ week waiters
Trauma & Orthopaedics108
Gynaecology75
ENT54
Oral Surgery26
General Surgery5
Ophthalmology4
Gastroenterology4
Urology3
Cardiology1
Dermatology1

Synthetic data. Source: projects/rtt-waiting-list-model.

The other six specialties went from 106 long waiters at the start to a peak of 148 and are down to 18. The four with long tails never cleared theirs.

52+ week waiters over timeThe four specialties still carrying long waits, and the other six combined
Data table
Snapshot dateTrauma & OrthopaedicsGynaecologyENTOral SurgeryOther six
2023-04-02106663930106
2023-04-09107674027100
2023-04-16104654026102
2023-04-2310765382498
2023-04-3011061372498
2023-05-0710659342290
2023-05-1410963372194
2023-05-2110864391892
2023-05-2811068411888
2023-06-0411868432084
2023-06-1111666431989
2023-06-1811670411889
2023-06-2511566392083
2023-07-0211665382187
2023-07-0911965412287
2023-07-1612565472187
2023-07-2312665412288
2023-07-3012765422290
2023-08-0612764422492
2023-08-1312359442389
2023-08-2012458432789
2023-08-2712359422688
2023-09-0311658442698
2023-09-1011155432899
2023-09-1711553412996
2023-09-2412056393097
2023-10-01120604229100
2023-10-08129594531101
2023-10-1513260473399
2023-10-2212662473298
2023-10-2912864473392
2023-11-0513260454094
2023-11-1213257464095
2023-11-1913157494098
2023-11-2612957523995
2023-12-03124645140100
2023-12-10124665438109
2023-12-17131645337114
2023-12-24133655338114
2023-12-31132675338116
2024-01-07131665435120
2024-01-14135655538124
2024-01-21133655637126
2024-01-28133705737129
2024-02-04133705538133
2024-02-11129705640138
2024-02-18135745940134
2024-02-25136756545136
2024-03-03135757147136
2024-03-10135757346135
2024-03-17139747546135
2024-03-24133727840136
2024-03-31139757839134
2024-04-07142768239138
2024-04-14140798042141
2024-04-21142797842137
2024-04-28139837844136
2024-05-05142848345138
2024-05-12146808744139
2024-05-19138848348142
2024-05-26133868449139
2024-06-02138928448145
2024-06-09136978244141
2024-06-16139959044143
2024-06-23132939045146
2024-06-30137948243148
2024-07-07128907944136
2024-07-14130857744126
2024-07-21125847839113
2024-07-28126888141103
2024-08-0412990833997
2024-08-1112592823895
2024-08-1812586813684
2024-08-2512979793875
2024-09-0112783794270
2024-09-0812378764165
2024-09-1512175693856
2024-09-2211478693557
2024-09-2911273723655
2024-10-0610973683251
2024-10-1311174703048
2024-10-2011874682939
2024-10-2711372632836
2024-11-0311374622733
2024-11-1011473632835
2024-11-1710573613039
2024-11-2410873572939
2024-12-0110970542840
2024-12-0810673582637
2024-12-1510874632735
2024-12-2210773642832
2024-12-2910673632927
2025-01-0510377572927
2025-01-1210176612825
2025-01-1910578612826
2025-01-2610583623124
2025-02-0211180633122
2025-02-0910979643220
2025-02-1611080633219
2025-02-2311383613115
2025-03-0210779552913
2025-03-0910781572813
2025-03-1611084592614
2025-03-2310387572313
2025-03-3010985622715
2025-04-0611382613213
2025-04-1311385603212
2025-04-2011087623412
2025-04-2711595593212
2025-05-0411691603211
2025-05-1111291582912
2025-05-1811288603014
2025-05-2511886592615
2025-06-0112288572714
2025-06-0811589583014
2025-06-1511287552917
2025-06-2211189583317
2025-06-2910588553018
2025-07-0610784572915
2025-07-1311180603015
2025-07-2010479583013
2025-07-2710773563120
2025-08-0311676543116
2025-08-1011772523116
2025-08-1711977552815
2025-08-2412275573015
2025-08-3111674552417
2025-09-0711672522716
2025-09-1411176512617
2025-09-2111575552517
2025-09-2810875542618

Synthetic data. Source: projects/rtt-waiting-list-model.

Against the same Sunday a year earlier, the tail past 52 weeks is thinner in every band from 52 to 104 weeks, while the 18 to 26 week band has grown from 538 to 678. That is the next cohort of long waiters if capacity doesn’t hold.

Waiting list by weeks waitedIncomplete pathways per wait band, now and a year earlier
Data table
Weeks waited28 Sep 202529 Sep 2024
0 to <6 weeks1,8491,838
6 to <12 weeks1,0681,070
12 to <18 weeks681629
18 to <26 weeks678538
26 to <39 weeks603575
39 to <52 weeks334341
52 to <65 weeks167201
65 to <78 weeks80100
78 to <104 weeks3144
104+ weeks33

Synthetic data. Source: projects/rtt-waiting-list-model.

Between April 2023 and September 2025 there were 40,162 non-admitted and 11,299 admitted clock stops, plus 1,095 validation removals. The two validation sprints stand out in the removals line. Removals shorten the list without treating anyone, which is why they have their own measure and stay out of Treatments.

Clock stops per weekTreatments and validation removals, by week ending Sunday
Data table
Week endingAdmittedNon-admittedValidation removal
2023-04-09782965
2023-04-16913375
2023-04-23873075
2023-04-30952938
2023-05-07943117
2023-05-14972782
2023-05-21822873
2023-05-28793166
2023-06-04873008
2023-06-11953013
2023-06-18722846
2023-06-25822931
2023-07-02883022
2023-07-09812594
2023-07-16792925
2023-07-23932814
2023-07-30803086
2023-08-06783056
2023-08-13842876
2023-08-20712831
2023-08-27722932
2023-09-03862750
2023-09-10862774
2023-09-17823077
2023-09-24752692
2023-10-01942923
2023-10-08682741
2023-10-15612829
2023-10-22852683
2023-10-29653024
2023-11-05703164
2023-11-12772605
2023-11-19722632
2023-11-26662715
2023-12-03693033
2023-12-10782563
2023-12-17502551
2023-12-24592324
2023-12-31321199
2024-01-07431264
2024-01-14532895
2024-01-21642663
2024-01-28822386
2024-02-04672523
2024-02-11863154
2024-02-18782924
2024-02-25762996
2024-03-03833075
2024-03-10843254
2024-03-17902926
2024-03-241043454
2024-03-31873708
2024-04-07843335
2024-04-14913333
2024-04-211023347
2024-04-28863564
2024-05-051173298
2024-05-12943527
2024-05-191093745
2024-05-261063485
2024-06-021073617
2024-06-091133515
2024-06-161193714
2024-06-231143915
2024-06-301003335
2024-07-071233515
2024-07-141283656
2024-07-211073638
2024-07-281143613
2024-08-041123588
2024-08-119733365
2024-08-1810028164
2024-08-2511130757
2024-09-018631241
2024-09-088734056
2024-09-1512531748
2024-09-229932049
2024-09-298734760
2024-10-06913082
2024-10-13923124
2024-10-20733143
2024-10-27933143
2024-11-03893277
2024-11-10853476
2024-11-17953245
2024-11-24623233
2024-12-011103224
2024-12-08942885
2024-12-15752915
2024-12-22933206
2024-12-29481446
2025-01-05491571
2025-01-12763274
2025-01-19763173
2025-01-26972957
2025-02-02873375
2025-02-09813697
2025-02-16923342
2025-02-23903513
2025-03-021083498
2025-03-09963284
2025-03-16723532
2025-03-231033292
2025-03-30903495
2025-04-06843424
2025-04-13843420
2025-04-20823292
2025-04-27853413
2025-05-04873462
2025-05-11743265
2025-05-18833315
2025-05-25813139
2025-06-01853054
2025-06-088832629
2025-06-1510232138
2025-06-229829429
2025-06-299934339
2025-07-06943101
2025-07-131043224
2025-07-20993015
2025-07-27873175
2025-08-03903684
2025-08-10882853
2025-08-17933022
2025-08-24923156
2025-08-31943153
2025-09-07882983
2025-09-14903087
2025-09-211033223
2025-09-28733402

Synthetic data. Source: projects/rtt-waiting-list-model.

At the latest snapshot the median wait is 11.0 weeks and the 92nd percentile is 44.6 weeks, down from 13.0 and 49.0 at the April 2024 low point.

What was tested, and what wasn’t

  • TMDL structure: tested. A small .NET program in the project loads the folder with TmdlSerializer from Microsoft’s Analysis Services client library (the Tabular Object Model). That fails on syntax errors, unknown properties and broken references, and I checked that it does by breaking a sort-by column on purpose. Serialising the loaded model back out reproduces the files byte for byte.
  • DAX syntax: tested. All 18 measures and the full query file parse cleanly in DAX Formatter. That checks syntax, not function names or results.
  • KPI logic: tested in pandas. Each pandas function follows one measure’s definition, and run.py checks that the two fact CSVs agree on list size and within-18-weeks counts at all 131 snapshots.
  • Not tested: opening the model in Power BI Desktop, running the Power Query steps, refreshing, and evaluating the DAX. I didn’t have Power BI Desktop or an Analysis Services engine to run it on. The README explains how to open it, and the DAX query’s output by specialty should match the numbers above.

Limits

  • The waits come from a hazard model, not from any real trust. The shape is plausible; the levels are not calibrated to anything.
  • “Within 18 weeks” here means fewer than 18 completed weeks. Check that against your local RTT reporting rules before reusing the measures: a one-day difference at the boundary changes the count.
  • Pathways that stopped before 2 April 2023 are not in the extract, so Clock Starts before that date only counts people who were still waiting.
  • No clock pauses, no patient-level identifiers, and one pathway per referral. Real RTT data has to handle all three.
  • The median and 92nd percentile ignore a wait band filter, because the pathway fact has no relationship to Wait Band.
  • The median measure scans the pathway fact at query time. At 58,050 rows that’s fine. At millions I’d precompute each pathway’s wait at each snapshot, or accept banded percentiles.

What I’d do next

  • Open it in Power BI Desktop, refresh, and compare the DAX query’s output with run.py, then build the report pages on top.
  • Add a DAX query test to CI: deploy to a workspace, run the query over XMLA, and diff the result against the pandas numbers.
  • Split incomplete pathways into those with and without a decision to admit, which is the view theatre capacity planning needs.
  • Replace the time-comparison measures with a calculation group once there are more than two of them. The theatre utilisation semantic model does this, and calculation groups in practice covers the pitfalls.

Built with

  • Power BI
  • TMDL
  • DAX
  • Power Query M
  • Python
  • pandas
  • Tabular Object Model

Downloads