Skip to content

Commit

Permalink
Add generators and filters
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardhorsford committed Nov 12, 2024
1 parent 97cd5a4 commit a5049de
Show file tree
Hide file tree
Showing 16 changed files with 991 additions and 40 deletions.
10 changes: 9 additions & 1 deletion app/data/breast-screening-units.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
module.exports = [
{
id: "f66f2a7d-99a8-4793-8371-3d075e1a7c54",
name: "Oxford Breast screening unit",
name: "Oxford Breast Imaging Centre",
address: `Surgery and Diagnostics Centre
Churchill Hospital
Old Road,
Headington
Oxford
OX3 7LE`,
phoneNumber: "01865 235621",
abbreviation: "OXF"
}
]
30 changes: 30 additions & 0 deletions app/data/ethnicities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
"Asian or Asian British": [
"Bangladeshi",
"Chinese",
"Indian",
"Pakistani",
"Another Asian background"
],
"Black, African, Black British or Caribbean": [
"African",
"Caribbean",
"Another black background"
],
"Mixed or multiple ethnic groups": [
"Asian and White",
"Black African and White",
"Black Caribbean and White",
"Another mixed background",
],
"White": [
"British, English, Northern Irish, Scottish, or Welsh",
"Irish",
"Irish Traveller or Gypsy",
"Another White background"
],
"Another ethnic group": [
"Arab",
"Another ethnic background"
]
}
25 changes: 21 additions & 4 deletions app/data/session-data-defaults.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
const users = require("./users")
const breastScreeningUnits = require("./breast-screening-units.js")
// app/data/session-data-defaults.js

const users = require("./users");
const breastScreeningUnits = require("./breast-screening-units");
const ethnicities = require("./ethnicities");

// Load generated data
let participants = [];
let clinics = [];
let events = [];

try {
participants = require("./generated/participants.json").participants;
clinics = require("./generated/clinics.json").clinics;
events = require("./generated/events.json").events;
} catch (err) {
console.warn('Generated data files not found. Please run the data generator first.');
}

module.exports = {
users,
currentUser: users[0],
breastScreeningUnits
}
breastScreeningUnits,
participants,
clinics,
events
};
3 changes: 2 additions & 1 deletion app/data/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = [
firstName: "Jane",
lastName: "Hitchin",
role: "mamographer",
id: "ae7537b3-aed1-4620-87fd-9dc5b5bdc8cb"
id: "ae7537b3-aed1-4620-87fd-9dc5b5bdc8cb",
breastScreeningUnit: "f66f2a7d-99a8-4793-8371-3d075e1a7c54"
}
]
60 changes: 28 additions & 32 deletions app/filters.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const fs = require('fs');
const path = require('path');

module.exports = function (env) { /* eslint-disable-line func-names,no-unused-vars */
/**
* Instantiate object used to store the methods registered as a
Expand All @@ -7,39 +10,32 @@ module.exports = function (env) { /* eslint-disable-line func-names,no-unused-va
*/
const filters = {};

/* ------------------------------------------------------------------
add your methods to the filters obj below this comment block:
@example:
filters.sayHi = function(name) {
return 'Hi ' + name + '!'
}
Which in your templates would be used as:
{{ 'Paul' | sayHi }} => 'Hi Paul'
Notice the first argument of your filters method is whatever
gets 'piped' via '|' to the filter.
Filters can take additional arguments, for example:
filters.sayHi = function(name,tone) {
return (tone == 'formal' ? 'Greetings' : 'Hi') + ' ' + name + '!'
}
Which would be used like this:
{{ 'Joel' | sayHi('formal') }} => 'Greetings Joel!'
{{ 'Gemma' | sayHi }} => 'Hi Gemma!'
For more on filters and how to write them see the Nunjucks
documentation.
// Get all files from utils directory
const utilsPath = path.join(__dirname, 'lib/utils');

try {
// Read all files in the utils directory
const files = fs.readdirSync(utilsPath);

files.forEach(file => {
// Only process .js files
if (path.extname(file) === '.js') {
// Get the utils module
const utils = require(path.join(utilsPath, file));

// Add each exported function as a filter
Object.entries(utils).forEach(([name, func]) => {
// Only add if it's a function
if (typeof func === 'function') {
filters[name] = func;
}
});
}
});
} catch (err) {
console.warn('Error loading filters from utils:', err);
}

------------------------------------------------------------------ */

/* ------------------------------------------------------------------
keep the following line to return your filters to the app
------------------------------------------------------------------ */
return filters;
};
106 changes: 106 additions & 0 deletions app/lib/generate-seed-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// app/lib/generate-seed-data.js

const { faker } = require('@faker-js/faker');
const weighted = require('weighted');
const fs = require('fs');
const path = require('path');

const { generateParticipant } = require('./generators/participant-generator');
const { generateClinicsForBSU } = require('./generators/clinic-generator');
const { generateEvent } = require('./generators/event-generator');

// Load existing data
const breastScreeningUnits = require('../data/breast-screening-units');
const ethnicities = require('../data/ethnicities');

const CONFIG = {
numberOfParticipants: 1000,
outputPath: path.join(__dirname, '../data/generated'),
clinicDefaults: {
slotsPerDay: 32,
daysToGenerate: 5,
startTime: '09:00',
endTime: '17:00',
slotDurationMinutes: 8
},
eventOutcomes: {
'clear': 0.95,
'needs_further_tests': 0.04,
'cancer_detected': 0.01
}
};

const generateData = async () => {
// Create output directory if it doesn't exist
if (!fs.existsSync(CONFIG.outputPath)) {
fs.mkdirSync(CONFIG.outputPath, { recursive: true });
}

// Generate base data
console.log('Generating participants...');
const participants = Array.from({ length: CONFIG.numberOfParticipants }, () =>
generateParticipant({ ethnicities, breastScreeningUnits })
);

console.log('Generating clinics and events...');
const clinics = [];
const events = [];

// Calculate date range
const startDate = new Date();
startDate.setDate(startDate.getDate() - 3);

for (let i = 0; i < CONFIG.clinicDefaults.daysToGenerate; i++) {
const clinicDate = new Date(startDate);
clinicDate.setDate(clinicDate.getDate() + i);

// Generate clinics for each BSU (currently just Oxford)
breastScreeningUnits.forEach(unit => {
const newClinics = generateClinicsForBSU({
date: clinicDate,
breastScreeningUnit: unit,
config: CONFIG.clinicDefaults
});

// Generate events for each clinic
newClinics.forEach(clinic => {
const clinicEvents = clinic.slots
.filter(slot => Math.random() > 0.2)
.map(slot => {
const participant = faker.helpers.arrayElement(participants);
return generateEvent({
slot,
participant,
clinic,
outcomeWeights: CONFIG.eventOutcomes
});
});

events.push(...clinicEvents);
});

clinics.push(...newClinics);
});
}

// Write generated data to files
const writeData = (filename, data) => {
fs.writeFileSync(
path.join(CONFIG.outputPath, filename),
JSON.stringify(data, null, 2)
);
};

writeData('participants.json', { participants });
writeData('clinics.json', { clinics });
writeData('events.json', { events });

console.log('\nData generation complete!');
console.log(`Generated:`);
console.log(`- ${participants.length} participants`);
console.log(`- ${clinics.length} clinics`);
console.log(`- ${events.length} events`);
};

// Run the generator
generateData().catch(console.error);
89 changes: 89 additions & 0 deletions app/lib/generators/clinic-generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// app/lib/generators/clinic-generator.js

const { faker } = require('@faker-js/faker');
const generateId = require('../utils/id-generator');
const weighted = require('weighted');

const CLINIC_TYPES = [
{ type: 'hospital', weight: 0.7 },
{ type: 'mobile_unit', weight: 0.3 }
];

const generateTimeSlots = (date, config) => {
const slots = [];
const startTime = new Date(`${date.toISOString().split('T')[0]}T${config.startTime}`);
const endTime = new Date(`${date.toISOString().split('T')[0]}T${config.endTime}`);

let currentTime = new Date(startTime);
while (currentTime < endTime) {
const slotId = generateId();
slots.push({
id: slotId,
dateTime: new Date(currentTime).toISOString(),
type: 'screening',
capacity: 2,
bookedCount: 0
});
currentTime.setMinutes(currentTime.getMinutes() + config.slotDurationMinutes);
}
return slots;
};

// Generate multiple clinics for a BSU on a given day
const generateClinicsForBSU = ({ date, breastScreeningUnit, config }) => {
// Determine number of clinics for this BSU today (1-2)
const numberOfClinics = Math.random() < 0.3 ? 2 : 1;

return Array.from({ length: numberOfClinics }, () => {
// If this is the second clinic for the day, make it more likely to be a mobile unit
const isSecondClinic = numberOfClinics === 2;
const clinicType = weighted.select(
CLINIC_TYPES.map(t => t.type),
CLINIC_TYPES.map(t => isSecondClinic ? (t.type === 'mobile_unit' ? 0.7 : 0.3) : t.weight)
);

return {
id: generateId(),
date: date.toISOString().split('T')[0],
breastScreeningUnitId: breastScreeningUnit.id,
clinicType,
location: clinicType === 'hospital' ?
breastScreeningUnit.address :
generateMobileLocation(breastScreeningUnit),
slots: generateTimeSlots(date, config),
status: date < new Date() ? 'completed' : 'scheduled',
staffing: {
mamographers: [],
radiologists: [],
support: []
},
targetBookings: 60,
targetAttendance: 40,
notes: null
};
});
};

const generateMobileLocation = (bsu) => {
const locations = [
'Community Centre',
'Health Centre',
'Leisure Centre',
'Shopping Centre Car Park',
'Supermarket Car Park'
];

const location = faker.helpers.arrayElement(locations);
return {
name: `${faker.location.city()} ${location}`,
address: {
line1: faker.location.streetAddress(),
city: faker.location.city(),
postcode: faker.location.zipCode('??# #??')
}
};
};

module.exports = {
generateClinicsForBSU
};
Loading

0 comments on commit a5049de

Please sign in to comment.