Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions app/assets/javascript/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ document.addEventListener('DOMContentLoaded', () => {
// Handle reset data in background
setupResetSessionLink()

// Auto-save breast density factors when changed
setupBreastDensityFactorsAutosave()

// Reading workflow: auto-dismiss the opinion banner after a delay
const opinionBanner = document.querySelector('[data-reading-opinion-banner]')
if (opinionBanner) {
Expand Down Expand Up @@ -185,6 +188,101 @@ document.addEventListener('DOMContentLoaded', () => {
}
})

// Breast density factors are edited in place rather than on their own page,
// so there's no submit button to save them - each change posts on its own.
function setupBreastDensityFactorsAutosave() {
const container = document.querySelector('[data-breast-density-factors-save-url]')
if (!container) {
return
}

const saveUrl = container.dataset.breastDensityFactorsSaveUrl
if (!saveUrl) {
return
}

const factorsName = 'breastDensityFactors'
const hrtName = 'breastDensityFactorsHrt'

const checkboxes = container.querySelectorAll(`input[name="${factorsName}"]`)
const hrtRadios = container.querySelectorAll(`input[name="${hrtName}"]`)

if (checkboxes.length === 0 && hrtRadios.length === 0) {
return
}

// Keep the expander's "n factors added" line in step with the inputs.
// Only the review page wraps these in an expander, so this does nothing
// elsewhere.
const updateContentsSummary = () => {
const summary = container
.closest('.js-expandable-section')
?.querySelector('.app-details__contents-summary')

if (!summary) {
return
}

// "No" to HRT is an answer, not a factor - match the count in
// getBreastDensityFactors so the two never disagree
const checkedFactors = container.querySelectorAll(
`input[name="${factorsName}"]:checked`
).length
const hrtYes = container.querySelector(`input[name="${hrtName}"]:checked`)?.value === 'yes'
const count = checkedFactors + (hrtYes ? 1 : 0)

if (count === 0) {
summary.textContent = 'No breast density factors added'
} else if (count === 1) {
summary.textContent = '1 breast density factor added'
} else {
summary.textContent = `${count} breast density factors added`
}
}

// Changes can land faster than the requests complete, so keep them in a
// queue - otherwise an earlier response could be the last one to arrive
let pendingSave = Promise.resolve()

const saveFactors = () => {
const formData = new URLSearchParams()

container
.querySelectorAll(`input[name="${factorsName}"]:checked`)
.forEach((checkbox) => formData.append(factorsName, checkbox.value))

const selectedHrt = container.querySelector(`input[name="${hrtName}"]:checked`)
if (selectedHrt) {
formData.append(hrtName, selectedHrt.value)
}

updateContentsSummary()

pendingSave = pendingSave
.then(async () => {
const response = await fetch(saveUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: formData.toString()
})

if (!response.ok) {
throw new Error(
`Breast density factors auto-save failed (${response.status})`
)
}
})
.catch((error) => console.error(error))
}

container
.querySelectorAll(`input[name="${factorsName}"], input[name="${hrtName}"]`)
.forEach((input) => input.addEventListener('change', saveFactors))
}

// Quick settings modal — press backtick (`) to open settings in a modal overlay.
// On close, the page reloads to pick up any changes.
document.addEventListener('keydown', (e) => {
Expand Down
25 changes: 7 additions & 18 deletions app/lib/generators/medical-information-generator.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// app/lib/generators/medical-information-generator.js

const { generateSymptoms } = require('./medical-information/symptoms-generator')
const { generateHRT } = require('./medical-information/hrt-generator')
const {
generatePregnancyAndBreastfeeding
} = require('./medical-information/pregnancy-and-breastfeeding-generator')
generateBreastDensityFactors
} = require('./medical-information/breast-density-factors-generator')
const {
generateOtherMedicalInformation
} = require('./medical-information/other-medical-information-generator')
Expand Down Expand Up @@ -59,23 +58,13 @@ const generateMedicalInformation = (options = {}) => {
medicalInfo.symptoms = symptoms
}

// Generate HRT information
const hrt = generateHRT({
probability: probabilityOfHRT
// Generate breast density factors (HRT, pregnancy, breastfeeding)
const breastDensityFactors = generateBreastDensityFactors({
probabilityOfHrt: probabilityOfHRT,
probabilityOfPregnancyBreastfeeding
})

if (hrt) {
medicalInfo.hrt = hrt
}

// Generate pregnancy and breastfeeding information
const pregnancyAndBreastfeeding = generatePregnancyAndBreastfeeding({
probability: probabilityOfPregnancyBreastfeeding
})

if (pregnancyAndBreastfeeding) {
medicalInfo.pregnancyAndBreastfeeding = pregnancyAndBreastfeeding
}
Object.assign(medicalInfo, breastDensityFactors)

// Generate other medical information
const otherMedicalInformation = generateOtherMedicalInformation({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// app/lib/generators/medical-information/breast-density-factors-generator.js

/**
* Generate breast density factors
*
* Replaces the old separate HRT and pregnancy/breastfeeding generators. The
* question is now a simple yes/no for HRT plus a checkbox group for pregnancy
* and breastfeeding, so the generated data is just as simple.
*
* Pregnancy and breastfeeding are rare in the screening cohort (routine
* screening starts at 50), so they stay well below the HRT rate.
*
* @param {object} [options] - Generation options
* @param {number} [options.probabilityOfHrt] - Chance of currently taking HRT (0-1)
* @param {number} [options.probabilityOfPregnancyBreastfeeding] - Chance of being pregnant or breastfeeding (0-1)
* @param {number} [options.probabilityOfBeingAsked=0.8] - Chance the question was asked at all
* @returns {object} Object with breastDensityFactors and breastDensityFactorsHrt, either may be absent
*/
const generateBreastDensityFactors = (options = {}) => {
const {
probabilityOfHrt = 0.3,
probabilityOfPregnancyBreastfeeding = 0.05,
probabilityOfBeingAsked = 0.8
} = options

const result = {}

// Not every appointment has got as far as asking. Leaving the answer unset
// keeps "no medical information" a possible outcome, and lets the summaries
// distinguish "not answered" from a recorded "no"
if (Math.random() < probabilityOfBeingAsked) {
result.breastDensityFactorsHrt =
Math.random() < probabilityOfHrt ? 'yes' : 'no'
}

const factors = []

if (Math.random() < probabilityOfPregnancyBreastfeeding) {
// Breastfeeding is the more likely of the two at screening age
if (Math.random() < 0.7) {
factors.push('breastfeeding')
} else {
factors.push('pregnant')
}
}

if (factors.length > 0) {
result.breastDensityFactors = factors
}

return result
}

module.exports = {
generateBreastDensityFactors
}
63 changes: 0 additions & 63 deletions app/lib/generators/medical-information/hrt-generator.js

This file was deleted.

This file was deleted.

Loading