333 lines
9.7 KiB
JavaScript
333 lines
9.7 KiB
JavaScript
/**
|
||
* TGICS patient questionnaire — stable codes + branching rules.
|
||
*/
|
||
|
||
const opt = (value, label) => ({ value, label });
|
||
|
||
const QUESTIONS = {
|
||
gender: {
|
||
code: 'gender',
|
||
label: 'Gender',
|
||
type: 'single',
|
||
required: true,
|
||
options: [
|
||
opt('male', 'Male'),
|
||
opt('female', 'Female'),
|
||
opt('prefer_not_to_say', 'Prefer not to say'),
|
||
],
|
||
},
|
||
patient_age: {
|
||
code: 'patient_age',
|
||
label: 'Age (in completed years)',
|
||
type: 'number',
|
||
required: true,
|
||
min: 0,
|
||
max: 120,
|
||
},
|
||
exposure_history: {
|
||
code: 'exposure_history',
|
||
label: 'History of exposure?',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('never_smoker', 'Never smoker'),
|
||
opt('ex_smoker', 'Ex-smoker'),
|
||
opt('current_smoker', 'Current smoker'),
|
||
opt('pulmonary_tb_contact', 'Suffered or ever came in contact with a patient who had Pulmonary TB'),
|
||
opt('indoor_air_pollution', 'Current exposure to any sources of Indoor Air Pollution'),
|
||
],
|
||
},
|
||
occupation_exposure: {
|
||
code: 'occupation_exposure',
|
||
label: 'Work/occupation exposed to',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('smoke', 'Smoke'),
|
||
opt('fumes', 'Fumes'),
|
||
opt('dust', 'Dust'),
|
||
opt('gases', 'Gases'),
|
||
opt('none', 'None'),
|
||
],
|
||
},
|
||
family_history: {
|
||
code: 'family_history',
|
||
label: 'Family history',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('allergic_dermatitis_rhinitis_migraine', 'Allergic Dermatitis, Allergic Rhinitis, Migraine'),
|
||
opt('asthma', 'Asthma'),
|
||
opt('gerd', 'GERD (Gastroesophageal Reflux Disease)'),
|
||
opt('tb', 'TB'),
|
||
opt('rheumatic', 'Rheumatic diseases (arthritis, heart disease)'),
|
||
opt('cardiovascular', 'Cardiovascular diseases'),
|
||
opt('no_specific', 'No specific'),
|
||
],
|
||
},
|
||
past_disease_history: {
|
||
code: 'past_disease_history',
|
||
label: 'Past disease history of patient',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('allergic_dermatitis_rhinitis_migraine', 'Allergic Dermatitis, Allergic Rhinitis, Migraine'),
|
||
opt('asthma', 'Asthma'),
|
||
opt('copd', 'COPD'),
|
||
opt('tb', 'TB'),
|
||
opt('sinus_issues', 'Sinus issues (Post Nasal Drip)'),
|
||
opt('gerd', 'GERD (Gastroesophageal Reflux Disease)'),
|
||
opt('rheumatic', 'Rheumatic diseases (arthritis, heart disease)'),
|
||
opt('other_lung_disease', 'Any other lung disease'),
|
||
opt('no_history', 'No history of any disease'),
|
||
],
|
||
},
|
||
organ_system: {
|
||
code: 'organ_system',
|
||
label: 'What organ/system is involved for which the patient is visiting the doctor today?',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('skin', 'Skin'),
|
||
opt('brain_spinal', 'Brain & spinal cord'),
|
||
opt('ent', 'Ear/Nose/Throat'),
|
||
opt('heart', 'Heart disease'),
|
||
opt('lungs', 'Lungs & breathing tubes'),
|
||
opt('abdomen', 'Abdomen'),
|
||
opt('kidney', 'Kidney'),
|
||
opt('pregnancy_reproductive', 'Pregnancy & reproductive issues'),
|
||
opt('muscles_bones_joints', 'Muscles/Bones/Joints'),
|
||
],
|
||
},
|
||
ailment: {
|
||
code: 'ailment',
|
||
label: 'For which ailment is the patient visiting the Doctor today?',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('fever', 'Fever'),
|
||
opt('high_bp', 'High blood pressure'),
|
||
opt('diabetes', 'Diabetes'),
|
||
opt('anaemia', 'Anaemia'),
|
||
opt('stomach_upset', 'Stomach upset (vomiting / loose motions)'),
|
||
opt('cold_throat', 'Cold (runny nose) &/or throat irritation/pain'),
|
||
opt('thyroid', 'Thyroid'),
|
||
opt('rashes', 'Rashes (any where)'),
|
||
opt('swelling', 'Swelling on feet / body'),
|
||
opt('breathlessness', 'Breathlessness'),
|
||
opt('backache_weakness', 'Back ache / Weakness / Joint pain / Body ache'),
|
||
opt('foreign_body', 'Foreign body in eye/ear/nose/throat'),
|
||
opt('physical_injury', 'Physical injury'),
|
||
opt('others', 'Others (please specify briefly)'),
|
||
],
|
||
otherField: 'ailment_other',
|
||
},
|
||
has_cough: {
|
||
code: 'has_cough',
|
||
label: 'Does the patient complain of cough?',
|
||
type: 'single',
|
||
required: true,
|
||
options: [opt('yes', 'Yes'), opt('no', 'No')],
|
||
},
|
||
cough_duration: {
|
||
code: 'cough_duration',
|
||
label: 'What is the duration of cough?',
|
||
type: 'single',
|
||
required: true,
|
||
options: [
|
||
opt('lt_7_days', 'Less than 7 days'),
|
||
opt('7_days_to_3_weeks', '7 days to <3 weeks'),
|
||
opt('3_weeks_to_2_months', '3 weeks to 2 months'),
|
||
opt('gt_2_months', 'More than 2 months'),
|
||
],
|
||
},
|
||
cough_type: {
|
||
code: 'cough_type',
|
||
label: 'Is the cough dry or wet?',
|
||
type: 'single',
|
||
required: true,
|
||
options: [
|
||
opt('dry', 'Dry'),
|
||
opt('wet', 'Wet (with sputum/mucus/phlegm)'),
|
||
],
|
||
},
|
||
sputum_quantity: {
|
||
code: 'sputum_quantity',
|
||
label: 'How much is the quantity of sputum/mucus/phlegm?',
|
||
type: 'single',
|
||
required: true,
|
||
options: [
|
||
opt('scanty', 'Scanty'),
|
||
opt('moderate', 'Moderate'),
|
||
opt('excess', 'Excess'),
|
||
opt('sometimes_excess_sometimes_less', 'Sometimes excess and sometimes less'),
|
||
opt('more_early_morning', 'More in early morning'),
|
||
],
|
||
},
|
||
sputum_colour: {
|
||
code: 'sputum_colour',
|
||
label: 'What is the colour of the sputum/mucus/phlegm?',
|
||
type: 'single',
|
||
required: true,
|
||
options: [
|
||
opt('white', 'White'),
|
||
opt('yellow', 'Yellow'),
|
||
opt('green', 'Green'),
|
||
opt('brownish', 'Brownish'),
|
||
opt('pink', 'Pink'),
|
||
opt('blood_tinged', 'Blood tinged'),
|
||
],
|
||
},
|
||
cough_associated: {
|
||
code: 'cough_associated',
|
||
label: 'Is cough associated with?',
|
||
type: 'multi',
|
||
required: true,
|
||
options: [
|
||
opt('fever', 'Fever'),
|
||
opt('weight_loss', 'Weight loss'),
|
||
opt('loss_of_appetite', 'Loss of appetite'),
|
||
opt('evening_rise_fever', 'Evening rise of fever'),
|
||
opt('difficulty_breathing', 'Difficulty in breathing'),
|
||
opt('wheeze', 'Whistling sound from chest/ wheeze'),
|
||
opt('chest_pain', 'Chest pain'),
|
||
opt('hoarseness', 'Hoarseness of voice'),
|
||
opt('sinusitis', 'Sinusitis (Post Nasal Drip)'),
|
||
opt('acid_reflux', 'Acid Reflux/Regurgitation'),
|
||
opt('night_sweats', 'Night sweats'),
|
||
opt('others', 'Others (please specify)'),
|
||
opt('none', 'None of the above'),
|
||
],
|
||
otherField: 'cough_associated_other',
|
||
},
|
||
cough_diagnosis: {
|
||
code: 'cough_diagnosis',
|
||
label: 'What is the diagnosis for cough?',
|
||
type: 'single',
|
||
required: true,
|
||
options: [
|
||
opt('common_cold', 'Common cold'),
|
||
opt('sinusitis', 'Sinusitis'),
|
||
opt('allergic_cough', 'Allergic Cough'),
|
||
opt('allergic_rhinitis', 'Allergic Rhinitis'),
|
||
opt('asthma', 'Asthma'),
|
||
opt('drug_induced', 'Drug induced cough'),
|
||
opt('acidity_gerd', 'Acidity induced cough (GERD)'),
|
||
opt('post_nasal_drip', 'Post Nasal Drip (Upper Airway Cough Syndrome)'),
|
||
opt('tb', 'TB'),
|
||
opt('copd', 'COPD'),
|
||
opt('others', 'Others (please specify)'),
|
||
opt('not_sure', 'Not sure of diagnosis'),
|
||
],
|
||
otherField: 'cough_diagnosis_other',
|
||
},
|
||
};
|
||
|
||
function optionValues(question) {
|
||
return new Set(question.options.map((o) => o.value));
|
||
}
|
||
|
||
function validateAnswers(answers) {
|
||
if (!answers || typeof answers !== 'object') {
|
||
return { ok: false, message: 'Answers are required.' };
|
||
}
|
||
|
||
const requireSingle = (code) => {
|
||
const q = QUESTIONS[code];
|
||
const val = answers[code];
|
||
if (val === undefined || val === null || val === '') {
|
||
return `${q.label} is required.`;
|
||
}
|
||
if (!optionValues(q).has(String(val))) {
|
||
return `Invalid value for ${q.label}.`;
|
||
}
|
||
return null;
|
||
};
|
||
|
||
const requireMulti = (code) => {
|
||
const q = QUESTIONS[code];
|
||
const val = answers[code];
|
||
if (!Array.isArray(val) || val.length === 0) {
|
||
return `${q.label} is required.`;
|
||
}
|
||
const allowed = optionValues(q);
|
||
for (const v of val) {
|
||
if (!allowed.has(String(v))) {
|
||
return `Invalid value for ${q.label}.`;
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
const requireOther = (code) => {
|
||
const q = QUESTIONS[code];
|
||
if (!q.otherField) return null;
|
||
const val = answers[code];
|
||
const selected = Array.isArray(val) ? val : [val];
|
||
if (selected.includes('others')) {
|
||
const other = String(answers[q.otherField] || '').trim();
|
||
if (!other) {
|
||
return `Please specify details for ${q.label}.`;
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
let err =
|
||
requireSingle('gender') ||
|
||
null;
|
||
if (err) return { ok: false, message: err };
|
||
|
||
const age = Number(answers.patient_age);
|
||
if (!Number.isFinite(age) || age < 0 || age > 120) {
|
||
return { ok: false, message: 'Valid patient age is required (0–120).' };
|
||
}
|
||
|
||
for (const code of [
|
||
'exposure_history',
|
||
'occupation_exposure',
|
||
'family_history',
|
||
'past_disease_history',
|
||
'organ_system',
|
||
'ailment',
|
||
]) {
|
||
err = requireMulti(code) || requireOther(code);
|
||
if (err) return { ok: false, message: err };
|
||
}
|
||
|
||
err = requireSingle('has_cough');
|
||
if (err) return { ok: false, message: err };
|
||
|
||
const hasCough = String(answers.has_cough) === 'yes';
|
||
if (!hasCough) {
|
||
return { ok: true, hasCough: false };
|
||
}
|
||
|
||
err =
|
||
requireSingle('cough_duration') ||
|
||
requireSingle('cough_type');
|
||
if (err) return { ok: false, message: err };
|
||
|
||
const isWet = String(answers.cough_type) === 'wet';
|
||
if (isWet) {
|
||
err =
|
||
requireSingle('sputum_quantity') ||
|
||
requireSingle('sputum_colour');
|
||
if (err) return { ok: false, message: err };
|
||
}
|
||
|
||
err =
|
||
requireMulti('cough_associated') ||
|
||
requireOther('cough_associated') ||
|
||
requireSingle('cough_diagnosis') ||
|
||
requireOther('cough_diagnosis');
|
||
if (err) return { ok: false, message: err };
|
||
|
||
return { ok: true, hasCough: true };
|
||
}
|
||
|
||
module.exports = {
|
||
QUESTIONS,
|
||
validateAnswers,
|
||
};
|