Skip to content

Commit b530529

Browse files
Add pain as a symptom / support significant symptoms (#180)
1 parent 7ccf06a commit b530529

File tree

15 files changed

+431
-254
lines changed

15 files changed

+431
-254
lines changed

app/data/session-data-defaults.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const fs = require('fs')
1111
const { needsRegeneration } = require('../lib/utils/regenerate-data')
1212
const config = require('../config')
1313
const repeatReasons = require('./repeat-reasons')
14+
const symptomTypes = require('./symptom-types')
1415

1516
// Check if generated data folder exists and create if needed
1617
const generatedDataPath = path.join(__dirname, 'generated')
@@ -87,5 +88,6 @@ module.exports = {
8788
settings: defaultSettings,
8889
defaultSettings,
8990
medicalHistoryTypes,
90-
repeatReasons
91+
repeatReasons,
92+
symptomTypes
9193
}

app/data/symptom-types.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module.exports = [
2+
{
3+
name: 'lump',
4+
slug: 'lump',
5+
isSignificantByDefault: true
6+
},
7+
{
8+
name: 'swelling or shape change',
9+
slug: 'swelling-or-shape-change',
10+
isSignificantByDefault: true
11+
},
12+
{
13+
name: 'skin change',
14+
slug: 'skin-change',
15+
isSignificantByDefault: true
16+
},
17+
{
18+
name: 'nipple change',
19+
slug: 'nipple-change',
20+
isSignificantByDefault: true
21+
},
22+
{
23+
name: 'breast pain',
24+
slug: 'breast-pain',
25+
isSignificantByDefault: false
26+
},
27+
{
28+
name: 'other',
29+
slug: 'other',
30+
isSignificantByDefault: false
31+
}
32+
]

app/lib/generators/symptoms-generator.js

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
const { faker } = require('@faker-js/faker')
44
const weighted = require('weighted')
55
const generateId = require('../utils/id-generator')
6+
const symptomTypesData = require('../../data/symptom-types.js')
67

7-
// Updated symptom types to match the form
8-
const SYMPTOM_TYPES = {
9-
'Lump': {
10-
weight: 0.4,
8+
// Helper to convert lowercase to sentence case
9+
const toSentenceCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
10+
11+
// Generator-specific configuration (weights, descriptions, etc.)
12+
// Keys match lowercase names from symptom-types.js
13+
const GENERATOR_CONFIG = {
14+
'lump': {
15+
weight: 0.25,
1116
requiresLocation: true,
1217
descriptions: [
1318
"Hard lump that doesn't move",
@@ -17,8 +22,21 @@ const SYMPTOM_TYPES = {
1722
'Tender lump that varies with menstrual cycle'
1823
]
1924
},
20-
'Swelling or shape change': {
21-
weight: 0.2,
25+
'breast pain': {
26+
weight: 0.35, // Most common symptom
27+
requiresLocation: true,
28+
descriptions: [
29+
'Sharp pain in breast',
30+
'Dull ache that comes and goes',
31+
'Burning or stabbing sensation',
32+
'Tenderness that varies with menstrual cycle',
33+
'Constant throbbing pain',
34+
'Pain when pressure applied',
35+
'Shooting pain through breast'
36+
]
37+
},
38+
'swelling or shape change': {
39+
weight: 0.15,
2240
requiresLocation: true,
2341
descriptions: [
2442
'Change in size or shape of breast',
@@ -28,14 +46,15 @@ const SYMPTOM_TYPES = {
2846
'Visible dent or dimpling'
2947
]
3048
},
31-
'Nipple change': {
32-
weight: 0.15,
49+
'nipple change': {
50+
weight: 0.1,
3351
requiresLocation: false, // Uses nippleChangeLocation instead
3452
nippleChangeTypes: [
3553
'bloody discharge',
3654
'other discharge',
37-
'inversion or shape change',
38-
'rash',
55+
'inversion',
56+
'rash or eczema',
57+
'shape change',
3958
'colour change'
4059
],
4160
nippleChangeDescriptions: {
@@ -46,10 +65,15 @@ const SYMPTOM_TYPES = {
4665
]
4766
}
4867
},
49-
'Skin change': {
50-
weight: 0.15,
68+
'skin change': {
69+
weight: 0.13,
5170
requiresLocation: true,
52-
skinChangeTypes: ['dimples or indentation', 'rash', 'colour change'],
71+
skinChangeTypes: [
72+
'sores or cysts',
73+
'dimples or indentation',
74+
'rash',
75+
'colour change'
76+
],
5377
skinChangeDescriptions: {
5478
other: [
5579
'Orange peel texture',
@@ -58,30 +82,34 @@ const SYMPTOM_TYPES = {
5882
]
5983
}
6084
},
61-
'Other': {
85+
'other': {
6286
weight: 0.02,
6387
requiresLocation: true,
6488
descriptions: [
6589
'Unusual sensation or tenderness',
6690
'Heaviness in breast',
6791
'Tingling sensation',
68-
'Unusual firmness',
69-
'Persistent pain'
92+
'Unusual firmness'
7093
]
7194
}
7295
}
7396

74-
// const DATE_RANGE_OPTIONS = [
75-
// "Less than a week",
76-
// "1 week to a month",
77-
// "1 to 3 months",
78-
// "3 months to a year",
79-
// "1 to 3 years",
80-
// "Over 3 years"
81-
// ]
97+
// Build SYMPTOM_TYPES by merging data file with generator config
98+
// Use sentence case keys to match stored data format
99+
const SYMPTOM_TYPES = {}
100+
for (const symptomType of symptomTypesData) {
101+
const config = GENERATOR_CONFIG[symptomType.name]
102+
if (config) {
103+
const sentenceCaseName = toSentenceCase(symptomType.name)
104+
SYMPTOM_TYPES[sentenceCaseName] = {
105+
...config,
106+
isSignificantByDefault: symptomType.isSignificantByDefault
107+
}
108+
}
109+
}
82110

83111
const DATE_RANGE_OPTIONS = [
84-
'Less than 3 months',
112+
'Less than 3 months',
85113
'3 months to a year',
86114
'1 to 3 years',
87115
'Over 3 years'
@@ -175,7 +203,7 @@ const generateSymptom = (options = {}) => {
175203

176204
// Generate dateType matching the form structure
177205
const dateTypeWeights = {
178-
...Object.fromEntries(DATE_RANGE_OPTIONS.map((range) => [range, 0.1])), // 60% total for ranges
206+
...Object.fromEntries(DATE_RANGE_OPTIONS.map((range) => [range, 0.1])),
179207
dateKnown: 0.3,
180208
notSure: 0.1
181209
}
@@ -190,6 +218,14 @@ const generateSymptom = (options = {}) => {
190218
dateAdded: new Date().toISOString()
191219
}
192220

221+
// Set significance based on type
222+
if (typeData.isSignificantByDefault) {
223+
symptom.isSignificant = true
224+
} else {
225+
// For non-significant-by-default types, randomly mark 10% as significant
226+
symptom.isSignificant = Math.random() < 0.1
227+
}
228+
193229
// Add user who added the symptom
194230
if (options.addedByUserId) {
195231
symptom.addedByUserId = options.addedByUserId
@@ -213,7 +249,6 @@ const generateSymptom = (options = {}) => {
213249
// For range options, store the same value in approximateDuration
214250
symptom.approximateDuration = symptom.dateType
215251
}
216-
// For range options and 'notSure', no additional date fields needed
217252

218253
// 30% chance the symptom has recently stopped
219254
symptom.hasStopped = Math.random() < 0.3
@@ -227,10 +262,15 @@ const generateSymptom = (options = {}) => {
227262
symptom.isIntermittent = Math.random() < 0.25
228263

229264
// Handle type-specific fields
230-
if (type === 'Other') {
231-
symptom.otherLocationDescription = faker.helpers.arrayElement(
232-
typeData.descriptions
233-
)
265+
if (type === 'Other' || type === 'Breast pain') {
266+
// Both Other and Breast pain use simple descriptions
267+
if (type === 'Breast pain') {
268+
// Breast pain doesn't need otherDescription, but we can add to additionalInfo later
269+
} else {
270+
symptom.otherDescription = faker.helpers.arrayElement(
271+
typeData.descriptions
272+
)
273+
}
234274
} else if (type === 'Nipple change') {
235275
const changeType = faker.helpers.arrayElement(typeData.nippleChangeTypes)
236276
symptom.nippleChangeType = changeType

app/lib/utils/status.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ const getStatusTagColour = (status) => {
197197
// Metadata
198198
'has_symptoms': 'yellow',
199199
'has_repeat': 'yellow',
200+
'significant_symptom': 'yellow',
201+
'highlight_to_image_readers': 'yellow',
200202

201203
// Reading statuses
202204
'waiting_for_1st_read': 'grey',
@@ -291,6 +293,20 @@ const hasAppointmentNote = (event) => {
291293
return event?.appointmentNote && event.appointmentNote.trim().length > 0
292294
}
293295

296+
/**
297+
* Check if an event has an appointment note
298+
*
299+
* @param {object} event - Event object to check
300+
* @returns {boolean} Whether the event has an appointment note
301+
*/
302+
const hasSymptoms = (event) => {
303+
// symptoms stored at event.medicalInformation.symptoms[]
304+
return (
305+
event?.medicalInformation?.symptoms &&
306+
event.medicalInformation.symptoms.length > 0
307+
)
308+
}
309+
294310
module.exports = {
295311
hasNotStarted,
296312
isCompleted,
@@ -304,6 +320,7 @@ module.exports = {
304320
filterEventsByStatus,
305321
isSpecialAppointment,
306322
hasAppointmentNote,
323+
hasSymptoms,
307324
// Export groups for testing/reference
308325
STATUS_GROUPS
309326
}

app/routes/events.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const {
2424
} = require('../lib/utils/referrers')
2525
const { createDynamicTemplateRoute } = require('../lib/utils/dynamic-routing')
2626
const { isAppointmentWorkflow } = require('../lib/utils/status')
27+
const { sentenceCase } = require('../lib/utils/strings')
28+
// Load symptom types data
29+
const symptomTypes = require('../data/symptom-types')
2730

2831
/**
2932
* Get single event and its related data
@@ -590,6 +593,10 @@ module.exports = (router) => {
590593
const symptomType = symptomTemp.type
591594
const isNewSymptom = !symptomTemp.id
592595

596+
const symptomTypeConfig = symptomTypes.find(
597+
(st) => st.name === symptomType.toLowerCase()
598+
)
599+
593600
// Start with base symptom data
594601
const symptom = {
595602
id: symptomTemp.id || generateId(),
@@ -604,6 +611,16 @@ module.exports = (router) => {
604611
symptom.dateAdded = new Date().toISOString()
605612
}
606613

614+
if (symptomTypeConfig) {
615+
if (symptomTypeConfig.isSignificantByDefault) {
616+
// Always significant for default significant types
617+
symptom.isSignificant = true
618+
} else {
619+
// Use string value for non-default types
620+
symptom.isSignificant = symptomTemp.isSignificant === 'yes'
621+
}
622+
}
623+
607624
// Add investigation details if investigated
608625
if (symptomTemp.hasBeenInvestigated === 'yes') {
609626
symptom.investigatedDescription = symptomTemp.investigatedDescription
@@ -638,7 +655,7 @@ module.exports = (router) => {
638655
? true
639656
: false
640657

641-
if (symptom.hasStopped) {
658+
if (symptom.hasStopped && symptom) {
642659
symptom.approximateDateStopped = symptomTemp.approximateDateStopped
643660
}
644661

@@ -805,21 +822,15 @@ module.exports = (router) => {
805822

806823
// If symptomType is provided, pre-populate and go to details
807824
if (symptomType) {
808-
// Map camelCase symptom types to display names
809-
const symptomTypeMap = {
810-
lump: 'Lump',
811-
swellingOrShapeChange: 'Swelling or shape change',
812-
skinChange: 'Skin change',
813-
nippleChange: 'Nipple change',
814-
other: 'Other'
815-
}
816-
817-
const fullSymptomType = symptomTypeMap[symptomType]
825+
// Find symptom type by slug
826+
const symptomTypeConfig = symptomTypes.find(
827+
(st) => st.slug === symptomType
828+
)
818829

819-
if (fullSymptomType) {
830+
if (symptomTypeConfig) {
820831
// Pre-populate symptomTemp with the selected type
821832
data.event.symptomTemp = {
822-
type: fullSymptomType
833+
type: sentenceCase(symptomTypeConfig.name)
823834
}
824835

825836
// Redirect to details page

app/views/_includes/add-symptom-button-menu.njk

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,12 @@
88
{% set methodUrl = methodUrl ~ "/add?symptomType=" %}
99
{% endif %}
1010

11-
{% set symptoms = [
12-
"Lump",
13-
"Swelling or shape change",
14-
"Skin change",
15-
"Nipple change",
16-
"Other"
17-
] %}
18-
1911
{% set buttonMenuItems = [] %}
2012

21-
{% for symptom in symptoms %}
13+
{% for symptomType in data.symptomTypes %}
2214
{% set buttonMenuItems = buttonMenuItems | push({
23-
text: symptom,
24-
href: (methodUrl ~ (symptom | camelCase) ) | urlWithReferrer(referrerChain, scrollTo),
15+
text: symptomType.name | sentenceCase,
16+
href: (methodUrl ~ symptomType.slug) | urlWithReferrer(referrerChain, scrollTo),
2517
classes: "nhsuk-button--secondary js-expand-parent-section"
2618
}) %}
2719
{% endfor %}
@@ -37,6 +29,4 @@
3729
alignMenu: "left"
3830
}) }}
3931
</div>
40-
{% endif %}
41-
42-
32+
{% endif %}

app/views/_includes/medical-information/index.njk

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,10 @@
9999
<h4>Add symptoms</h4>
100100

101101
<div class="nhsuk-button-group app-button-group--small">
102-
{% set symptoms = {
103-
lump: 'Lump',
104-
swellingOrShapeChange: 'Swelling or shape change',
105-
skinChange: 'Skin change',
106-
nippleChange: 'Nipple change',
107-
other: 'Other'
108-
} %}
109-
{% for symptomType, symptomName in symptoms %}
102+
{% for symptomType in data.symptomTypes %}
110103
{{ button({
111-
text: symptomName,
112-
href: ("./medical-information/symptoms/add?symptomType=" + symptomType) | urlWithReferrer(currentUrl, scrollTo),
104+
text: symptomType.name | sentenceCase,
105+
href: ("./medical-information/symptoms/add?symptomType=" + symptomType.slug) | urlWithReferrer(currentUrl, scrollTo),
113106
classes: "nhsuk-button--secondary app-button--small"
114107
}) }}
115108
{% endfor %}

0 commit comments

Comments
 (0)