Features: - Admin can add, edit, and delete developmental milestones via dashboard - Milestones stored in PostgreSQL (developmental_milestones table) - Client-side loads milestones from API instead of static file - Import script to migrate existing static data to database - Organized by age group and domain - Supports sorting and filtering Admin UI: - New section in Admin panel for milestone management - Filter by age group - Add/Edit modal with validation - Delete with confirmation - Auto-complete for age groups and domains API Endpoints: - GET /api/milestones-data - Public endpoint for authenticated users - GET /api/admin/milestones - List all milestones (admin only) - GET /api/admin/milestones/meta - Get age groups and domains - POST /api/admin/milestones - Create milestone - PUT /api/admin/milestones/:id - Update milestone - DELETE /api/admin/milestones/:id - Delete milestone - POST /api/admin/milestones/bulk-import - Bulk import Usage: 1. Run import script: node scripts/import-milestones.js 2. Access Admin dashboard → Developmental Milestones section 3. Add/Edit/Delete milestones as needed
93 lines
3.6 KiB
JavaScript
93 lines
3.6 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { callAI } = require('../utils/ai');
|
|
const PROMPTS = require('../utils/prompts');
|
|
const { authMiddleware } = require('../middleware/auth');
|
|
const db = require('../db/database');
|
|
|
|
// Get all milestones for client (authenticated users)
|
|
router.get('/milestones-data', authMiddleware, async (req, res) => {
|
|
try {
|
|
const result = await db.query(
|
|
'SELECT * FROM developmental_milestones ORDER BY age_group, domain, sort_order, id'
|
|
);
|
|
|
|
// Group by age_group and domain to match the static data structure
|
|
const grouped = {};
|
|
result.rows.forEach(row => {
|
|
if (!grouped[row.age_group]) {
|
|
grouped[row.age_group] = {};
|
|
}
|
|
if (!grouped[row.age_group][row.domain]) {
|
|
grouped[row.age_group][row.domain] = [];
|
|
}
|
|
grouped[row.age_group][row.domain].push(row.milestone_text);
|
|
});
|
|
|
|
res.json({ success: true, milestones: grouped });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Narrative format
|
|
router.post('/generate-milestone-narrative', authMiddleware, async (req, res) => {
|
|
try {
|
|
const { milestones, ageGroup, patientAge, patientGender, model, format } = req.body;
|
|
if (!milestones) return res.status(400).json({ error: 'No milestones' });
|
|
|
|
const achieved = milestones.filter(m => m.status === 'yes');
|
|
const notAchieved = milestones.filter(m => m.status === 'no');
|
|
const notAssessed = milestones.filter(m => m.status === null);
|
|
|
|
if (achieved.length === 0 && notAchieved.length === 0) {
|
|
return res.json({ success: true, narrative: 'No developmental milestones were assessed during this visit.', summary: { achieved: 0, notAchieved: 0, notAssessed: notAssessed.length } });
|
|
}
|
|
|
|
let milestoneText = '';
|
|
if (achieved.length > 0) {
|
|
milestoneText += 'ACHIEVED:\n';
|
|
achieved.forEach(m => { milestoneText += ` - [${m.domain}] ${m.milestone}\n`; });
|
|
}
|
|
if (notAchieved.length > 0) {
|
|
milestoneText += '\nNOT YET ACHIEVED:\n';
|
|
notAchieved.forEach(m => { milestoneText += ` - [${m.domain}] ${m.milestone}\n`; });
|
|
}
|
|
|
|
// Choose narrative or list format
|
|
const prompt = (format === 'list') ? PROMPTS.milestoneList : PROMPTS.milestoneNarrative;
|
|
|
|
const result = await callAI([
|
|
{ role: 'system', content: prompt },
|
|
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\n${milestoneText}\n\nAssessed: ${achieved.length + notAchieved.length} | Achieved: ${achieved.length} | Not achieved: ${notAchieved.length} | Not assessed (OMIT): ${notAssessed.length}` }
|
|
], { model });
|
|
|
|
res.json({
|
|
success: true,
|
|
narrative: result.content,
|
|
model: result.model,
|
|
summary: { achieved: achieved.length, notAchieved: notAchieved.length, notAssessed: notAssessed.length }
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// 3-sentence summary
|
|
router.post('/generate-milestone-summary', authMiddleware, async (req, res) => {
|
|
try {
|
|
const { narrative, ageGroup, patientAge, patientGender, model } = req.body;
|
|
if (!narrative) return res.status(400).json({ error: 'No narrative' });
|
|
|
|
const result = await callAI([
|
|
{ role: 'system', content: PROMPTS.milestoneSummary },
|
|
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\nNARRATIVE:\n${narrative}` }
|
|
], { model, maxTokens: 500 });
|
|
|
|
res.json({ success: true, summary: result.content, model: result.model });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|