Skip to content

Commit

Permalink
Add first pass at pre-appointment questionnaire
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardhorsford committed Nov 19, 2024
1 parent 36cf3c6 commit d2e9eaa
Show file tree
Hide file tree
Showing 13 changed files with 1,680 additions and 51 deletions.
40 changes: 20 additions & 20 deletions app/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,30 @@ module.exports = function (env) { /* eslint-disable-line func-names,no-unused-va

// Get all files from utils directory
const utilsPath = path.join(__dirname, 'lib/utils');
const filtersPath = path.join(__dirname, 'filters');

const folderPaths = [utilsPath, filtersPath]

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;
}
});
}
folderPaths.forEach(folderPath => {
const files = fs.readdirSync(folderPath);

files.forEach(file => {
if (path.extname(file) === '.js') {
const module = require(path.join(folderPath, file));

Object.entries(module).forEach(([name, func]) => {
if (typeof func === 'function') {
filters[name] = func;
}
});
}
});
});
} catch (err) {
console.warn('Error loading filters from utils:', err);
}
catch (err) {
console.warn('Error loading filters:', err);
}


return filters;
};
35 changes: 35 additions & 0 deletions app/filters/formatting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Format a yes/no/not answered response with optional additional details
* @param {string|boolean} value - The response value to format
* @param {Object} [options] - Formatting options
* @param {string} [options.yesValue] - Additional details to show after "Yes -" for positive responses
* @param {string} [options.noText="No"] - Text to show for negative responses
* @param {string} [options.notAnsweredText="Not answered"] - Text to show when no response given
* @param {string} [options.yesPrefix="Yes"] - Text to show for positive responses (before any yesValue)
* @returns {string} Formatted response text
* @example
* formatAnswer("yes", { yesValue: "Details here" }) // Returns "Yes - Details here"
* formatAnswer("no") // Returns "No"
* formatAnswer(null) // Returns "Not answered"
* formatAnswer("yes", { yesPrefix: "Currently" }) // Returns "Currently"
*/
const formatAnswer = (value, options = {}) => {
const {
yesValue = null,
noText = "No",
notAnsweredText = "Not answered",
yesPrefix = "Yes"
} = options;

// Handle null/undefined/empty string
if (!value || value === "no" || value === "false") {
return noText;
}

// For any truthy value (includes "yes", true, etc)
return yesValue ? `${yesPrefix} - ${yesValue}` : yesPrefix;
};

module.exports = {
formatAnswer
};
168 changes: 150 additions & 18 deletions app/lib/generators/participant-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,103 @@ const generateNHSNumber = () => {
return `${baseNumber}${finalCheckDigit}`;
};

// New medical history generators
const generateMedicalHistorySurvey = () => {
// 50% chance of having completed the survey
if (Math.random() > 0.5) {
return null;
}

return {
completedAt: faker.date.past({ years: 1 }).toISOString(),

// Non-cancerous procedures/diagnoses
nonCancerousProcedures: generateNonCancerousProcedures(),

// Current hormone therapy (if they had previous cancer)
onHormoneTherapy: Math.random() < 0.3, // 30% of those with cancer history

// Other medical history
otherMedicalHistory: Math.random() < 0.3 ?
faker.helpers.arrayElements([
"Type 2 diabetes - diet controlled",
"High blood pressure - medication",
"Osteoarthritis",
"Previous shoulder surgery",
"Rheumatoid arthritis"
], { min: 1, max: 2 }) :
null
};
};

const generateNonCancerousProcedures = () => {
const procedures = [];

const possibleProcedures = {
'benign_lump': {
probability: 0.15,
details: () => ({
dateDiscovered: faker.date.past({ years: 5 }).toISOString(),
position: faker.helpers.arrayElement(['Left breast', 'Right breast']),
wasRemoved: Math.random() < 0.7,
pathology: 'Fibroadenoma'
})
},
'cyst_aspiration': {
probability: 0.1,
details: () => ({
date: faker.date.past({ years: 3 }).toISOString(),
location: faker.helpers.arrayElement(['Left breast', 'Right breast']),
notes: 'Simple cyst, fluid aspirated'
})
},
'non_implant_augmentation': {
probability: 0.02,
details: () => ({
date: faker.date.past({ years: 5 }).toISOString(),
procedure: 'Fat transfer procedure',
hospital: 'General Hospital'
})
},
'breast_reduction': {
probability: 0.03,
details: () => ({
date: faker.date.past({ years: 5 }).toISOString(),
notes: 'Bilateral breast reduction',
hospital: 'City Hospital'
})
},
'previous_biopsy': {
probability: 0.08,
details: () => ({
date: faker.date.past({ years: 2 }).toISOString(),
result: 'Benign',
location: faker.helpers.arrayElement(['Left breast', 'Right breast'])
})
},
'skin_lesion': {
probability: 0.05,
details: () => ({
date: faker.date.past({ years: 3 }).toISOString(),
type: faker.helpers.arrayElement(['Seborrheic keratosis', 'Dermatofibroma']),
location: faker.helpers.arrayElement(['Left breast', 'Right breast'])
})
}
};

Object.entries(possibleProcedures).forEach(([type, config]) => {
if (Math.random() < config.probability) {
procedures.push({
type,
...config.details()
});
}
});

return procedures;
};


const generateParticipant = ({ ethnicities, breastScreeningUnits }) => {
const id = generateId();

Expand Down Expand Up @@ -101,7 +198,8 @@ const generateParticipant = ({ ethnicities, breastScreeningUnits }) => {
nhsNumber: generateNHSNumber(),
riskFactors: generateRiskFactors(),
familyHistory: generateFamilyHistory(),
previousCancerHistory: generatePreviousCancerHistory()
previousCancerHistory: generatePreviousCancerHistory(),
medicalHistorySurvey: generateMedicalHistorySurvey()
},
currentHealthInformation: {
isPregnant: false,
Expand All @@ -114,6 +212,31 @@ const generateParticipant = ({ ethnicities, breastScreeningUnits }) => {
};


// Modified family history generator to add more detail
const generateFamilyHistory = () => {
if (Math.random() > 0.15) return null; // 15% chance of family history

const affectedRelatives = faker.helpers.arrayElements(
[
{ relation: 'mother', age: faker.number.int({ min: 35, max: 75 }) },
{ relation: 'sister', age: faker.number.int({ min: 30, max: 70 }) },
{ relation: 'daughter', age: faker.number.int({ min: 25, max: 45 }) },
{ relation: 'grandmother', age: faker.number.int({ min: 45, max: 85 }) },
{ relation: 'aunt', age: faker.number.int({ min: 40, max: 80 }) }
],
{ min: 1, max: 3 }
);

return {
hasFirstDegreeHistory: affectedRelatives.some(r =>
['mother', 'sister', 'daughter'].includes(r.relation)
),
affectedRelatives,
additionalDetails: Math.random() < 0.3 ?
'Multiple occurrences on maternal side' : null
};
};

const generateRiskFactors = () => {
const factors = [];
const possibleFactors = {
Expand All @@ -133,34 +256,43 @@ const generateRiskFactors = () => {
return factors;
};

const generateFamilyHistory = () => {
if (Math.random() > 0.15) return null; // 15% chance of family history

return {
hasFirstDegreeHistory: Math.random() < 0.7, // 70% of those with family history
affectedRelatives: faker.helpers.arrayElements(
['mother', 'sister', 'daughter', 'grandmother', 'aunt'],
{ min: 1, max: 3 }
)
};
};

// Modified previous cancer history to include more detail
const generatePreviousCancerHistory = () => {
if (Math.random() > 0.02) return null; // 2% chance of previous cancer

const treatments = faker.helpers.arrayElements(
[
{ type: 'surgery', details: 'Wide local excision' },
{ type: 'radiotherapy', details: '15 fractions' },
{ type: 'chemotherapy', details: '6 cycles' },
{ type: 'hormone_therapy', details: '5 years tamoxifen' }
],
{ min: 1, max: 3 }
);

return {
yearDiagnosed: faker.date.past({ years: 20 }).getFullYear(),
type: faker.helpers.arrayElement([
'ductal_carcinoma_in_situ',
'invasive_ductal_carcinoma',
'invasive_lobular_carcinoma'
]),
treatment: faker.helpers.arrayElements([
'surgery',
'radiotherapy',
'chemotherapy',
'hormone_therapy'
], { min: 1, max: 3 })
position: faker.helpers.arrayElement([
'Left breast - upper outer quadrant',
'Right breast - upper outer quadrant',
'Left breast - lower inner quadrant',
'Right breast - lower inner quadrant'
]),
treatments,
hospital: faker.helpers.arrayElement([
'City General Hospital',
'Royal County Hospital',
'Memorial Cancer Centre',
'University Teaching Hospital'
]),
additionalNotes: Math.random() < 0.3 ?
'Regular follow-up completed' : null
};
};

Expand Down
Loading

0 comments on commit d2e9eaa

Please sign in to comment.