// ============================================================ // MODULE 3: DEVELOPMENTAL MILESTONES → NARRATIVE // COMPLETELY SEPARATE FROM HPI MODULES 1 & 2 // Uses AI to convert checked milestones into sentences // ============================================================ (function() { var ageSelect = document.getElementById('ms-age-group'); var checklist = document.getElementById('milestone-checklist'); var actionsBar = document.getElementById('milestone-actions'); var allYesBtn = document.getElementById('ms-all-yes'); var allClearBtn = document.getElementById('ms-all-clear'); var generateBtn = document.getElementById('ms-generate-btn'); var outputCard = document.getElementById('ms-output'); var summaryBar = document.getElementById('ms-summary-bar'); var narrativeText = document.getElementById('ms-narrative-text'); var modelUsed = document.getElementById('ms-model-used'); // State: { "domain-0": { domain, milestone, status: null|'yes'|'no' } } var state = {}; // When age group changes, rebuild checklist ageSelect.addEventListener('change', function() { var age = ageSelect.value; state = {}; outputCard.classList.add('hidden'); if (!age || !MILESTONES_DATA[age]) { checklist.innerHTML = '

Select an age group to load milestones.

'; actionsBar.style.display = 'none'; return; } renderChecklist(MILESTONES_DATA[age]); actionsBar.style.display = 'flex'; }); function renderChecklist(data) { checklist.innerHTML = ''; Object.keys(data).forEach(function(domain) { var items = data[domain]; var config = DOMAIN_CONFIG[domain] || { icon: '📋', css: '' }; var section = document.createElement('div'); section.className = 'domain-section ' + config.css; var itemsHtml = ''; items.forEach(function(milestone, idx) { var id = domain + '-' + idx; state[id] = { domain: domain, milestone: milestone, status: null }; itemsHtml += '
' + '
' + '' + '' + '
' + '' + milestone + '' + '
'; }); section.innerHTML = '
' + '' + config.icon + '' + '' + domain + '' + '' + items.length + ' items' + '
' + '
' + itemsHtml + '
'; checklist.appendChild(section); }); // Attach click handlers checklist.querySelectorAll('.toggle-btn').forEach(function(btn) { btn.addEventListener('click', function() { handleToggle(btn.getAttribute('data-id'), btn.getAttribute('data-action')); }); }); } function safeId(str) { return str.replace(/[^a-zA-Z0-9]/g, '_'); } function handleToggle(id, action) { var sid = safeId(id); var item = document.getElementById('item-' + sid); var yesBtn = item.querySelector('[data-action="yes"]'); var noBtn = item.querySelector('[data-action="no"]'); var current = state[id].status; if (action === 'yes') { if (current === 'yes') { // Deselect state[id].status = null; yesBtn.classList.remove('yes-on'); item.classList.remove('is-yes', 'is-no'); } else { state[id].status = 'yes'; yesBtn.classList.add('yes-on'); noBtn.classList.remove('no-on'); item.classList.add('is-yes'); item.classList.remove('is-no'); } } else { if (current === 'no') { state[id].status = null; noBtn.classList.remove('no-on'); item.classList.remove('is-yes', 'is-no'); } else { state[id].status = 'no'; noBtn.classList.add('no-on'); yesBtn.classList.remove('yes-on'); item.classList.remove('is-yes'); item.classList.add('is-no'); } } updateBadges(); } function updateBadges() { var counts = {}; Object.keys(state).forEach(function(id) { var d = state[id].domain; if (!counts[d]) counts[d] = { total: 0, yes: 0, no: 0 }; counts[d].total++; if (state[id].status === 'yes') counts[d].yes++; if (state[id].status === 'no') counts[d].no++; }); Object.keys(counts).forEach(function(domain) { var badge = document.getElementById('badge-' + safeId(domain)); if (badge) { var c = counts[domain]; var assessed = c.yes + c.no; badge.textContent = assessed > 0 ? c.yes + '✓ ' + c.no + '✗ / ' + c.total : c.total + ' items'; } }); } // All Yes allYesBtn.addEventListener('click', function() { Object.keys(state).forEach(function(id) { state[id].status = 'yes'; var sid = safeId(id); var item = document.getElementById('item-' + sid); if (item) { item.querySelector('[data-action="yes"]').classList.add('yes-on'); item.querySelector('[data-action="no"]').classList.remove('no-on'); item.classList.add('is-yes'); item.classList.remove('is-no'); } }); updateBadges(); showToast('All marked as achieved', 'success'); }); // Clear All allClearBtn.addEventListener('click', function() { Object.keys(state).forEach(function(id) { state[id].status = null; var sid = safeId(id); var item = document.getElementById('item-' + sid); if (item) { item.querySelector('[data-action="yes"]').classList.remove('yes-on'); item.querySelector('[data-action="no"]').classList.remove('no-on'); item.classList.remove('is-yes', 'is-no'); } }); updateBadges(); outputCard.classList.add('hidden'); showToast('All cleared', 'info'); }); // GENERATE NARRATIVE // Sends ONLY assessed milestones to AI // AI converts them into professional narrative sentences generateBtn.addEventListener('click', function() { var ageGroup = ageSelect.value; if (!ageGroup) { showToast('Select an age group first', 'error'); return; } // Collect all milestone data (AI will only use yes/no, ignore null) var allMilestones = Object.keys(state).map(function(id) { return state[id]; }); var assessed = allMilestones.filter(function(m) { return m.status !== null; }); if (assessed.length === 0) { showToast('Please assess at least one milestone', 'error'); return; } showLoading('AI is generating developmental narrative...'); fetch('/api/generate-milestone-narrative', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ milestones: allMilestones, ageGroup: ageGroup, patientAge: document.getElementById('ms-patient-age').value, patientGender: document.getElementById('ms-patient-gender').value, model: getSelectedModel() }) }) .then(function(r) { return r.json(); }) .then(function(data) { hideLoading(); if (data.success) { narrativeText.textContent = data.narrative; modelUsed.textContent = (data.model || '').split('/').pop(); // Summary bar if (data.summary) { summaryBar.innerHTML = '
Achieved: ' + data.summary.achieved + '
' + '
Not Yet: ' + data.summary.notAchieved + '
' + '
Not Assessed: ' + data.summary.notAssessed + ' (omitted)
'; summaryBar.classList.remove('hidden'); } outputCard.classList.remove('hidden'); outputCard.scrollIntoView({ behavior: 'smooth' }); showToast('Developmental narrative generated!', 'success'); } else { showToast(data.error || 'Failed', 'error'); } }) .catch(function(err) { hideLoading(); showToast('Error: ' + err.message, 'error'); }); }); console.log('✅ Milestones module loaded (separate from HPI)'); // ============================================================ // 3-SENTENCE SUMMARY GENERATOR // Takes the full narrative and condenses it // ============================================================ var quickSummaryBtn = document.getElementById('ms-quick-summary-btn'); var quickSummaryDiv = document.getElementById('ms-quick-summary'); var summaryText = document.getElementById('ms-summary-text'); var summaryModelUsed = document.getElementById('ms-summary-model-used'); quickSummaryBtn.addEventListener('click', function() { var fullNarrative = narrativeText.innerText.trim(); if (!fullNarrative) { showToast('Generate the full narrative first', 'error'); return; } // Disable button and show loading state quickSummaryBtn.disabled = true; quickSummaryBtn.classList.add('loading'); var originalHTML = quickSummaryBtn.innerHTML; quickSummaryBtn.innerHTML = ' Summarizing...'; fetch('/api/generate-milestone-summary', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ narrative: fullNarrative, ageGroup: ageSelect.value, patientAge: document.getElementById('ms-patient-age').value, patientGender: document.getElementById('ms-patient-gender').value, model: getSelectedModel() }) }) .then(function(r) { return r.json(); }) .then(function(data) { quickSummaryBtn.disabled = false; quickSummaryBtn.classList.remove('loading'); quickSummaryBtn.innerHTML = ' Regenerate Summary'; if (data.success) { summaryText.textContent = data.summary; summaryModelUsed.textContent = (data.model || '').split('/').pop(); quickSummaryDiv.classList.remove('hidden'); quickSummaryDiv.scrollIntoView({ behavior: 'smooth' }); showToast('3-sentence summary generated!', 'success'); } else { quickSummaryBtn.innerHTML = originalHTML; showToast(data.error || 'Summary failed', 'error'); } }) .catch(function(err) { quickSummaryBtn.disabled = false; quickSummaryBtn.classList.remove('loading'); quickSummaryBtn.innerHTML = originalHTML; showToast('Error: ' + err.message, 'error'); }); }); console.log('✅ 3-sentence summary feature loaded'); })();