const { test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const root = path.join(__dirname, '..'); function read(relativePath) { return fs.readFileSync(path.join(root, relativePath), 'utf8'); } test('clinical assistant starter prompts are Redis or indexed-source backed', () => { const route = read('src/routes/clinicalAssistant.js'); const pool = read('src/utils/clinicalPromptPool.js'); assert.doesNotMatch(route, /EXAMPLE_CANDIDATES|TOPIC_SUGGESTIONS|topic_suggestions|buildSuggestionAnswer/); assert.match(route, /createClinicalPromptPool\(\{[\s\S]*redisCache: redisCache/); assert.match(route, /isUsefulIndexedTopicExample/); assert.match(pool, /clinical-assistant:prompt-pool:v2/); assert.match(pool, /redisBaseKey \+ ':all'/); assert.match(pool, /redisBaseKey \+ ':last-good'/); assert.match(pool, /redisBaseKey \+ ':category:' \+ category/); assert.match(pool, /redisBaseKey \+ ':age:' \+ ageBand/); assert.match(pool, /redisBaseKey \+ ':intent:' \+ intent/); assert.match(pool, /CLINICAL_ASSISTANT_PROMPT_POOL_TARGET, 1000/); // Built once, then only from the admin button: no timer unless the environment asks for one. assert.match(pool, /CLINICAL_ASSISTANT_PROMPT_POOL_REFRESH_MS, 0\)/); assert.match(pool, /process\.env\.CLINICAL_ASSISTANT_PROMPT_MODEL \|\| 'openrouter-gpt-4\.1-mini'/); assert.match(route, /loadStoredPromptPool: loadLatestPromptPoolSnapshot/); assert.match(pool, /PEDIATRIC_TAXONOMY/); assert.match(pool, /taxonomyWithQuotas\(target\)/); assert.match(pool, /age_band: ageBand/); assert.match(pool, /function hasPediatricSignal\(prompt, item\)/); assert.match(pool, /var examples = await fallbackIndexedExamples\(\)/); assert.doesNotMatch(pool, /taxonomyFallbackExamples|taxonomySupplementExamples/); assert.doesNotMatch(pool, /return fallbackIndexedExamples\(\)/); }); test('clinical assistant prompt pool can be regenerated by admins', () => { const admin = read('src/routes/adminConfig.js'); const page = read('public/components/admin.html'); const js = read('public/js/admin/clinicalAssistant.js'); assert.match(admin, /\/clinical-assistant\/prompt-pool\/regenerate/); assert.match(admin, /\/clinical-assistant\/prompt-pool\/restore/); assert.match(page, /btn-regenerate-assistant-prompt-pool/); assert.match(page, /btn-restore-assistant-prompt-pool/); assert.match(js, /regenerateAssistantPromptPool/); assert.match(js, /restoreAssistantPromptPool/); }); test('clinical assistant prompt forbids relabeling other sources as named sources', () => { const answer = read('src/utils/clinicalAnswer.js'); assert.match(answer, /If the user names a specific source, textbook, guideline, or table/); assert.match(answer, /do not claim that another source is from the named source/); assert.match(answer, /If the named source is absent from the retrieved sources, say that explicitly/); }); test('clinical assistant prompt forbids LaTeX-escaped citations', () => { const answer = read('src/utils/clinicalAnswer.js'); assert.match(answer, /Never escape citation brackets/); assert.ok(answer.includes(String.raw`write [1], not \\[1\\]`)); }); test('clinical assistant prompt requires bracketed citations in table source columns', () => { const answer = read('src/utils/clinicalAnswer.js'); assert.match(answer, /Source, Source\(s\), Citation, or Citation\(s\) column/); assert.match(answer, /must use bracketed citation tokens like \[1\] or \[1, 3\]/); assert.match(answer, /never bare numbers like 1 or 1, 3/); }); // ---- what a starter question has to be ---------------------------------------- function fakePool(overrides) { const { createClinicalPromptPool } = require('../src/utils/clinicalPromptPool'); const store = new Map(); const redisCache = { getJson: async k => store.has(k) ? store.get(k) : null, setJson: async (k, v) => { store.set(k, v); } }; return { store, pool: createClinicalPromptPool(Object.assign({ redisCache, getSetting: async () => '', semanticSearch: async () => ({}), dedupeSources: x => x, normalizeMcpSearchResponse: () => [], callAI: async () => ({ content: '{"questions":[]}' }) }, overrides || {})) }; } test('a starter question is a case and a decision, not a chapter heading', () => { const { pool } = fakePool(); const ok = q => pool.isUsefulQuestion(q, { category: 'respiratory', intent: 'management', age_band: 'infant' }); // What the old pool was full of. assert.equal(ok('What red flags in a child\'s headache history warrant further investigation?'), false, 'no number, no case'); assert.equal(ok('Which clinical scores are useful for assessing asthma severity in children?'), false, 'a heading'); assert.equal(ok('How is bronchiolitis managed in infants?'), false, 'a heading'); assert.equal(ok('What are the causes of neonatal jaundice presenting at 2 days?'), false, 'a heading with a number in it is still a heading'); // What it should hold. assert.equal(ok('A 6-week-old with 3 days of projectile non-bilious vomiting, weight down 8% and a chloride of 88: which fluid do you start, and what corrects before theatre is safe?'), true); assert.equal(ok('A 14-year-old on the ward with asthma, SpO2 91% after two salbutamol nebulisers: at what point do you add magnesium, and what dose?'), true); assert.equal(ok('In a neonate with bilious emesis and a scaphoid abdomen at 2 days of life, which imaging comes first and what wait is acceptable?'), true); }); test('a pool built by an older prompt is served, then rebuilt in the background', async () => { let generated = 0; const { store, pool } = fakePool({ callAI: async () => { generated++; return { content: '{"questions":[]}' }; } }); // A fresh pool from the previous prompt version, one day old. const old = { generatedAt: Date.now() - 86400000, promptVersion: pool.PROMPT_VERSION - 1, target: 10, examples: [{ label: 'Old', prompt: 'Old question?', category: 'respiratory', age_band: 'infant', intent: 'management' }] }; await pool.writePromptPool(old, { skipStore: true }); const served = await pool.getAvailableExamples(); assert.equal(served[0].prompt, 'Old question?', 'the old pool is what the reader gets meanwhile'); await new Promise(r => setTimeout(r, 20)); assert.ok(generated > 0 || store.size >= 1, 'a rebuild was started'); // The same pool stamped with the current version is not stale. const fresh = Object.assign({}, old, { promptVersion: pool.PROMPT_VERSION }); await pool.writePromptPool(fresh, { skipStore: true }); const again = await pool.refreshIfNeeded(false); assert.equal(again[0].prompt, 'Old question?'); }); test('a reply cut off mid-list still yields the questions that finished', () => { const { pool } = fakePool(); // Reach the parser through the public surface it feeds: a refresh with a // callAI that answers with truncated JSON. const src = require('node:fs').readFileSync(require('node:path').join(__dirname, '..', 'src/utils/clinicalPromptPool.js'), 'utf8'); assert.match(src, /objects\.forEach\(function \(chunk\)/, 'the salvage exists'); assert.match(src, /maxTokens: 7000/, 'and the ceiling makes room for cases'); assert.ok(pool); });