Four changes batched: 1. ED Encounters tab (new) — multi-stage emergency note with don't-miss tooltips and 2023 E/M MDM finalize. New route /api/ed-encounters (generate per-stage + finalize MDM), new ed-encounters.js owning all client logic, new ed-encounter.html component, new template_ed memory category. Persists draft to localStorage every keystroke and to saved_encounters on stage advance. encounters.js touched only to register the new tab in sessionStorage restore + tabMap (save and idempotency code untouched). 2. Notes model selector — /notes/from-voice now accepts a client-supplied model (validated by the existing callAI allow-list); falls back to the admin default. Added <select class="tab-model-select"> to notes.html so the existing app.js populator handles options + default. 3. Remove AI-learning-from-corrections — deleted correctionTracker.js, POST /memories/correction, the corrections branch in /memories/context, the settings UI section, the FAQ entry, and all dead trackAIOutput/saveCorrection guards in callers. Legacy correction_* DB rows are filtered (NOT LIKE) rather than dropped, so no destructive migration. 4. Fix notes AI framing — /notes/from-voice prompt no longer assumes "physician dictation". Plain notes (shopping lists, reminders, ideas) now match the dictation tone instead of being forced into clinical structure. All 46 tests pass.
449 lines
24 KiB
JavaScript
449 lines
24 KiB
JavaScript
// ============================================================
|
|
// SICK VISIT TAB — Recording, ROS/PE inference, Note generation
|
|
// ============================================================
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// ── 15 ROS systems (same keys as shadess.js) ─────────────────────────────
|
|
var ROS_SYSTEMS = [
|
|
{ key: 'constitutional', label: 'Constitutional', detail: 'fever, fatigue, weight loss/gain, appetite' },
|
|
{ key: 'eyes', label: 'Eyes', detail: 'vision changes, discharge, redness' },
|
|
{ key: 'ent', label: 'ENT', detail: 'ear pain, congestion, sore throat, mouth sores' },
|
|
{ key: 'cardiovascular', label: 'Cardiovascular', detail: 'chest pain, palpitations, murmur' },
|
|
{ key: 'respiratory', label: 'Respiratory', detail: 'cough, wheeze, dyspnea' },
|
|
{ key: 'gastrointestinal', label: 'Gastrointestinal', detail: 'nausea, vomiting, diarrhea, constipation, abdominal pain' },
|
|
{ key: 'genitourinary', label: 'Genitourinary', detail: 'dysuria, frequency, discharge, menstrual' },
|
|
{ key: 'musculoskeletal', label: 'Musculoskeletal', detail: 'joint pain, swelling, gait issues, back pain' },
|
|
{ key: 'skin', label: 'Skin', detail: 'rash, lesions, itch, hair/nail changes' },
|
|
{ key: 'neurological', label: 'Neurological', detail: 'headache, dizziness, seizures, numbness' },
|
|
{ key: 'psychiatric', label: 'Psychiatric', detail: 'mood, anxiety, sleep, behavior changes' },
|
|
{ key: 'endocrine', label: 'Endocrine', detail: 'polyuria/polydipsia, heat/cold intolerance, growth concerns' },
|
|
{ key: 'hematologic', label: 'Hematologic/Lymphatic', detail: 'bruising, bleeding, lymph nodes' },
|
|
{ key: 'allergic', label: 'Allergic/Immunologic', detail: 'allergic reactions, frequent infections' },
|
|
{ key: 'developmental', label: 'Developmental/Behavioral', detail: 'milestones, school, social' },
|
|
];
|
|
|
|
// ── 17 PE systems ─────────────────────────────────────────────────────────
|
|
var PE_SYSTEMS = [
|
|
{ key: 'general', label: 'General Appearance', detail: 'WDWN, alert, NAD, etc.' },
|
|
{ key: 'peEyes', label: 'Eyes', detail: 'PERRL, conjunctiva, EOM' },
|
|
{ key: 'ears', label: 'Ears', detail: 'TMs, canals, hearing' },
|
|
{ key: 'nose', label: 'Nose', detail: 'mucosa, septum, turbinates' },
|
|
{ key: 'mouthThroat', label: 'Mouth/Throat', detail: 'lips, gums, teeth, tonsils, pharynx' },
|
|
{ key: 'neck', label: 'Neck', detail: 'lymph nodes, thyroid, masses' },
|
|
{ key: 'chestLungs', label: 'Chest/Lungs', detail: 'air entry, wheezing, retractions' },
|
|
{ key: 'peCv', label: 'Cardiovascular', detail: 'S1/S2, murmur, pulses, cap refill' },
|
|
{ key: 'abdomen', label: 'Abdomen', detail: 'soft, tenderness, organomegaly, BS' },
|
|
{ key: 'guTanner', label: 'Genitourinary/Tanner', detail: 'Tanner stage, genitalia, hernias' },
|
|
{ key: 'msk', label: 'Musculoskeletal', detail: 'strength, ROM, gait, spine' },
|
|
{ key: 'extremities', label: 'Extremities', detail: 'tone, reflexes, clubbing, edema' },
|
|
{ key: 'peSkin', label: 'Skin', detail: 'rashes, lesions, cafe au lait, bruises' },
|
|
{ key: 'peNeuro', label: 'Neurological', detail: 'CN intact, DTRs, coordination' },
|
|
{ key: 'lymphatic', label: 'Lymphatic', detail: 'cervical, axillary, inguinal nodes' },
|
|
{ key: 'breast', label: 'Breast', detail: 'if indicated by age/Tanner' },
|
|
{ key: 'pePsych', label: 'Psychiatric', detail: 'affect, mood, behavior during exam' },
|
|
];
|
|
|
|
// ── Inference rules (CC keyword → ROS and PE system keys) ─────────────────
|
|
var INFERENCE_RULES = [
|
|
{ keywords: ['ear', 'hearing', 'otalgia', 'otitis'],
|
|
ros: ['constitutional', 'ent'],
|
|
pe: ['general', 'ears', 'nose', 'mouthThroat', 'neck'] },
|
|
{ keywords: ['throat', 'strep', 'pharyn', 'tonsil'],
|
|
ros: ['constitutional', 'ent'],
|
|
pe: ['general', 'mouthThroat', 'neck', 'ears', 'lymphatic'] },
|
|
{ keywords: ['cough', 'breath', 'asthma', 'wheez', 'respiratory', 'bronch'],
|
|
ros: ['constitutional', 'respiratory', 'cardiovascular', 'ent'],
|
|
pe: ['general', 'chestLungs', 'peCv', 'ears', 'nose'] },
|
|
{ keywords: ['stomach', 'vomit', 'nausea', 'diarr', 'abdomin', 'constip', 'belly'],
|
|
ros: ['constitutional', 'gastrointestinal', 'genitourinary'],
|
|
pe: ['general', 'abdomen', 'chestLungs', 'peCv', 'peSkin'] },
|
|
{ keywords: ['fever', 'temp', 'hot'],
|
|
ros: ['constitutional', 'ent', 'respiratory', 'gastrointestinal'],
|
|
pe: ['general', 'ears', 'mouthThroat', 'chestLungs', 'abdomen', 'peSkin', 'lymphatic'] },
|
|
{ keywords: ['rash', 'skin', 'itch', 'eczema', 'hives', 'dermat'],
|
|
ros: ['constitutional', 'skin', 'allergic'],
|
|
pe: ['general', 'peSkin', 'chestLungs', 'peCv', 'lymphatic'] },
|
|
{ keywords: ['headache', 'head', 'migrain'],
|
|
ros: ['constitutional', 'neurological', 'eyes', 'ent'],
|
|
pe: ['general', 'peNeuro', 'peEyes', 'ears', 'neck'] },
|
|
{ keywords: ['urin', 'uti', 'dysuria', 'bladder', 'kidney'],
|
|
ros: ['constitutional', 'genitourinary', 'gastrointestinal'],
|
|
pe: ['general', 'abdomen', 'guTanner', 'chestLungs', 'peCv'] },
|
|
{ keywords: ['joint', 'knee', 'limb', 'leg', 'arm', 'ankle', 'musculo', 'back', 'pain'],
|
|
ros: ['constitutional', 'musculoskeletal', 'neurological'],
|
|
pe: ['general', 'msk', 'extremities', 'peNeuro', 'peSkin'] },
|
|
{ keywords: ['anxi', 'mood', 'behav', 'depress', 'mental', 'school'],
|
|
ros: ['constitutional', 'psychiatric', 'neurological', 'developmental'],
|
|
pe: ['general', 'pePsych', 'peNeuro', 'peSkin'] },
|
|
];
|
|
|
|
var DEFAULT_ROS = ['constitutional', 'ent', 'respiratory', 'gastrointestinal'];
|
|
var DEFAULT_PE = ['general', 'ears', 'chestLungs', 'peCv', 'abdomen', 'peSkin', 'peNeuro'];
|
|
|
|
function inferSystems(cc) {
|
|
var lc = (cc || '').toLowerCase();
|
|
var rosSet = {};
|
|
var peSet = {};
|
|
var matched = false;
|
|
|
|
INFERENCE_RULES.forEach(function(rule) {
|
|
if (rule.keywords.some(function(kw) { return lc.indexOf(kw) !== -1; })) {
|
|
matched = true;
|
|
rule.ros.forEach(function(k) { rosSet[k] = true; });
|
|
rule.pe.forEach(function(k) { peSet[k] = true; });
|
|
}
|
|
});
|
|
|
|
if (!matched) {
|
|
DEFAULT_ROS.forEach(function(k) { rosSet[k] = true; });
|
|
DEFAULT_PE.forEach(function(k) { peSet[k] = true; });
|
|
}
|
|
|
|
// Return top 4 ROS, top 7 PE
|
|
var rosKeys = Object.keys(rosSet).slice(0, 4);
|
|
var peKeys = Object.keys(peSet).slice(0, 7);
|
|
|
|
// Ensure we always have at least the default counts
|
|
DEFAULT_ROS.forEach(function(k) { if (rosKeys.length < 4 && rosKeys.indexOf(k) === -1) rosKeys.push(k); });
|
|
DEFAULT_PE.forEach(function(k) { if (peKeys.length < 7 && peKeys.indexOf(k) === -1) peKeys.push(k); });
|
|
|
|
return {
|
|
ros: rosKeys.map(function(k) { return ROS_SYSTEMS.find(function(s) { return s.key === k; }); }).filter(Boolean),
|
|
pe: peKeys.map(function(k) { return PE_SYSTEMS.find(function(s) { return s.key === k; }); }).filter(Boolean),
|
|
};
|
|
}
|
|
|
|
// ── Module state ─────────────────────────────────────────────────────────
|
|
var _sickRosData = {};
|
|
var _sickPeData = {};
|
|
var _sickSelectedDx = [];
|
|
var _sickRosSystems = DEFAULT_ROS.map(function(k) { return ROS_SYSTEMS.find(function(s) { return s.key === k; }); }).filter(Boolean);
|
|
var _sickPeSystems = DEFAULT_PE.map(function(k) { return PE_SYSTEMS.find(function(s) { return s.key === k; }); }).filter(Boolean);
|
|
|
|
// ── Recording ────────────────────────────────────────────────────────────
|
|
var _sickRecorder = null;
|
|
var _sickTimer = null;
|
|
var _sickRecording = false;
|
|
var _sickPaused = false;
|
|
var _sickRecognition = null;
|
|
var _sickTranscript = '';
|
|
|
|
function initRecording() {
|
|
var recordBtn = document.getElementById('sick-record-btn');
|
|
var pauseBtn = document.getElementById('sick-pause-btn');
|
|
var indicator = document.getElementById('sick-rec-indicator');
|
|
var timerEl = document.getElementById('sick-timer');
|
|
var transcriptEl = document.getElementById('sick-transcript');
|
|
if (!recordBtn) return;
|
|
|
|
_sickTimer = createTimer(timerEl);
|
|
_sickRecognition = createSpeechRecognition();
|
|
if (_sickRecognition) {
|
|
_sickRecognition.onresult = function(e) {
|
|
var interim = '';
|
|
for (var i = e.resultIndex; i < e.results.length; i++) {
|
|
if (e.results[i].isFinal) _sickTranscript += e.results[i][0].transcript + ' ';
|
|
else interim = e.results[i][0].transcript;
|
|
}
|
|
if (transcriptEl) transcriptEl.innerHTML = _sickTranscript + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
|
};
|
|
_sickRecognition.onend = function() { if (_sickRecording && !_sickPaused) try { _sickRecognition.start(); } catch(e) {} };
|
|
}
|
|
|
|
recordBtn.addEventListener('click', function() {
|
|
if (!_sickRecording) {
|
|
_sickTranscript = '';
|
|
_sickRecorder = new AudioRecorder();
|
|
_sickRecorder.start().then(function() {
|
|
_sickRecording = true; _sickPaused = false;
|
|
recordBtn.innerHTML = '<i class="fas fa-stop"></i> Stop';
|
|
recordBtn.classList.add('recording');
|
|
if (pauseBtn) pauseBtn.classList.remove('hidden');
|
|
indicator.classList.remove('hidden');
|
|
_sickTimer.start();
|
|
if (_sickRecognition) try { _sickRecognition.start(); } catch(e) {}
|
|
showToast('Sick visit recording started', 'info');
|
|
document.dispatchEvent(new CustomEvent('recording-started', { detail: { module: 'sick' } }));
|
|
}).catch(function() { showToast('Microphone denied', 'error'); });
|
|
} else {
|
|
_sickRecording = false; _sickPaused = false;
|
|
var dur = _sickTimer.stop();
|
|
recordBtn.innerHTML = '<i class="fas fa-microphone"></i> Listen In';
|
|
recordBtn.classList.remove('recording');
|
|
if (pauseBtn) pauseBtn.classList.add('hidden');
|
|
indicator.classList.add('hidden');
|
|
if (_sickRecognition) try { _sickRecognition.stop(); } catch(e) {}
|
|
document.dispatchEvent(new CustomEvent('recording-stopped', { detail: { module: 'sick' } }));
|
|
|
|
showBusy('Transcribing...');
|
|
_sickRecorder.stop().then(function(blob) {
|
|
if (!blob || blob.size === 0) { hideBusy(); if (_sickTranscript.trim() && transcriptEl) transcriptEl.textContent = _sickTranscript.trim(); return; }
|
|
return transcribeAudio(blob).then(function(data) {
|
|
hideBusy();
|
|
if (data.success) { if (transcriptEl) transcriptEl.textContent = data.text; _sickTranscript = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
|
else { if (_sickTranscript.trim() && transcriptEl) transcriptEl.textContent = _sickTranscript.trim(); }
|
|
});
|
|
}).catch(function() { hideBusy(); });
|
|
}
|
|
});
|
|
|
|
if (pauseBtn) {
|
|
pauseBtn.addEventListener('click', function() {
|
|
if (!_sickRecording || !_sickRecorder || !_sickRecorder.mediaRecorder) return;
|
|
if (!_sickPaused) {
|
|
try { if (_sickRecorder.mediaRecorder.state === 'recording') _sickRecorder.mediaRecorder.pause(); } catch(e) {}
|
|
_sickTimer.stop(); _sickPaused = true;
|
|
pauseBtn.innerHTML = '<i class="fas fa-play"></i> Resume';
|
|
if (_sickRecognition) try { _sickRecognition.stop(); } catch(e) {}
|
|
} else {
|
|
try {
|
|
if (_sickRecorder.mediaRecorder.state === 'paused') { _sickRecorder.mediaRecorder.resume(); }
|
|
else if (_sickRecorder.mediaRecorder.state === 'inactive' && _sickRecorder.stream && _sickRecorder.stream.active) {
|
|
var mime = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
|
|
_sickRecorder.mediaRecorder = new MediaRecorder(_sickRecorder.stream, { mimeType: mime, audioBitsPerSecond: 32000 });
|
|
_sickRecorder.mediaRecorder.ondataavailable = function(e) { if (e.data.size > 0) _sickRecorder.chunks.push(e.data); };
|
|
_sickRecorder.mediaRecorder.start(1000);
|
|
}
|
|
} catch(e) {}
|
|
_sickTimer.resume(); _sickPaused = false;
|
|
pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
|
|
if (_sickRecognition) try { _sickRecognition.start(); } catch(e) {}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── ROS/PE rendering ──────────────────────────────────────────────────────
|
|
function populateAddDropdowns() {
|
|
var rosAddEl = document.getElementById('sick-ros-add');
|
|
var peAddEl = document.getElementById('sick-pe-add');
|
|
|
|
if (rosAddEl) {
|
|
rosAddEl.innerHTML = '<option value="">+ Add system</option>' +
|
|
ROS_SYSTEMS.map(function(s) {
|
|
return '<option value="' + esc(s.key) + '">' + esc(s.label) + '</option>';
|
|
}).join('');
|
|
rosAddEl.addEventListener('change', function() {
|
|
var key = this.value;
|
|
if (!key) return;
|
|
this.value = '';
|
|
var sys = ROS_SYSTEMS.find(function(s) { return s.key === key; });
|
|
if (!sys) return;
|
|
if (_sickRosSystems.some(function(s) { return s.key === key; })) return;
|
|
_sickRosSystems.push(sys);
|
|
renderRosPe();
|
|
});
|
|
}
|
|
|
|
if (peAddEl) {
|
|
peAddEl.innerHTML = '<option value="">+ Add system</option>' +
|
|
PE_SYSTEMS.map(function(s) {
|
|
return '<option value="' + esc(s.key) + '">' + esc(s.label) + '</option>';
|
|
}).join('');
|
|
peAddEl.addEventListener('change', function() {
|
|
var key = this.value;
|
|
if (!key) return;
|
|
this.value = '';
|
|
var sys = PE_SYSTEMS.find(function(s) { return s.key === key; });
|
|
if (!sys) return;
|
|
if (_sickPeSystems.some(function(s) { return s.key === key; })) return;
|
|
_sickPeSystems.push(sys);
|
|
renderRosPe();
|
|
});
|
|
}
|
|
}
|
|
|
|
function renderRosPe() {
|
|
var rosContainer = document.getElementById('sick-ros-container');
|
|
var peContainer = document.getElementById('sick-pe-container');
|
|
|
|
if (rosContainer && typeof window.renderRosRows === 'function') {
|
|
rosContainer.innerHTML = window.renderRosRows(_sickRosSystems, _sickRosData, 'sros', { wnl: 'WNL', abnormal: 'Abnormal', notrev: 'Not reviewed' });
|
|
window.wireRosContainer(rosContainer, _sickRosData);
|
|
}
|
|
if (peContainer && typeof window.renderRosRows === 'function') {
|
|
peContainer.innerHTML = window.renderRosRows(_sickPeSystems, _sickPeData, 'spe', { wnl: 'Normal', abnormal: 'Abnormal', notrev: 'Not examined' });
|
|
window.wireRosContainer(peContainer, _sickPeData);
|
|
}
|
|
}
|
|
|
|
function inferAndRender() {
|
|
var cc = (document.getElementById('sick-cc') || {}).value || '';
|
|
var inferred = inferSystems(cc);
|
|
_sickRosSystems = inferred.ros;
|
|
_sickPeSystems = inferred.pe;
|
|
_sickRosData = {};
|
|
_sickPeData = {};
|
|
renderRosPe();
|
|
if (cc) showToast('ROS/PE updated for: ' + cc, 'info');
|
|
}
|
|
|
|
// ── Diagnoses ─────────────────────────────────────────────────────────────
|
|
function initDx() {
|
|
var dxContainer = document.getElementById('sick-dx-container');
|
|
if (dxContainer && typeof window.renderDxComponent === 'function') {
|
|
window.renderDxComponent(dxContainer, _sickSelectedDx, 'sick');
|
|
}
|
|
}
|
|
|
|
// ── Generate note ─────────────────────────────────────────────────────────
|
|
function generateSickNote() {
|
|
var age = (document.getElementById('sick-age') || {}).value || '';
|
|
var gender = (document.getElementById('sick-gender') || {}).value || '';
|
|
var cc = (document.getElementById('sick-cc') || {}).value || '';
|
|
if (!cc) { showToast('Enter a chief complaint first', 'error'); return; }
|
|
|
|
var transcriptEl = document.getElementById('sick-transcript');
|
|
var transcript = transcriptEl ? (transcriptEl.innerText || transcriptEl.textContent || '') : '';
|
|
|
|
var rosText = typeof window.formatRosForAI === 'function' ? window.formatRosForAI(_sickRosSystems, _sickRosData, 'Review of Systems') : '';
|
|
var peText = typeof window.formatRosForAI === 'function' ? window.formatRosForAI(_sickPeSystems, _sickPeData, 'Physical Examination') : '';
|
|
var dxText = typeof window.formatDxForAI === 'function' ? window.formatDxForAI(_sickSelectedDx, 'sick-dx-freetext') : '';
|
|
|
|
showBusy('Generating sick visit note...');
|
|
|
|
var modelEl = document.getElementById('sick-model-select');
|
|
|
|
var memoriesPromise = (typeof getUserMemoryContext === 'function') ? getUserMemoryContext() : Promise.resolve('');
|
|
|
|
memoriesPromise.then(function(memCtx) {
|
|
return fetch('/api/sick-visit/note', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
patientAge: age,
|
|
patientGender: gender,
|
|
chiefComplaint: cc,
|
|
transcript: transcript || null,
|
|
ros: rosText || null,
|
|
physicalExam: peText || null,
|
|
diagnoses: dxText || null,
|
|
physicianMemories: memCtx || null,
|
|
model: modelEl ? modelEl.value : getSelectedModel()
|
|
})
|
|
});
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
hideBusy();
|
|
if (data.success) {
|
|
var noteEl = document.getElementById('sick-note-text');
|
|
var outCard = document.getElementById('sick-note-output');
|
|
var tag = document.getElementById('sick-note-model-tag');
|
|
if (noteEl) setOutputText(noteEl, data.note);
|
|
var sickTranscript = document.getElementById('sick-transcript');
|
|
if (sickTranscript) storeSourceContext('sick-note-text', sickTranscript.innerText.trim());
|
|
if (tag) tag.textContent = (data.model || '').split('/').pop();
|
|
if (outCard) { outCard.classList.remove('hidden'); outCard.scrollIntoView({ behavior: 'smooth' }); }
|
|
showToast('Sick visit note generated!', 'success');
|
|
if (typeof suggestBillingCodes === 'function') suggestBillingCodes('sick-note-text', data.note, 'sickvisit', document.getElementById('sick-age').value);
|
|
} else {
|
|
showToast(data.error || 'Generation failed', 'error');
|
|
}
|
|
})
|
|
.catch(function(err) { hideBusy(); showToast(err.message, 'error'); });
|
|
}
|
|
|
|
// ── Reset ─────────────────────────────────────────────────────────────────
|
|
function resetSickVisit() {
|
|
var fields = ['sick-age', 'sick-gender', 'sick-cc', 'sick-label'];
|
|
fields.forEach(function(id) { var el = document.getElementById(id); if (el) el.value = ''; });
|
|
var transcriptEl = document.getElementById('sick-transcript');
|
|
if (transcriptEl) transcriptEl.textContent = '';
|
|
_sickTranscript = '';
|
|
_sickRosData = {};
|
|
_sickPeData = {};
|
|
_sickSelectedDx.length = 0;
|
|
_sickRosSystems = DEFAULT_ROS.map(function(k) { return ROS_SYSTEMS.find(function(s) { return s.key === k; }); }).filter(Boolean);
|
|
_sickPeSystems = DEFAULT_PE.map(function(k) { return PE_SYSTEMS.find(function(s) { return s.key === k; }); }).filter(Boolean);
|
|
renderRosPe();
|
|
initDx();
|
|
var outCard = document.getElementById('sick-note-output');
|
|
var noteEl = document.getElementById('sick-note-text');
|
|
if (outCard) outCard.classList.add('hidden');
|
|
if (noteEl) noteEl.textContent = '';
|
|
window._savedEncId_sickvisit = null;
|
|
showToast('Sick visit cleared for new patient', 'info');
|
|
}
|
|
|
|
// ── Click handler ─────────────────────────────────────────────────────────
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target.closest('#btn-sick-generate')) generateSickNote();
|
|
if (e.target.closest('#btn-sick-infer')) inferAndRender();
|
|
if (e.target.closest('#btn-sick-new-patient')) resetSickVisit();
|
|
if (e.target.closest('#sick-refine-btn')) refineDocument('sick-note-text', 'sick-refine-input');
|
|
if (e.target.closest('#sick-shorten-btn')) shortenDocument('sick-note-text');
|
|
|
|
if (e.target.closest('#sick-ros-all-wnl')) {
|
|
_sickRosSystems.forEach(function(sys) { _sickRosData[sys.key + '_status'] = 'wnl'; });
|
|
renderRosPe();
|
|
showToast('All ROS set to WNL', 'success');
|
|
}
|
|
if (e.target.closest('#sick-ros-clear')) {
|
|
_sickRosSystems.forEach(function(sys) { _sickRosData[sys.key + '_status'] = ''; _sickRosData[sys.key + '_note'] = ''; });
|
|
renderRosPe();
|
|
showToast('ROS cleared', 'info');
|
|
}
|
|
if (e.target.closest('#sick-pe-all-normal')) {
|
|
_sickPeSystems.forEach(function(sys) { _sickPeData[sys.key + '_status'] = 'wnl'; });
|
|
renderRosPe();
|
|
showToast('All PE set to Normal', 'success');
|
|
}
|
|
if (e.target.closest('#sick-pe-clear')) {
|
|
_sickPeSystems.forEach(function(sys) { _sickPeData[sys.key + '_status'] = ''; _sickPeData[sys.key + '_note'] = ''; });
|
|
renderRosPe();
|
|
showToast('PE cleared', 'info');
|
|
}
|
|
|
|
if (e.target.closest('#btn-sick-save')) {
|
|
var labelEl = document.getElementById('sick-label');
|
|
var transcriptEl = document.getElementById('sick-transcript');
|
|
var noteEl = document.getElementById('sick-note-text');
|
|
if (typeof saveEncounter === 'function') {
|
|
saveEncounter({
|
|
id: window._savedEncId_sickvisit || null,
|
|
label: labelEl ? labelEl.value.trim() : '',
|
|
enc_type: 'sickvisit',
|
|
transcript: transcriptEl ? (transcriptEl.innerText || '') : '',
|
|
generated_note: noteEl ? (noteEl.innerText || '') : '',
|
|
onSaved: function(id) { window._savedEncId_sickvisit = id; }
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// ── Load handler ──────────────────────────────────────────────────────────
|
|
if (typeof registerEncounterLoadHandler === 'function') {
|
|
registerEncounterLoadHandler('sickvisit', function(enc) {
|
|
var transcriptEl = document.getElementById('sick-transcript');
|
|
if (transcriptEl && enc.transcript) transcriptEl.textContent = enc.transcript;
|
|
if (enc.generated_note) {
|
|
var noteEl = document.getElementById('sick-note-text');
|
|
var outCard = document.getElementById('sick-note-output');
|
|
if (noteEl) noteEl.textContent = enc.generated_note;
|
|
if (outCard) outCard.classList.remove('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function esc(str) {
|
|
if (!str) return '';
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
// ── Init ──────────────────────────────────────────────────────────────────
|
|
var _inited = false;
|
|
document.addEventListener('tabChanged', function(e) {
|
|
if (e.detail.tab !== 'sickvisit' || _inited) return;
|
|
_inited = true;
|
|
initRecording();
|
|
populateAddDropdowns();
|
|
renderRosPe();
|
|
initDx();
|
|
});
|
|
|
|
console.log('SickVisit module loaded');
|
|
|
|
})();
|