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.
524 lines
22 KiB
JavaScript
524 lines
22 KiB
JavaScript
// ============================================================
|
|
// ED ENCOUNTER TAB — multi-stage emergency note with don't-miss
|
|
// tooltips and 2023 E/M MDM finalize.
|
|
//
|
|
// State model:
|
|
// _state = {
|
|
// stage: 1|2|..., // current stage number
|
|
// stages: [ // per-stage history
|
|
// { transcript, note, dontMiss, model, generatedAt }
|
|
// ],
|
|
// finalized: false,
|
|
// mdm: null
|
|
// }
|
|
//
|
|
// Persistence: localStorage on every mutation (debounced) + auto-save
|
|
// to saved_encounters DB row (status='draft') on stage transitions.
|
|
// "Save & Done" generates MDM, marks status='final', clears localStorage.
|
|
//
|
|
// Sacred-file note: this file owns ALL ED logic. encounters.js gets
|
|
// a 2-string touch only (sessionStorage array + tabMap).
|
|
// ============================================================
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
var LS_KEY = 'ped_ed_draft_v1';
|
|
var AUTOSAVE_MS = 1500;
|
|
|
|
var _state = freshState();
|
|
var _saveTimer = null;
|
|
var _recorder = null;
|
|
var _timer = null;
|
|
var _recording = false;
|
|
var _paused = false;
|
|
var _recognition = null;
|
|
var _liveTranscript = '';
|
|
|
|
function freshState() {
|
|
return { stage: 1, stages: [], finalized: false, mdm: null };
|
|
}
|
|
|
|
// ── Persistence ──────────────────────────────────────────────────────
|
|
function persistLocal() {
|
|
if (_saveTimer) clearTimeout(_saveTimer);
|
|
_saveTimer = setTimeout(function() {
|
|
try {
|
|
var snap = {
|
|
state: _state,
|
|
label: getVal('ed-label'),
|
|
age: getVal('ed-age'),
|
|
gender: getVal('ed-gender'),
|
|
cc: getVal('ed-cc'),
|
|
currentTranscript: getText('ed-transcript'),
|
|
editedNote: getText('ed-note-text')
|
|
};
|
|
localStorage.setItem(LS_KEY, JSON.stringify(snap));
|
|
} catch (e) {}
|
|
}, 300);
|
|
}
|
|
|
|
function loadLocal() {
|
|
try {
|
|
var raw = localStorage.getItem(LS_KEY);
|
|
if (!raw) return;
|
|
var snap = JSON.parse(raw);
|
|
if (!snap || !snap.state) return;
|
|
_state = snap.state;
|
|
setVal('ed-label', snap.label || '');
|
|
setVal('ed-age', snap.age || '');
|
|
setVal('ed-gender', snap.gender || '');
|
|
setVal('ed-cc', snap.cc || '');
|
|
setText('ed-transcript', snap.currentTranscript || '');
|
|
// Render last generated stage if present
|
|
var last = lastStage();
|
|
if (last) {
|
|
setText('ed-note-text', snap.editedNote || last.note || '');
|
|
renderDontMiss(last.dontMiss || []);
|
|
showOutput();
|
|
updateStageTags();
|
|
}
|
|
if (_state.finalized && _state.mdm) renderMdm(_state.mdm);
|
|
} catch (e) {}
|
|
}
|
|
|
|
function clearLocal() {
|
|
try { localStorage.removeItem(LS_KEY); } catch (e) {}
|
|
}
|
|
|
|
function lastStage() {
|
|
return _state.stages.length ? _state.stages[_state.stages.length - 1] : null;
|
|
}
|
|
|
|
// ── DOM helpers ──────────────────────────────────────────────────────
|
|
function getVal(id) { var el = document.getElementById(id); return el ? el.value : ''; }
|
|
function setVal(id, v) { var el = document.getElementById(id); if (el) el.value = v; }
|
|
function getText(id) {
|
|
var el = document.getElementById(id);
|
|
if (!el) return '';
|
|
return (el.innerText || el.textContent || '').trim();
|
|
}
|
|
function setText(id, v) {
|
|
var el = document.getElementById(id);
|
|
if (!el) return;
|
|
if (typeof setOutputText === 'function' && id === 'ed-note-text') setOutputText(el, v);
|
|
else el.textContent = v;
|
|
}
|
|
function showEl(id) { var el = document.getElementById(id); if (el) el.classList.remove('hidden'); }
|
|
function hideEl(id) { var el = document.getElementById(id); if (el) el.classList.add('hidden'); }
|
|
function escHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
|
|
|
function updateStageTags() {
|
|
var s = _state.stage;
|
|
var badge = document.getElementById('ed-stage-badge');
|
|
var rec = document.getElementById('ed-rec-stage-num');
|
|
var gen = document.getElementById('ed-gen-stage-num');
|
|
var tag = document.getElementById('ed-note-stage-tag');
|
|
if (badge) badge.textContent = _state.finalized ? 'Finalized' : 'Stage ' + s;
|
|
if (rec) rec.textContent = String(s);
|
|
if (gen) gen.textContent = String(s);
|
|
if (tag) tag.textContent = 'Stage ' + s;
|
|
}
|
|
|
|
function showOutput() {
|
|
var card = document.getElementById('ed-note-output');
|
|
if (card) { card.classList.remove('hidden'); card.scrollIntoView({ behavior: 'smooth' }); }
|
|
}
|
|
|
|
// ── Recording ────────────────────────────────────────────────────────
|
|
function initRecording() {
|
|
var recordBtn = document.getElementById('ed-record-btn');
|
|
var pauseBtn = document.getElementById('ed-pause-btn');
|
|
var indicator = document.getElementById('ed-rec-indicator');
|
|
var timerEl = document.getElementById('ed-timer');
|
|
var transcriptEl = document.getElementById('ed-transcript');
|
|
if (!recordBtn) return;
|
|
|
|
_timer = createTimer(timerEl);
|
|
_recognition = createSpeechRecognition();
|
|
if (_recognition) {
|
|
_recognition.onresult = function(e) {
|
|
var interim = '';
|
|
for (var i = e.resultIndex; i < e.results.length; i++) {
|
|
if (e.results[i].isFinal) _liveTranscript += e.results[i][0].transcript + ' ';
|
|
else interim = e.results[i][0].transcript;
|
|
}
|
|
if (transcriptEl) transcriptEl.innerHTML = _liveTranscript + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
|
};
|
|
_recognition.onend = function() { if (_recording && !_paused) try { _recognition.start(); } catch(e) {} };
|
|
}
|
|
|
|
recordBtn.addEventListener('click', function() {
|
|
if (!_recording) {
|
|
_liveTranscript = '';
|
|
_recorder = new AudioRecorder();
|
|
_recorder.start().then(function() {
|
|
_recording = true; _paused = false;
|
|
recordBtn.innerHTML = '<i class="fas fa-stop"></i> Stop';
|
|
recordBtn.classList.add('recording');
|
|
if (pauseBtn) pauseBtn.classList.remove('hidden');
|
|
indicator.classList.remove('hidden');
|
|
_timer.start();
|
|
if (_recognition) try { _recognition.start(); } catch(e) {}
|
|
showToast('ED recording started — Stage ' + _state.stage, 'info');
|
|
document.dispatchEvent(new CustomEvent('recording-started', { detail: { module: 'ed' } }));
|
|
}).catch(function() { showToast('Microphone denied', 'error'); });
|
|
} else {
|
|
_recording = false; _paused = false;
|
|
var dur = _timer.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 (_recognition) try { _recognition.stop(); } catch(e) {}
|
|
document.dispatchEvent(new CustomEvent('recording-stopped', { detail: { module: 'ed' } }));
|
|
|
|
showBusy('Transcribing...');
|
|
_recorder.stop().then(function(blob) {
|
|
if (!blob || blob.size === 0) {
|
|
hideBusy();
|
|
if (_liveTranscript.trim() && transcriptEl) transcriptEl.textContent = _liveTranscript.trim();
|
|
persistLocal();
|
|
return;
|
|
}
|
|
return transcribeAudio(blob).then(function(data) {
|
|
hideBusy();
|
|
if (data && data.success) {
|
|
if (transcriptEl) transcriptEl.textContent = data.text;
|
|
showToast('Transcribed ' + dur + 's', 'success');
|
|
} else if (_liveTranscript.trim() && transcriptEl) {
|
|
transcriptEl.textContent = _liveTranscript.trim();
|
|
}
|
|
persistLocal();
|
|
});
|
|
}).catch(function() { hideBusy(); });
|
|
}
|
|
});
|
|
|
|
if (pauseBtn) {
|
|
pauseBtn.addEventListener('click', function() {
|
|
if (!_recording || !_recorder || !_recorder.mediaRecorder) return;
|
|
if (!_paused) {
|
|
try { if (_recorder.mediaRecorder.state === 'recording') _recorder.mediaRecorder.pause(); } catch(e) {}
|
|
_timer.stop(); _paused = true;
|
|
pauseBtn.innerHTML = '<i class="fas fa-play"></i> Resume';
|
|
if (_recognition) try { _recognition.stop(); } catch(e) {}
|
|
} else {
|
|
try {
|
|
if (_recorder.mediaRecorder.state === 'paused') { _recorder.mediaRecorder.resume(); }
|
|
else if (_recorder.mediaRecorder.state === 'inactive' && _recorder.stream && _recorder.stream.active) {
|
|
var mime = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
|
|
_recorder.mediaRecorder = new MediaRecorder(_recorder.stream, { mimeType: mime, audioBitsPerSecond: 32000 });
|
|
_recorder.mediaRecorder.ondataavailable = function(e) { if (e.data.size > 0) _recorder.chunks.push(e.data); };
|
|
_recorder.mediaRecorder.start(1000);
|
|
}
|
|
} catch(e) {}
|
|
_timer.resume(); _paused = false;
|
|
pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
|
|
if (_recognition) try { _recognition.start(); } catch(e) {}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── Generate (per-stage) ─────────────────────────────────────────────
|
|
function generateStage() {
|
|
var cc = getVal('ed-cc');
|
|
if (!cc.trim()) { showToast('Enter a chief complaint first', 'error'); return; }
|
|
var transcript = getText('ed-transcript');
|
|
if (!transcript) { showToast('No transcript — record or type the dictation first', 'error'); return; }
|
|
|
|
var modelEl = document.getElementById('ed-model-select');
|
|
var selectedModel = modelEl ? modelEl.value : '';
|
|
|
|
showBusy('Generating ED note (Stage ' + _state.stage + ')...');
|
|
|
|
var memoriesPromise = (typeof getUserMemoryContext === 'function') ? getUserMemoryContext() : Promise.resolve('');
|
|
|
|
memoriesPromise.then(function(memCtx) {
|
|
var prevNote = (_state.stages.length > 0) ? _state.stages[_state.stages.length - 1].note : '';
|
|
return fetch('/api/ed-encounters/generate', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
stage: _state.stage,
|
|
transcript: transcript,
|
|
chiefComplaint: cc,
|
|
patientAge: getVal('ed-age'),
|
|
patientGender: getVal('ed-gender'),
|
|
previousNote: prevNote || undefined,
|
|
physicianMemories: memCtx || undefined,
|
|
model: selectedModel || undefined
|
|
})
|
|
});
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
hideBusy();
|
|
if (!data.success) { showToast(data.error || 'Generation failed', 'error'); return; }
|
|
|
|
_state.stages.push({
|
|
transcript: transcript,
|
|
note: data.note || '',
|
|
dontMiss: data.dontMiss || [],
|
|
model: data.model || '',
|
|
generatedAt: new Date().toISOString()
|
|
});
|
|
|
|
setText('ed-note-text', data.note || '');
|
|
var modelTag = document.getElementById('ed-note-model-tag');
|
|
if (modelTag) modelTag.textContent = (data.model || '').split('/').pop();
|
|
renderDontMiss(data.dontMiss || []);
|
|
updateStageTags();
|
|
showOutput();
|
|
persistLocal();
|
|
autoSaveDraft(); // best-effort DB draft save on stage completion
|
|
showToast('Stage ' + _state.stage + ' generated. Save & Done, or Add more.', 'success');
|
|
})
|
|
.catch(function(err) { hideBusy(); showToast(err.message, 'error'); });
|
|
}
|
|
|
|
// ── Don't-miss rendering ─────────────────────────────────────────────
|
|
function renderDontMiss(items) {
|
|
var card = document.getElementById('ed-dontmiss-card');
|
|
var list = document.getElementById('ed-dontmiss-list');
|
|
if (!card || !list) return;
|
|
if (!items || !items.length) {
|
|
card.classList.add('hidden');
|
|
list.innerHTML = '';
|
|
return;
|
|
}
|
|
list.innerHTML = items.map(function(it) {
|
|
var why = it.why ? '<div style="font-size:11px;color:var(--g500);margin-top:2px;">' + escHtml(it.why) + '</div>' : '';
|
|
return '<div style="padding:6px 0;border-bottom:1px solid var(--g100);">' +
|
|
'<div style="font-size:13px;color:var(--g800);"><i class="fas fa-circle-exclamation" style="color:#f59e0b;font-size:11px;margin-right:6px;"></i>' + escHtml(it.point) + '</div>' +
|
|
why + '</div>';
|
|
}).join('');
|
|
card.classList.remove('hidden');
|
|
}
|
|
|
|
// ── Add another stage ────────────────────────────────────────────────
|
|
function advanceStage() {
|
|
// Lock in any physician edits to the displayed note before moving on
|
|
var edited = getText('ed-note-text');
|
|
if (edited && _state.stages.length) {
|
|
_state.stages[_state.stages.length - 1].note = edited;
|
|
}
|
|
_state.stage += 1;
|
|
setText('ed-transcript', '');
|
|
_liveTranscript = '';
|
|
updateStageTags();
|
|
persistLocal();
|
|
showToast('Ready for Stage ' + _state.stage + ' — record or type additional findings.', 'info');
|
|
var rec = document.getElementById('ed-record-btn');
|
|
if (rec) rec.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
|
|
// ── Finalize → MDM + persist as final ────────────────────────────────
|
|
function finalize() {
|
|
var noteEl = document.getElementById('ed-note-text');
|
|
var note = noteEl ? (noteEl.innerText || noteEl.textContent || '').trim() : '';
|
|
if (!note) { showToast('Generate a note first', 'error'); return; }
|
|
var label = getVal('ed-label');
|
|
if (!label.trim()) { showToast('Enter a patient label before finalizing', 'error'); return; }
|
|
|
|
// Lock in physician edits
|
|
if (_state.stages.length) _state.stages[_state.stages.length - 1].note = note;
|
|
|
|
var modelEl = document.getElementById('ed-model-select');
|
|
var selectedModel = modelEl ? modelEl.value : '';
|
|
var fullTranscript = _state.stages.map(function(s, i) {
|
|
return '--- Stage ' + (i + 1) + ' ---\n' + (s.transcript || '');
|
|
}).join('\n\n');
|
|
|
|
showBusy('Generating MDM and finalizing...');
|
|
fetch('/api/ed-encounters/finalize', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
note: note,
|
|
transcript: fullTranscript,
|
|
patientAge: getVal('ed-age'),
|
|
model: selectedModel || undefined
|
|
})
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (!data.success) { hideBusy(); showToast(data.error || 'MDM generation failed', 'error'); return; }
|
|
_state.mdm = data.mdm;
|
|
_state.finalized = true;
|
|
renderMdm(data.mdm);
|
|
|
|
// Final save: encounter row with status='final', full transcript +
|
|
// note + MDM bundled into partial_data so resume works.
|
|
var partial = { stages: _state.stages, mdm: data.mdm, finalized: true };
|
|
if (typeof saveEncounter === 'function') {
|
|
saveEncounter({
|
|
id: window._savedEncId_ed || null,
|
|
label: label,
|
|
enc_type: 'ed',
|
|
transcript: fullTranscript,
|
|
generated_note: composeFinalNote(note, data.mdm),
|
|
partial_data: partial,
|
|
status: 'final',
|
|
idempotency_key: 'ed-final-' + Date.now(),
|
|
onSaved: function(id) {
|
|
window._savedEncId_ed = id;
|
|
try { sessionStorage.setItem('_savedEncId_ed', id); } catch(e) {}
|
|
clearLocal();
|
|
hideBusy();
|
|
updateStageTags();
|
|
showToast('Encounter finalized and saved.', 'success');
|
|
}
|
|
});
|
|
} else {
|
|
hideBusy();
|
|
}
|
|
})
|
|
.catch(function(err) { hideBusy(); showToast(err.message, 'error'); });
|
|
}
|
|
|
|
function composeFinalNote(noteText, mdm) {
|
|
if (!mdm) return noteText;
|
|
var lines = [
|
|
noteText,
|
|
'',
|
|
'Medical Decision Making (2023 E/M):',
|
|
'Problems Addressed (' + (mdm.problemsAddressed || '') + '): ' + (mdm.problemsNarrative || ''),
|
|
'Data Reviewed (' + (mdm.dataReviewed || '') + '): ' + (mdm.dataNarrative || ''),
|
|
'Risk (' + (mdm.risk || '') + '): ' + (mdm.riskNarrative || ''),
|
|
'Suggested Level: ' + (mdm.suggestedLevel || '') + ' — ' + (mdm.levelRationale || '')
|
|
];
|
|
return lines.join('\n');
|
|
}
|
|
|
|
function renderMdm(mdm) {
|
|
var card = document.getElementById('ed-mdm-card');
|
|
var text = document.getElementById('ed-mdm-text');
|
|
var tag = document.getElementById('ed-mdm-level-tag');
|
|
if (!card || !text || !mdm) return;
|
|
if (tag) tag.textContent = mdm.suggestedLevel || '';
|
|
var html =
|
|
'<div style="margin-bottom:6px;"><strong>Problems Addressed</strong> (' + escHtml(mdm.problemsAddressed) + ')<br>' + escHtml(mdm.problemsNarrative) + '</div>' +
|
|
'<div style="margin-bottom:6px;"><strong>Data Reviewed</strong> (' + escHtml(mdm.dataReviewed) + ')<br>' + escHtml(mdm.dataNarrative) + '</div>' +
|
|
'<div style="margin-bottom:6px;"><strong>Risk</strong> (' + escHtml(mdm.risk) + ')<br>' + escHtml(mdm.riskNarrative) + '</div>' +
|
|
'<div style="padding-top:8px;border-top:1px solid var(--g100);"><strong>Suggested Level: ' + escHtml(mdm.suggestedLevel) + '</strong><br>' +
|
|
'<span style="font-size:12px;color:var(--g600);">' + escHtml(mdm.levelRationale) + '</span></div>' +
|
|
'<p style="font-size:10px;color:var(--g400);margin:8px 0 0;">Suggestion only. Verify against your institution\'s coding guidelines.</p>';
|
|
text.innerHTML = html;
|
|
card.classList.remove('hidden');
|
|
card.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
|
|
// ── Save draft (manual / auto on stage advance) ──────────────────────
|
|
function autoSaveDraft() {
|
|
var label = getVal('ed-label');
|
|
if (!label.trim()) return; // need a label to save; skip silently
|
|
if (typeof saveEncounter !== 'function') return;
|
|
var fullTranscript = _state.stages.map(function(s) { return s.transcript || ''; }).join('\n\n');
|
|
var partial = { stages: _state.stages, finalized: false };
|
|
saveEncounter({
|
|
id: window._savedEncId_ed || null,
|
|
label: label,
|
|
enc_type: 'ed',
|
|
transcript: fullTranscript,
|
|
generated_note: lastStage() ? lastStage().note : '',
|
|
partial_data: partial,
|
|
status: 'draft',
|
|
idempotency_key: 'ed-draft-' + (window._savedEncId_ed || 'new'),
|
|
onSaved: function(id) {
|
|
window._savedEncId_ed = id;
|
|
try { sessionStorage.setItem('_savedEncId_ed', id); } catch(e) {}
|
|
}
|
|
});
|
|
}
|
|
|
|
function manualSaveDraft() {
|
|
if (!getVal('ed-label').trim()) { showToast('Enter a patient label first', 'error'); return; }
|
|
autoSaveDraft();
|
|
showToast('Draft saved', 'success');
|
|
}
|
|
|
|
// ── Reset / new patient ──────────────────────────────────────────────
|
|
function resetEncounter() {
|
|
_state = freshState();
|
|
setVal('ed-label', '');
|
|
setVal('ed-age', '');
|
|
setVal('ed-gender', '');
|
|
setVal('ed-cc', '');
|
|
setText('ed-transcript', '');
|
|
setText('ed-note-text', '');
|
|
_liveTranscript = '';
|
|
hideEl('ed-note-output');
|
|
hideEl('ed-dontmiss-card');
|
|
hideEl('ed-mdm-card');
|
|
var modelTag = document.getElementById('ed-note-model-tag');
|
|
if (modelTag) modelTag.textContent = '';
|
|
window._savedEncId_ed = null;
|
|
try { sessionStorage.removeItem('_savedEncId_ed'); } catch(e) {}
|
|
clearLocal();
|
|
updateStageTags();
|
|
showToast('ED encounter cleared for new patient', 'info');
|
|
}
|
|
|
|
// ── Wire click handlers ──────────────────────────────────────────────
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target.closest('#btn-ed-generate')) generateStage();
|
|
if (e.target.closest('#btn-ed-add-more')) advanceStage();
|
|
if (e.target.closest('#btn-ed-finalize')) finalize();
|
|
if (e.target.closest('#btn-ed-new')) resetEncounter();
|
|
if (e.target.closest('#btn-ed-save')) manualSaveDraft();
|
|
if (e.target.closest('#ed-refine-btn')) refineDocument('ed-note-text', 'ed-refine-input');
|
|
if (e.target.closest('#ed-shorten-btn')) shortenDocument('ed-note-text');
|
|
});
|
|
|
|
// Persist on input changes
|
|
document.addEventListener('input', function(e) {
|
|
if (!e.target) return;
|
|
var id = e.target.id;
|
|
if (id === 'ed-label' || id === 'ed-age' || id === 'ed-gender' || id === 'ed-cc' || id === 'ed-transcript' || id === 'ed-note-text') {
|
|
persistLocal();
|
|
}
|
|
});
|
|
|
|
// ── Load handler (resume from saved encounters list) ────────────────
|
|
if (typeof registerEncounterLoadHandler === 'function') {
|
|
registerEncounterLoadHandler('ed', function(enc) {
|
|
// Reset and restore from the row
|
|
_state = freshState();
|
|
setVal('ed-label', enc.label || '');
|
|
var partial = enc.partial_data;
|
|
try { if (typeof partial === 'string') partial = JSON.parse(partial); } catch (e) { partial = null; }
|
|
if (partial && partial.stages) {
|
|
_state.stages = partial.stages;
|
|
_state.stage = partial.stages.length || 1;
|
|
_state.finalized = !!partial.finalized;
|
|
_state.mdm = partial.mdm || null;
|
|
var last = lastStage();
|
|
if (last) {
|
|
setText('ed-note-text', last.note || '');
|
|
renderDontMiss(last.dontMiss || []);
|
|
showOutput();
|
|
}
|
|
if (_state.mdm) renderMdm(_state.mdm);
|
|
} else if (enc.generated_note) {
|
|
setText('ed-note-text', enc.generated_note);
|
|
showOutput();
|
|
}
|
|
if (enc.transcript) setText('ed-transcript', enc.transcript);
|
|
updateStageTags();
|
|
});
|
|
}
|
|
|
|
// ── Init on tab activation ───────────────────────────────────────────
|
|
var _inited = false;
|
|
document.addEventListener('tabChanged', function(e) {
|
|
if (e.detail.tab !== 'ed' || _inited) return;
|
|
_inited = true;
|
|
initRecording();
|
|
loadLocal();
|
|
updateStageTags();
|
|
});
|
|
|
|
console.log('ED encounters module loaded');
|
|
})();
|