Skip to content

Commit e43759a

Browse files
Added Provider level synthetic data to the generator
1 parent 20d8634 commit e43759a

File tree

2 files changed

+273
-5
lines changed

2 files changed

+273
-5
lines changed

app/components/PODDashboard.syntheticDataGenerator.tsx

Lines changed: 270 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
1515
export type ReportingCadence = "Weekly" | "Monthly" | "Quarterly" | "Annual";
1616
export type Domain = "Access" | "Quality" | "Capacity" | "System Performance" | "Workforce";
1717
export 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)
5050
const 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)
68114
const 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
};
7771043
export default generatePODMetrics;

app/components/PODSyntheticDashboard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ const SyntheticDataPODashboard: React.FC = () => {
261261
levels: [
262262
{
263263
groupBy: 'orgName' as any,
264-
label: selectedLevel === "Region" ? "NHS Region" : "Integrated Care Board",
264+
label: selectedLevel === "Region" ? "NHS Region" : selectedLevel === "ICB" ? "Integrated Care Board" : "NHS Trust",
265265
expandedByDefault: false,
266266
},
267267
{
@@ -287,6 +287,7 @@ const SyntheticDataPODashboard: React.FC = () => {
287287
{ value: 'National' as const, label: 'National' as const },
288288
{ value: 'Region' as const, label: 'Regional' as const },
289289
{ value: 'ICB' as const, label: 'ICB' as const },
290+
{ value: 'Provider' as const, label: 'Provider' as const },
290291
],
291292
},
292293
{
@@ -327,6 +328,7 @@ const SyntheticDataPODashboard: React.FC = () => {
327328
{selectedLevel === "National" && "England - National Performance"}
328329
{selectedLevel === "Region" && "Regional Performance"}
329330
{selectedLevel === "ICB" && "Integrated Care Board Performance"}
331+
{selectedLevel === "Provider" && "NHS Trust Provider Performance"}
330332
</h1>
331333
<p style={{ marginBottom: '2rem', color: '#425563' }}>
332334
Showing {filteredMetrics.length} metric{filteredMetrics.length !== 1 ? 's' : ''}

0 commit comments

Comments
 (0)