Skip to content

Commit 39c1e6c

Browse files
Lots of updates
1 parent 7d1317f commit 39c1e6c

27 files changed

+826
-233
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
/dist
55
/tmp
66
/public
7-
/app/data/generated/
7+
app/data/generated/
8+
reference
89

910
# Runtime data
1011
pids

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ The prototype will generate example data on first run.
3333
- Delete the generated folder to regenerate fresh example data
3434
- You can also run the generator directly with `node app/lib/generate-seed-data.js`
3535
- Uses NHS.UK design system components and patterns
36+
- Use `tree app` to generate a tree diagram of the project
3637

3738
## Security
3839

app.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const express = require('express');
1010
const nunjucks = require('nunjucks');
1111
const sessionInCookie = require('client-sessions');
1212
const sessionInMemory = require('express-session');
13+
const flash = require('connect-flash');
1314

1415
// Run before other code to make sure variables from .env are available
1516
dotenv.config();
@@ -111,6 +112,9 @@ if (useAutoStoreData === 'true') {
111112
utils.addCheckedFunction(nunjucksAppEnv);
112113
}
113114

115+
// Flash messages
116+
app.use(flash());
117+
114118
// Warn if node_modules folder doesn't exist
115119
function checkFiles() {
116120
const nodeModulesExists = fs.existsSync(path.join(__dirname, '/node_modules'));
@@ -148,6 +152,8 @@ if (!sessionDataDefaultsFileExists) {
148152
// Local variables
149153
app.use(locals(config));
150154

155+
156+
151157
// View engine
152158
app.set('view engine', 'html');
153159
documentationApp.set('view engine', 'html');

app/data/breast-screening-units.js

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,61 @@
1+
// app/data/breast-screening-units.js
2+
const generateId = require('../lib/utils/id-generator');
3+
14
module.exports = [
25
{
3-
id: "f66f2a7d-99a8-4793-8371-3d075e1a7c54",
6+
id: "m5ekcxvu", // Must be hardcoded so it matches generated data
47
name: "Oxford Breast Imaging Centre",
5-
address: `Surgery and Diagnostics Centre
6-
Churchill Hospital
7-
Old Road,
8-
Headington
9-
Oxford
10-
OX3 7LE`,
11-
phoneNumber: "01865 235621",
12-
abbreviation: "OXF"
8+
address: {
9+
line1: "Surgery and Diagnostics Centre",
10+
line2: "Churchill Hospital",
11+
line3: "Old Road",
12+
line4: "Headington",
13+
city: "Oxford",
14+
postcode: "OX3 7LE"
15+
},
16+
phoneNumber: "01865235621",
17+
abbreviation: "OXF",
18+
locations: [
19+
{
20+
id: generateId(),
21+
name: "Churchill Hospital breast unit",
22+
type: "hospital",
23+
isMainSite: true,
24+
address: {
25+
line1: "Surgery and Diagnostics Centre",
26+
line2: "Churchill Hospital",
27+
line3: "Old Road",
28+
line4: "Headington",
29+
city: "Oxford",
30+
postcode: "OX3 7LE"
31+
}
32+
},
33+
// {
34+
// id: generateId(),
35+
// name: "Horton Hospital breast unit",
36+
// type: "hospital",
37+
// isMainSite: false,
38+
// address: {
39+
// line1: "Horton General Hospital",
40+
// line2: "Oxford Road",
41+
// city: "Banbury",
42+
// postcode: "OX16 9AL"
43+
// }
44+
// },
45+
{
46+
id: generateId(),
47+
name: "Mobile Unit WX71 HCP",
48+
type: "mobile_unit",
49+
isMainSite: false,
50+
registration: "WX71 HCP"
51+
},
52+
{
53+
id: generateId(),
54+
name: "Mobile Unit WX71 HCR",
55+
type: "mobile_unit",
56+
isMainSite: false,
57+
registration: "WX71 HCR"
58+
}
59+
]
1360
}
14-
]
61+
];

app/data/session-data-defaults.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,48 @@ const breastScreeningUnits = require("./breast-screening-units");
55
const ethnicities = require("./ethnicities");
66
const path = require('path');
77
const fs = require('fs');
8+
const dayjs = require('dayjs');
89

9-
// Check if generated data folder exists
10+
// Check if generated data folder exists and create if needed
1011
const generatedDataPath = path.join(__dirname, 'generated');
12+
if (!fs.existsSync(generatedDataPath)) {
13+
fs.mkdirSync(generatedDataPath);
14+
}
1115

1216
let participants = [];
1317
let clinics = [];
1418
let events = [];
19+
let generationInfo = {
20+
generatedAt: 'Never',
21+
stats: { participants: 0, clinics: 0, events: 0 }
22+
};
1523

16-
// Generate data if folder doesn't exist
17-
if (!fs.existsSync(generatedDataPath)) {
18-
console.log('Generating seed data...');
24+
// Check if we need to regenerate data
25+
const generationInfoPath = path.join(generatedDataPath, 'generation-info.json');
26+
let needsRegeneration = true;
27+
28+
if (fs.existsSync(generationInfoPath)) {
29+
try {
30+
generationInfo = JSON.parse(fs.readFileSync(generationInfoPath));
31+
const generatedDate = dayjs(generationInfo.generatedAt).startOf('day');
32+
const today = dayjs().startOf('day');
33+
needsRegeneration = !generatedDate.isSame(today, 'day');
34+
} catch (err) {
35+
console.warn('Error reading generation info:', err);
36+
needsRegeneration = true;
37+
}
38+
}
39+
40+
// Generate or load data
41+
if (needsRegeneration) {
42+
console.log('Generating new seed data...');
1943
require('../lib/generate-seed-data.js');
44+
45+
// Save generation info
46+
fs.writeFileSync(
47+
generationInfoPath,
48+
JSON.stringify({ generatedAt: new Date().toISOString() })
49+
);
2050
}
2151

2252
// Load generated data
@@ -34,5 +64,6 @@ module.exports = {
3464
breastScreeningUnits,
3565
participants,
3666
clinics,
37-
events
67+
events,
68+
generationInfo
3869
};

app/lib/generate-seed-data.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ const generateData = async () => {
9494
writeData('participants.json', { participants });
9595
writeData('clinics.json', { clinics });
9696
writeData('events.json', { events });
97+
writeData('generation-info.json', {
98+
generatedAt: new Date().toISOString(),
99+
stats: {
100+
participants: participants.length,
101+
clinics: clinics.length,
102+
events: events.length
103+
}
104+
});
97105

98106
console.log('\nData generation complete!');
99107
console.log(`Generated:`);
@@ -102,5 +110,10 @@ const generateData = async () => {
102110
console.log(`- ${events.length} events`);
103111
};
104112

105-
// Run the generator
106-
generateData().catch(console.error);
113+
// Export the function instead of running it immediately
114+
module.exports = generateData;
115+
116+
// Only run if this file is being run directly
117+
if (require.main === module) {
118+
generateData().catch(console.error);
119+
}

app/lib/generators/clinic-generator.js

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22

33
const { faker } = require('@faker-js/faker');
44
const generateId = require('../utils/id-generator');
5-
const weighted = require('weighted');
6-
7-
const CLINIC_TYPES = [
8-
{ type: 'hospital', weight: 0.7 },
9-
{ type: 'mobile_unit', weight: 0.3 }
10-
];
5+
const dayjs = require('dayjs');
116

127
const generateTimeSlots = (date, config) => {
138
const slots = [];
@@ -29,29 +24,66 @@ const generateTimeSlots = (date, config) => {
2924
return slots;
3025
};
3126

27+
const determineClinicStatus = (date) => {
28+
const now = dayjs();
29+
const clinicDate = dayjs(date);
30+
const clinicStart = clinicDate.hour(8); // Assume clinic starts at 8am
31+
const clinicEnd = clinicDate.hour(17); // Assume clinic ends at 5pm
32+
33+
if (clinicDate.isBefore(now, 'day')) {
34+
return 'closed';
35+
} else if (clinicDate.isAfter(now, 'day')) {
36+
return 'scheduled';
37+
} else {
38+
// Today - check time
39+
if (now.isBefore(clinicStart)) {
40+
return 'scheduled';
41+
} else if (now.isAfter(clinicEnd)) {
42+
return 'closed';
43+
} else {
44+
return 'in_progress';
45+
}
46+
}
47+
};
48+
49+
const generateMobileSiteName = () => {
50+
const sites = [
51+
"Tesco Extra Banbury",
52+
"Witney Community Hospital",
53+
"Thame Community Hospital",
54+
"Bicester Community Hospital",
55+
"Sainsbury's Kidlington",
56+
"Carterton Health Centre",
57+
"Wantage Community Hospital",
58+
"Tesco Faringdon",
59+
"Didcot Civic Hall",
60+
"Chipping Norton Health Centre"
61+
];
62+
63+
return faker.helpers.arrayElement(sites);
64+
};
65+
3266
// Generate multiple clinics for a BSU on a given day
3367
const generateClinicsForBSU = ({ date, breastScreeningUnit, config }) => {
3468
// Determine number of clinics for this BSU today (1-2)
3569
const numberOfClinics = Math.random() < 0.3 ? 2 : 1;
3670

37-
return Array.from({ length: numberOfClinics }, () => {
38-
// If this is the second clinic for the day, make it more likely to be a mobile unit
39-
const isSecondClinic = numberOfClinics === 2;
40-
const clinicType = weighted.select(
41-
CLINIC_TYPES.map(t => t.type),
42-
CLINIC_TYPES.map(t => isSecondClinic ? (t.type === 'mobile_unit' ? 0.7 : 0.3) : t.weight)
43-
);
44-
71+
// Randomly select locations from available ones
72+
const selectedLocations = faker.helpers.arrayElements(
73+
breastScreeningUnit.locations,
74+
{ min: numberOfClinics, max: numberOfClinics }
75+
);
76+
77+
return selectedLocations.map(location => {
4578
return {
4679
id: generateId(),
4780
date: date.toISOString().split('T')[0],
4881
breastScreeningUnitId: breastScreeningUnit.id,
49-
clinicType,
50-
location: clinicType === 'hospital' ?
51-
breastScreeningUnit.address :
52-
generateMobileLocation(breastScreeningUnit),
82+
clinicType: location.type,
83+
locationId: location.id,
84+
siteName: location.type === 'mobile_unit' ? generateMobileSiteName() : null,
5385
slots: generateTimeSlots(date, config),
54-
status: date < new Date() ? 'completed' : 'scheduled',
86+
status: determineClinicStatus(date),
5587
staffing: {
5688
mamographers: [],
5789
radiologists: [],
@@ -64,26 +96,6 @@ const generateClinicsForBSU = ({ date, breastScreeningUnit, config }) => {
6496
});
6597
};
6698

67-
const generateMobileLocation = (bsu) => {
68-
const locations = [
69-
'Community Centre',
70-
'Health Centre',
71-
'Leisure Centre',
72-
'Shopping Centre Car Park',
73-
'Supermarket Car Park'
74-
];
75-
76-
const location = faker.helpers.arrayElement(locations);
77-
return {
78-
name: `${faker.location.city()} ${location}`,
79-
address: {
80-
line1: faker.location.streetAddress(),
81-
city: faker.location.city(),
82-
postcode: faker.location.zipCode('??# #??')
83-
}
84-
};
85-
};
86-
8799
module.exports = {
88100
generateClinicsForBSU
89101
};

0 commit comments

Comments
 (0)