extract well visit schedule data

This commit is contained in:
Daniel 2026-05-08 05:11:29 +02:00
parent 03285532cb
commit b4d7420f22
5 changed files with 4152 additions and 5 deletions

File diff suppressed because it is too large Load diff

View file

@ -475,7 +475,6 @@
integrity="sha384-JUh163oCRItcbPme8pYnROHQMC6fNKTBWtRG3I3I0erJkzNgL7uxKlNwcrcFKeqF"
crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
<script defer src="/js/milestonesData.js"></script>
<script defer src="/js/pediatricScheduleData.js"></script>
<script defer src="/js/audioBackup.js"></script>
<script defer src="/js/browserWhisper.js"></script>
<script defer src="/js/speechRecognition.js"></script>
@ -498,7 +497,7 @@
<script defer src="/js/diagrams.js"></script>
<script type="module" src="/js/clinicalAssistant.js"></script>
<script defer src="/js/nextcloud.js"></script>
<script defer src="/js/wellVisit.js"></script>
<script type="module" src="/js/wellVisit.js"></script>
<script defer src="/js/shadess.js"></script>
<script defer src="/js/sickVisit.js"></script>
<script defer src="/js/ed-encounters.js"></script>

View file

@ -1,3 +1,5 @@
import { applyWellVisitScheduleGlobals, loadWellVisitScheduleData } from './wellVisit/scheduleData.js';
// ============================================================
// WELL VISIT / PREVENTIVE CARE TAB
// ============================================================
@ -77,6 +79,25 @@
// ─── Module-level state for visit statuses ────────────────────────────────
// keyed by visitId + '.' + itemKey — persisted to localStorage
var _visitStatuses = {};
var _scheduleReady = null;
function ensureScheduleData() {
if (typeof VISIT_AGES !== 'undefined' && typeof PERIODICITY !== 'undefined') return Promise.resolve();
if (!_scheduleReady) {
_scheduleReady = loadWellVisitScheduleData().then(function(data) {
applyWellVisitScheduleGlobals(data, window);
});
}
return _scheduleReady;
}
function showScheduleLoadError(panelId, err) {
var panel = document.getElementById(panelId);
if (panel) {
panel.innerHTML = '<div class="wv-empty">Unable to load well-visit schedule data. Please refresh and try again.</div>';
}
console.error('Failed to load well-visit schedule data', err);
}
// SSHADESS is only relevant for 12+ year visits
var SSHADESS_VISITS = ['12y','13y','14y','15y','16y','17y','18y','19y','20y','21y'];
@ -721,9 +742,18 @@
var _catchupInited = false;
document.addEventListener('tabChanged', function(e) {
var tab = e.detail.tab;
if (tab === 'wellvisit' && !_inited) { _inited = true; init(); }
if (tab === 'vaxschedule' && !_vaxInited) { _vaxInited = true; renderFullSchedule(); }
if (tab === 'catchup' && !_catchupInited) { _catchupInited = true; renderCatchUp(); }
if (tab === 'wellvisit' && !_inited) {
_inited = true;
ensureScheduleData().then(init).catch(function(err) { showScheduleLoadError('wv-visit-detail', err); });
}
if (tab === 'vaxschedule' && !_vaxInited) {
_vaxInited = true;
ensureScheduleData().then(renderFullSchedule).catch(function(err) { showScheduleLoadError('wv-full-schedule', err); });
}
if (tab === 'catchup' && !_catchupInited) {
_catchupInited = true;
ensureScheduleData().then(renderCatchUp).catch(function(err) { showScheduleLoadError('wv-catchup', err); });
}
});
})();

View file

@ -0,0 +1,32 @@
var scheduleDataCache = null;
export function loadWellVisitScheduleData() {
if (scheduleDataCache) return Promise.resolve(scheduleDataCache);
return fetch('/data/well-visit/schedule.json', { credentials: 'same-origin' })
.then(function(response) {
if (!response.ok) throw new Error('Failed to load well-visit schedule data');
return response.json();
})
.then(function(data) {
scheduleDataCache = data;
return scheduleDataCache;
});
}
export function applyWellVisitScheduleGlobals(data, target) {
var scope = target || globalThis;
scope.VISIT_AGES = data.visitAges;
scope.WELL_VISIT_CODES = data.wellVisitCodes;
scope.VACCINES = data.vaccines;
scope.PERIODICITY = data.periodicity;
scope.CATCH_UP_SCHEDULE = data.catchUpSchedule;
scope.ANTICIPATORY_GUIDANCE = data.anticipatoryGuidance;
scope.AAP_FOOTNOTES = data.aapFootnotes;
scope.DYSLIPIDEMIA_SCHEDULE = data.dyslipidemiaSchedule;
scope.NEWBORN_SCREENING = data.newbornScreening;
scope.GROWTH_REFERENCE = data.growthReference;
scope.REFLEXES_REFERENCE = data.reflexesReference;
scope.BMI_CLASSIFICATION = data.bmiClassification;
return scope;
}

View file

@ -0,0 +1,83 @@
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
function loadScheduleJson() {
const raw = fs.readFileSync(path.join(__dirname, '..', 'public', 'data', 'well-visit', 'schedule.json'), 'utf8');
return JSON.parse(raw);
}
function loadLegacyScheduleData() {
return require('../public/js/pediatricScheduleData.js');
}
test('schedule.json exactly mirrors existing pediatric schedule constants', () => {
const json = loadScheduleJson();
const legacy = loadLegacyScheduleData();
assert.equal(json.version, '2025.1');
assert.deepEqual(json.visitAges, legacy.VISIT_AGES);
assert.deepEqual(json.wellVisitCodes, legacy.WELL_VISIT_CODES);
assert.deepEqual(json.vaccines, legacy.VACCINES);
assert.deepEqual(json.periodicity, legacy.PERIODICITY);
assert.deepEqual(json.catchUpSchedule, legacy.CATCH_UP_SCHEDULE);
assert.deepEqual(json.anticipatoryGuidance, legacy.ANTICIPATORY_GUIDANCE);
assert.deepEqual(json.aapFootnotes, legacy.AAP_FOOTNOTES);
assert.deepEqual(json.dyslipidemiaSchedule, legacy.DYSLIPIDEMIA_SCHEDULE);
assert.deepEqual(json.newbornScreening, legacy.NEWBORN_SCREENING);
assert.deepEqual(json.growthReference, legacy.GROWTH_REFERENCE);
assert.deepEqual(json.reflexesReference, legacy.REFLEXES_REFERENCE);
assert.deepEqual(json.bmiClassification, legacy.BMI_CLASSIFICATION);
});
test('schedule.json preserves expected well-visit anchors', () => {
const data = loadScheduleJson();
assert.equal(data.visitAges.length, 32);
assert.deepEqual(data.visitAges[0], { id: 'prenatal', label: 'Prenatal', era: 'prenatal' });
assert.deepEqual(data.visitAges[data.visitAges.length - 1], { id: '21y', label: '21 Years', era: 'adolescence' });
assert.equal(data.wellVisitCodes['2mo'].cpt, '99391');
assert.equal(data.periodicity['12mo'].procedures.lead, 'dot_or_risk');
assert.equal(data.periodicity['11y'].developmental.depressionSuicideRisk, 'dot');
assert.equal(data.catchUpSchedule.DTaP.series[4].minimumAge, '4 years');
assert.equal(data.growthReference['2mo'].feeding.length, 4);
assert.equal(data.reflexesReference.newborn.reflexes[2].name, 'Moro (startle)');
assert.equal(data.bmiClassification.categories[3].label, 'Obesity (Class I)');
});
test('well-visit schedule loader fetches JSON and can apply legacy globals', async () => {
const data = loadScheduleJson();
const previousFetch = globalThis.fetch;
globalThis.fetch = async function(url, options) {
assert.equal(url, '/data/well-visit/schedule.json');
assert.deepEqual(options, { credentials: 'same-origin' });
return {
ok: true,
async json() {
return data;
}
};
};
try {
const module = await import('../public/js/wellVisit/scheduleData.js');
const loaded = await module.loadWellVisitScheduleData();
assert.strictEqual(loaded, data);
const target = {};
module.applyWellVisitScheduleGlobals(loaded, target);
assert.strictEqual(target.VISIT_AGES, data.visitAges);
assert.strictEqual(target.PERIODICITY, data.periodicity);
assert.strictEqual(target.CATCH_UP_SCHEDULE, data.catchUpSchedule);
assert.strictEqual(target.BMI_CLASSIFICATION, data.bmiClassification);
} finally {
globalThis.fetch = previousFetch;
}
});
test('index loads wellVisit as a module without the legacy schedule data script', () => {
const indexHtml = fs.readFileSync(path.join(__dirname, '..', 'public', 'index.html'), 'utf8');
assert.match(indexHtml, /<script type="module" src="\/js\/wellVisit\.js"><\/script>/);
assert.doesNotMatch(indexHtml, /src="\/js\/pediatricScheduleData\.js"/);
});