@@ -11,7 +11,7 @@ import {
1111 MetricPolarity ,
1212} from '@fergusbisset/nhs-fdp-design-system/components/DataVisualisation/charts/SPC/icons' ;
1313
14- export type OrgLevel = "National" | "Region" | "ICB" ;
14+ export type OrgLevel = "National" | "Region" | "ICB" | "Provider" ;
1515export type ReportingCadence = "Weekly" | "Monthly" | "Quarterly" | "Annual" ;
1616export type Domain = "Access" | "Quality" | "Capacity" | "System Performance" | "Workforce" ;
1717export type RAGStatus = "Red" | "Amber" | "Green" | "Not Set" ;
@@ -46,7 +46,7 @@ const REGIONS = [
4646 { code : "Y63" , name : "East of England" } ,
4747] ;
4848
49- // Sample ICBs (2-3 per region for demonstration)
49+ // Sample ICBs (2 per region for demonstration)
5050const ICBS = [
5151 { code : "QMJ" , name : "NHS North West London ICB" , region : "Y56" } ,
5252 { code : "QRV" , name : "NHS North East London ICB" , region : "Y56" } ,
@@ -64,6 +64,52 @@ const ICBS = [
6464 { code : "QH8" , name : "NHS Hertfordshire and West Essex ICB" , region : "Y63" } ,
6565] ;
6666
67+ // Sample NHS Trusts (Providers) - 2 per ICB for demonstration
68+ const PROVIDERS = [
69+ // North West London ICB
70+ { code : "RQM" , name : "Chelsea and Westminster Hospital NHS Foundation Trust" , icb : "QMJ" } ,
71+ { code : "RYJ" , name : "Imperial College Healthcare NHS Trust" , icb : "QMJ" } ,
72+ // North East London ICB
73+ { code : "R1H" , name : "Barts Health NHS Trust" , icb : "QRV" } ,
74+ { code : "RQX" , name : "Homerton Healthcare NHS Foundation Trust" , icb : "QRV" } ,
75+ // Birmingham and Solihull ICB
76+ { code : "RRK" , name : "University Hospitals Birmingham NHS Foundation Trust" , icb : "QWE" } ,
77+ { code : "RXK" , name : "Birmingham Women's and Children's NHS Foundation Trust" , icb : "QWE" } ,
78+ // Coventry and Warwickshire ICB
79+ { code : "RKB" , name : "University Hospitals Coventry and Warwickshire NHS Trust" , icb : "QHL" } ,
80+ { code : "RJ7" , name : "South Warwickshire NHS Foundation Trust" , icb : "QHL" } ,
81+ // Humber and North Yorkshire ICB
82+ { code : "RCB" , name : "Hull University Teaching Hospitals NHS Trust" , icb : "QOQ" } ,
83+ { code : "RCD" , name : "York and Scarborough Teaching Hospitals NHS Foundation Trust" , icb : "QOQ" } ,
84+ // South Yorkshire ICB
85+ { code : "RHQ" , name : "Sheffield Teaching Hospitals NHS Foundation Trust" , icb : "QF7" } ,
86+ { code : "RFF" , name : "Doncaster and Bassetlaw Teaching Hospitals NHS Foundation Trust" , icb : "QF7" } ,
87+ // Greater Manchester ICB
88+ { code : "R0A" , name : "Manchester University NHS Foundation Trust" , icb : "QYG" } ,
89+ { code : "RW6" , name : "Salford Royal NHS Foundation Trust" , icb : "QYG" } ,
90+ // Cheshire and Merseyside ICB
91+ { code : "REM" , name : "Liverpool University Hospitals NHS Foundation Trust" , icb : "QE1" } ,
92+ { code : "RBN" , name : "Wirral University Teaching Hospital NHS Foundation Trust" , icb : "QE1" } ,
93+ // Surrey Heartlands ICB
94+ { code : "RVR" , name : "Epsom and St Helier University Hospitals NHS Trust" , icb : "QU9" } ,
95+ { code : "RDU" , name : "Royal Surrey NHS Foundation Trust" , icb : "QU9" } ,
96+ // Kent and Medway ICB
97+ { code : "RN5" , name : "Maidstone and Tunbridge Wells NHS Trust" , icb : "QNX" } ,
98+ { code : "RWF" , name : "Medway NHS Foundation Trust" , icb : "QNX" } ,
99+ // Devon ICB
100+ { code : "RK9" , name : "Royal Devon University Healthcare NHS Foundation Trust" , icb : "QSL" } ,
101+ { code : "RH8" , name : "University Hospitals Plymouth NHS Trust" , icb : "QSL" } ,
102+ // Bristol, North Somerset and South Gloucestershire ICB
103+ { code : "RA7" , name : "University Hospitals Bristol and Weston NHS Foundation Trust" , icb : "QJ2" } ,
104+ { code : "RVJ" , name : "North Bristol NHS Trust" , icb : "QJ2" } ,
105+ // Bedfordshire, Luton and Milton Keynes ICB
106+ { code : "RD8" , name : "Bedfordshire Hospitals NHS Foundation Trust" , icb : "QJG" } ,
107+ { code : "RM3" , name : "Milton Keynes University Hospital NHS Foundation Trust" , icb : "QJG" } ,
108+ // Hertfordshire and West Essex ICB
109+ { code : "RWH" , name : "East and North Hertfordshire NHS Trust" , icb : "QH8" } ,
110+ { code : "RDD" , name : "Princess Alexandra Hospital NHS Trust" , icb : "QH8" } ,
111+ ] ;
112+
67113// Generate months (18 months from Mar 2024 to Aug 2025)
68114const genMonths = ( startYear : number , startMonth1Based : number , count : number ) : string [ ] => {
69115 const out : string [ ] = [ ] ;
@@ -126,6 +172,12 @@ const generateICBValue = (regionalValue: number): number => {
126172 return regionalValue * ( 1 + variation ) ;
127173} ;
128174
175+ // Generate Provider variation (Providers typically ±15-30% from their ICB)
176+ const generateProviderValue = ( icbValue : number ) : number => {
177+ const variation = ( seededRandom ( ) - 0.5 ) * 0.5 ; // ±25%
178+ return icbValue * ( 1 + variation ) ;
179+ } ;
180+
129181/**
130182 * Calculate latest data date based on reporting cadence
131183 * As of November 2025, most recent data available
@@ -255,6 +307,33 @@ export const generatePODMetrics = (): PODMetric[] => {
255307 } ) ;
256308 } ) ;
257309
310+ // Providers
311+ PROVIDERS . forEach ( provider => {
312+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "A&E 4 hour performance" ) ;
313+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 72 ) ;
314+ const providerValues = generateTimeSeries ( providerBase , "improving" , 0.06 ) ;
315+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
316+
317+ metrics . push ( {
318+ id : `ae-4hr-${ provider . code } ` ,
319+ level : "Provider" ,
320+ orgCode : provider . code ,
321+ orgName : provider . name ,
322+ domain : "Access" ,
323+ subdomain : "Urgent and emergency care" ,
324+ metricName : "A&E 4 hour performance" ,
325+ polarity : MetricPolarity . HigherIsBetter ,
326+ improvementDirection : ImprovementDirection . Up ,
327+ unit : "%" ,
328+ target : 76 ,
329+ reportingCadence : ae4hrCadence ,
330+ latestDataDate : ae4hrLatestDate ,
331+ dates : MONTHS_18 ,
332+ values : providerValues ,
333+ onTarget : calculateRAGStatus ( providerLatest , 76 , MetricPolarity . HigherIsBetter ) ,
334+ } ) ;
335+ } ) ;
336+
258337 // 2. Cancer 62-day Combined Standard
259338 const cancer62National = generateTimeSeries ( 68.5 , "improving" , 0.025 ) ;
260339 const cancer62Cadence : ReportingCadence = "Monthly" ;
@@ -331,6 +410,33 @@ export const generatePODMetrics = (): PODMetric[] => {
331410 } ) ;
332411 } ) ;
333412
413+ // Providers
414+ PROVIDERS . forEach ( provider => {
415+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "62-day Combined Standard" ) ;
416+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 68 ) ;
417+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.05 ) ;
418+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
419+
420+ metrics . push ( {
421+ id : `cancer-62day-${ provider . code } ` ,
422+ level : "Provider" ,
423+ orgCode : provider . code ,
424+ orgName : provider . name ,
425+ domain : "Quality" ,
426+ subdomain : "Cancer" ,
427+ metricName : "62-day Combined Standard" ,
428+ polarity : MetricPolarity . HigherIsBetter ,
429+ improvementDirection : ImprovementDirection . Up ,
430+ unit : "%" ,
431+ target : 75 ,
432+ reportingCadence : cancer62Cadence ,
433+ latestDataDate : cancer62LatestDate ,
434+ dates : MONTHS_18 ,
435+ values : providerValues ,
436+ onTarget : calculateRAGStatus ( providerLatest , 75 , MetricPolarity . HigherIsBetter ) ,
437+ } ) ;
438+ } ) ;
439+
334440 // 3. RTT 18 weeks performance
335441 const rtt18National = generateTimeSeries ( 62.3 , "deteriorating" , 0.02 ) ;
336442 const rtt18Cadence : ReportingCadence = "Monthly" ;
@@ -407,6 +513,33 @@ export const generatePODMetrics = (): PODMetric[] => {
407513 } ) ;
408514 } ) ;
409515
516+ // Providers
517+ PROVIDERS . forEach ( provider => {
518+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "RTT 18 weeks performance" ) ;
519+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 62 ) ;
520+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.04 ) ;
521+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
522+
523+ metrics . push ( {
524+ id : `rtt-18weeks-${ provider . code } ` ,
525+ level : "Provider" ,
526+ orgCode : provider . code ,
527+ orgName : provider . name ,
528+ domain : "Access" ,
529+ subdomain : "Elective care" ,
530+ metricName : "RTT 18 weeks performance" ,
531+ polarity : MetricPolarity . HigherIsBetter ,
532+ improvementDirection : ImprovementDirection . Up ,
533+ unit : "%" ,
534+ target : 92 ,
535+ reportingCadence : rtt18Cadence ,
536+ latestDataDate : rtt18LatestDate ,
537+ dates : MONTHS_18 ,
538+ values : providerValues ,
539+ onTarget : calculateRAGStatus ( providerLatest , 92 , MetricPolarity . HigherIsBetter ) ,
540+ } ) ;
541+ } ) ;
542+
410543 // 4. Ambulance Cat 2 mean response (minutes)
411544 const cat2National = generateTimeSeries ( 38.5 , "improving" , 0.04 ) ;
412545 const cat2Cadence : ReportingCadence = "Monthly" ;
@@ -483,6 +616,33 @@ export const generatePODMetrics = (): PODMetric[] => {
483616 } ) ;
484617 } ) ;
485618
619+ // Providers
620+ PROVIDERS . forEach ( provider => {
621+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "Cat 2 mean response" ) ;
622+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 38 ) ;
623+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.07 ) ;
624+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
625+
626+ metrics . push ( {
627+ id : `cat2-response-${ provider . code } ` ,
628+ level : "Provider" ,
629+ orgCode : provider . code ,
630+ orgName : provider . name ,
631+ domain : "Access" ,
632+ subdomain : "Urgent and emergency care" ,
633+ metricName : "Cat 2 mean response" ,
634+ polarity : MetricPolarity . LowerIsBetter ,
635+ improvementDirection : ImprovementDirection . Down ,
636+ unit : "minutes" ,
637+ target : 18 ,
638+ reportingCadence : cat2Cadence ,
639+ latestDataDate : cat2LatestDate ,
640+ dates : MONTHS_18 ,
641+ values : providerValues ,
642+ onTarget : calculateRAGStatus ( providerLatest , 18 , MetricPolarity . LowerIsBetter ) ,
643+ } ) ;
644+ } ) ;
645+
486646 // 5. % waiting over 12 hours (type 1 and 2)
487647 const ae12hrNational = generateTimeSeries ( 2.8 , "improving" , 0.1 ) ;
488648 const ae12hrCadence : ReportingCadence = "Monthly" ;
@@ -554,7 +714,33 @@ export const generatePODMetrics = (): PODMetric[] => {
554714 values : icbValues ,
555715 onTarget : "Not Set" ,
556716 } ) ;
557- } ) ; // 6. Average discharge delay (days)
717+ } ) ;
718+
719+ // Providers
720+ PROVIDERS . forEach ( provider => {
721+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "% waiting over 12 hours from arrival (type 1 and 2)" ) ;
722+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 2.8 ) ;
723+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.16 ) ;
724+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
725+
726+ metrics . push ( {
727+ id : `ae-12hr-${ provider . code } ` ,
728+ level : "Provider" ,
729+ orgCode : provider . code ,
730+ orgName : provider . name ,
731+ domain : "Access" ,
732+ subdomain : "Urgent and emergency care" ,
733+ metricName : "% waiting over 12 hours from arrival (type 1 and 2)" ,
734+ polarity : MetricPolarity . LowerIsBetter ,
735+ improvementDirection : ImprovementDirection . Down ,
736+ unit : "%" ,
737+ reportingCadence : ae12hrCadence ,
738+ latestDataDate : ae12hrLatestDate ,
739+ dates : MONTHS_18 ,
740+ values : providerValues ,
741+ onTarget : "Not Set" ,
742+ } ) ;
743+ } ) ; // 6. Average discharge delay (days)
558744 const dischargeDelayNational = generateTimeSeries ( 3.2 , "improving" , 0.08 ) ;
559745 const dischargeDelayCadence : ReportingCadence = "Monthly" ;
560746 const dischargeDelayLatestDate = getLatestDataDate ( dischargeDelayCadence ) ;
@@ -625,6 +811,31 @@ export const generatePODMetrics = (): PODMetric[] => {
625811 } ) ;
626812 } ) ;
627813
814+ // Providers
815+ PROVIDERS . forEach ( provider => {
816+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "Average discharge delay, including 0 day delays (days)" ) ;
817+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 3.2 ) ;
818+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.13 ) ;
819+
820+ metrics . push ( {
821+ id : `discharge-delay-${ provider . code } ` ,
822+ level : "Provider" ,
823+ orgCode : provider . code ,
824+ orgName : provider . name ,
825+ domain : "Quality" ,
826+ subdomain : "Secondary care" ,
827+ metricName : "Average discharge delay, including 0 day delays (days)" ,
828+ polarity : MetricPolarity . LowerIsBetter ,
829+ improvementDirection : ImprovementDirection . Down ,
830+ unit : "days" ,
831+ reportingCadence : dischargeDelayCadence ,
832+ latestDataDate : dischargeDelayLatestDate ,
833+ dates : MONTHS_18 ,
834+ values : providerValues ,
835+ onTarget : "Not Set" ,
836+ } ) ;
837+ } ) ;
838+
628839 // 7. % cancers diagnosed at stage 1 or 2
629840 const earlyDiagnosisNational = generateTimeSeries ( 54.8 , "improving" , 0.02 ) ;
630841 const earlyDiagnosisCadence : ReportingCadence = "Annual" ;
@@ -701,6 +912,33 @@ export const generatePODMetrics = (): PODMetric[] => {
701912 } ) ;
702913 } ) ;
703914
915+ // Providers
916+ PROVIDERS . forEach ( provider => {
917+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "% cancers diagnosed at stage 1 or 2" ) ;
918+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 54 ) ;
919+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.04 ) ;
920+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
921+
922+ metrics . push ( {
923+ id : `cancer-early-diagnosis-${ provider . code } ` ,
924+ level : "Provider" ,
925+ orgCode : provider . code ,
926+ orgName : provider . name ,
927+ domain : "Quality" ,
928+ subdomain : "Cancer" ,
929+ metricName : "% cancers diagnosed at stage 1 or 2" ,
930+ polarity : MetricPolarity . HigherIsBetter ,
931+ improvementDirection : ImprovementDirection . Up ,
932+ unit : "%" ,
933+ target : 75 ,
934+ reportingCadence : earlyDiagnosisCadence ,
935+ latestDataDate : earlyDiagnosisLatestDate ,
936+ dates : MONTHS_18 ,
937+ values : providerValues ,
938+ onTarget : calculateRAGStatus ( providerLatest , 75 , MetricPolarity . HigherIsBetter ) ,
939+ } ) ;
940+ } ) ;
941+
704942 // 8. Diagnostic waiting over 6 weeks
705943 const diagnosticNational = generateTimeSeries ( 22.5 , "deteriorating" , 0.06 ) ;
706944 const diagnosticCadence : ReportingCadence = "Monthly" ;
@@ -747,7 +985,9 @@ export const generatePODMetrics = (): PODMetric[] => {
747985 values : regionalValues ,
748986 onTarget : "Not Set" ,
749987 } ) ;
750- } ) ; ICBS . forEach ( icb => {
988+ } ) ;
989+
990+ ICBS . forEach ( icb => {
751991 const regionalMetric = metrics . find ( m => m . orgCode === icb . region && m . metricName === "Percentage of people waiting over 6 weeks for a diagnostic procedure or test" ) ;
752992 const icbBase = generateICBValue ( regionalMetric ?. values [ 0 ] || 22 ) ;
753993 const icbValues = generateTimeSeries ( icbBase , "stable" , 0.08 ) ;
@@ -772,6 +1012,32 @@ export const generatePODMetrics = (): PODMetric[] => {
7721012 } ) ;
7731013 } ) ;
7741014
1015+ // Providers
1016+ PROVIDERS . forEach ( provider => {
1017+ const icbMetric = metrics . find ( m => m . orgCode === provider . icb && m . metricName === "Percentage of people waiting over 6 weeks for a diagnostic procedure or test" ) ;
1018+ const providerBase = generateProviderValue ( icbMetric ?. values [ 0 ] || 22 ) ;
1019+ const providerValues = generateTimeSeries ( providerBase , "stable" , 0.09 ) ;
1020+ const providerLatest = providerValues [ providerValues . length - 1 ] ;
1021+
1022+ metrics . push ( {
1023+ id : `diagnostic-6weeks-${ provider . code } ` ,
1024+ level : "Provider" ,
1025+ orgCode : provider . code ,
1026+ orgName : provider . name ,
1027+ domain : "Access" ,
1028+ subdomain : "Primary prevention" ,
1029+ metricName : "Percentage of people waiting over 6 weeks for a diagnostic procedure or test" ,
1030+ polarity : MetricPolarity . LowerIsBetter ,
1031+ improvementDirection : ImprovementDirection . Down ,
1032+ unit : "%" ,
1033+ reportingCadence : diagnosticCadence ,
1034+ latestDataDate : diagnosticLatestDate ,
1035+ dates : MONTHS_18 ,
1036+ values : providerValues ,
1037+ onTarget : "Not Set" ,
1038+ } ) ;
1039+ } ) ;
1040+
7751041 return metrics ;
7761042} ;
7771043export default generatePODMetrics ;
0 commit comments