/** * Lightweight heuristics for showing trust signals on AI answers without * making any extra model calls. * * 1. `estimateConfidence(text)` — classifies a response as low / medium / * high based on hedge-word density vs. structured-section density. * 2. `extractCitations(text)` — finds references to authoritative * medical bodies (WHO, CDC, NHS, NIH, ICD, EMA, Mayo, Cochrane) and * returns them as clickable chip descriptors pointing at each body's * official patient-facing landing page. * * Kept pure / dependency-free so it's easy to unit-test and runs on both * server and client. */ export type Confidence = 'low' | 'medium' | 'high'; const HEDGE_WORDS = [ 'might', 'may', 'could', 'possibly', 'possible', 'uncertain', "it's hard to say", 'hard to say', 'unclear', 'perhaps', 'seems', "i'm not sure", 'not sure', 'difficult to say', ]; /** * Look for the structured section markers from the medical-knowledge * output contract (**Summary**, **What it could be**, etc). Their * presence is a strong signal that the model followed the contract — * we lift confidence accordingly. */ const STRUCTURE_MARKERS = [ '**summary**', '**what it could be**', '**self-care**', '**when to seek care**', '**red flags**', ]; export function estimateConfidence(text: string): Confidence { if (!text || text.length < 40) return 'low'; const lower = text.toLowerCase(); let hedges = 0; for (const h of HEDGE_WORDS) { // count occurrences (bounded to avoid quadratic on huge inputs) let idx = lower.indexOf(h); while (idx !== -1 && hedges < 10) { hedges++; idx = lower.indexOf(h, idx + h.length); } } let sections = 0; for (const m of STRUCTURE_MARKERS) { if (lower.includes(m)) sections++; } // Rules of thumb: full structure + few hedges = high; lots of hedges // and no structure = low; everything else = medium. if (sections >= 3 && hedges <= 3) return 'high'; if (sections === 0 && hedges >= 4) return 'low'; return 'medium'; } export const CONFIDENCE_LABELS: Record = { low: 'Exploratory', medium: 'Guidance', high: 'Aligned with guidelines', }; export const CONFIDENCE_COLORS: Record = { low: 'text-amber-300 border-amber-500/30 bg-amber-500/10', medium: 'text-sky-300 border-sky-500/30 bg-sky-500/10', high: 'text-emerald-300 border-emerald-500/30 bg-emerald-500/10', }; // --------------------------------------------------------------------- export interface Citation { id: string; label: string; url: string; } /** * Each citation body gets a stable label + a public patient-facing URL. * Matching is case-insensitive and tolerant of punctuation. */ const CITATION_PATTERNS: Array<{ id: string; re: RegExp; label: string; url: string }> = [ { id: 'who', re: /\bWHO\b|World Health Organization/i, label: 'WHO', url: 'https://www.who.int/health-topics' }, { id: 'cdc', re: /\bCDC\b|Centers for Disease Control/i, label: 'CDC', url: 'https://www.cdc.gov/health-topics.html' }, { id: 'nhs', re: /\bNHS\b|National Health Service/i, label: 'NHS', url: 'https://www.nhs.uk/conditions/' }, { id: 'nih', re: /\bNIH\b|MedlinePlus|National Institutes of Health/i, label: 'NIH / MedlinePlus', url: 'https://medlineplus.gov/' }, { id: 'icd', re: /\bICD-?(10|11)\b/i, label: 'ICD-11',url: 'https://icd.who.int/' }, { id: 'bnf', re: /\bBNF\b|British National Formulary/i, label: 'BNF', url: 'https://bnf.nice.org.uk/' }, { id: 'ema', re: /\bEMA\b|European Medicines Agency/i, label: 'EMA', url: 'https://www.ema.europa.eu/en/medicines' }, { id: 'mayo', re: /Mayo Clinic/i, label: 'Mayo Clinic', url: 'https://www.mayoclinic.org/diseases-conditions' }, { id: 'cochrane',re: /Cochrane/i, label: 'Cochrane', url: 'https://www.cochranelibrary.com/' }, { id: 'sie', re: /\bSIE\b|Società Italiana di Endocrinologia/i, label: 'SIE', url: 'https://www.societaitalianadiendocrinologia.it/' }, { id: 'sid', re: /\bSID\b|Società Italiana di Diabetologia/i, label: 'SID', url: 'https://www.siditalia.it/' }, { id: 'ada', re: /\bADA\b|American Diabetes Association/i, label: 'ADA', url: 'https://diabetes.org/about-diabetes' }, { id: 'eta', re: /\bETA\b|European Thyroid Association/i, label: 'ETA', url: 'https://www.eurothyroid.com/' }, { id: 'es', re: /Endocrine Society/i, label: 'Endocrine Society', url: 'https://www.endocrine.org/patient-engagement' }, ]; export function extractCitations(text: string): Citation[] { if (!text) return []; const out: Citation[] = []; const seen = new Set(); for (const p of CITATION_PATTERNS) { if (p.re.test(text) && !seen.has(p.id)) { seen.add(p.id); out.push({ id: p.id, label: p.label, url: p.url }); } } return out; }