// The fixture's question bank and plan template (YCI-2 to YCI-6, YCI-20). Fictional wording // drawn from the PRD; the real bank is signed off by the clinical safety lead. import type * as T from "../../src/api/types"; import { pronouns } from "../../src/logic/format"; type Patient = Pick & { doses: { taken: number; expected: number } | null; }; const greeting = (timeZone: string) => { const hour = Number(new Intl.DateTimeFormat("en-GB", { timeZone, hour: "2-digit", hourCycle: "h23" }).format(new Date())); return hour < 12 ? "Good morning" : hour < 17 ? "Good afternoon" : "Good evening"; }; const suggestion = ( id: string, text: string, reason: string, source: T.QuestionSource, suggested: boolean, followUps: string[] = [], ): T.QuestionSuggestion => ({ id: `s-${id}`, bankId: id, text, reason, source, followUps, suggested }); /** Questions from the findings, the missing factors and the bank, each with why it is there. */ export function suggestions(patient: Patient, check: T.CheckRun | null): T.QuestionSuggestion[] { const her = pronouns(patient.pronoun).possessive; const out: T.QuestionSuggestion[] = []; const symptom = ["When did it start?", "How often?", "How bad is it?"]; for (const finding of check?.assessment.contextual.findings ?? []) { const detail = check!.details[finding.id]; const title = detail?.clinicianTitle ?? finding.title; const from = `From the finding: ${title}`; const source: T.QuestionSource = { kind: "finding", findingId: finding.id, title }; const medicine = finding.medicines[0]?.name ?? "the medicine"; if (finding.ruleId.startsWith("allergy-")) { const allergen = check!.assessment.includedFacts.find((f) => f.kind === "allergy")?.label.toLowerCase() ?? "it"; out.push( suggestion("medicine-bought", `Have you bought the ${medicine}, the new antibiotic?`, from, source, true), suggestion("allergy-history", `Your records say ${allergen} gave you a reaction. What happened?`, from, source, true, [ "How soon after?", "Did your face or lips swell, or was breathing hard?", "Did you go to hospital?", ]), suggestion("medicine-started", "Have you started it?", from, source, false), suggestion("medicine-pack", "What name is on the pack?", from, source, false), ); } else if (finding.ruleId.startsWith("lab-metformin")) { out.push( suggestion("metformin-stomach", "Since starting Metformin, have you had stomach upset or loose stools?", from, source, false, symptom), suggestion("kidney-test", "Have you had a kidney test since October?", from, source, false, ["Where?"]), ); } else if (finding.ruleId.startsWith("lab-potassium")) { out.push(suggestion("potassium-symptoms", "Have you had any muscle weakness, or a fast or uneven heartbeat?", from, source, true, symptom)); } } for (const factor of check?.missingFactors ?? []) if (factor.id === "weight") out.push(suggestion("weight", "Do you know your weight?", `Missing: weight ยท ${factor.reason.toLowerCase()}`, { kind: "missing", factor: "weight" }, false)); if (patient.doses && patient.doses.taken < patient.doses.expected) out.push( suggestion( "missed-doses", "Many people miss doses. Did you miss any this week?", `From ${her} doses: ${patient.doses.taken} of ${patient.doses.expected} taken this week`, { kind: "bank", topic: "doses" }, false, ), ); out.push( suggestion("other-medicines", "Do you take anything from the chemist or the market, or any herbs?", "From the question bank: other medicines", { kind: "bank", topic: "other medicines" }, true, [ "For pain, like Alabukun?", "Which agbo, and how often?", ]), ); const seen = new Set(); return out.filter((s) => !seen.has(s.bankId) && seen.add(s.bankId)); } /** GET /patients/:id/check-ins/plan. */ export function planTemplate( patient: Patient, clinician: T.ClinicianProfile, clinic: T.ClinicProfile, check: T.CheckRun | null, alwaysOpen = false, eligibility: T.CheckInPlanTemplate["eligibility"] = { ok: true, code: null, message: null }, used = 0, ): T.CheckInPlanTemplate { const doctor = clinician.spokenName; const he = pronouns(clinician.pronoun); const emergency = clinic.emergencyNumbers.map((n) => n.number).join(" or "); return { patientId: patient.id, protocolVersion: "yci-protocol-2026-10-30-draft", bankVersion: "yci-bank-2026-10-30-draft", eligibility, urgentFirst: check?.assessment.contextual.findings.some((f) => f.tier === "get_help_now") ? { phone: patient.phone, confirmLine: `I will speak to ${patient.firstName} today.` } : null, fairUse: { used, cap: 30 }, opening: [`${greeting(patient.timeZone)}. I'm Yasmar, MARMAR's computer assistant. I'm not a person. I'm calling for ${doctor}'s clinic.`], identity: [`Am I speaking with ${patient.name}?`, "Please tell me your date of birth."], consent: `Your answers go to ${doctor}'s clinic, which may read them while we talk. MARMAR keeps the text of this call for 12 months. I can't give medical advice, and this isn't an emergency line. You can skip any question or stop at any time. Shall we go on?`, feeling: "How have you been feeling?", feelingFollowUps: ["When did it start?", "How often?", "How bad is it?"], suggestions: suggestions(patient, check), alwaysLast: `Anything else ${doctor} should know?`, closings: [ { id: "sent", text: `Thank you. I've sent your answers to ${doctor}'s clinic.`, requires: "none", fixed: false }, { id: "read_today", text: `I'll let ${doctor} know today.`, requires: "read_today", fixed: false }, { id: "see_in_clinic", text: `${he.subject[0].toUpperCase()}${he.subject.slice(1)} may want to see you in clinic soon.`, requires: "none", fixed: false }, { id: "call_by_5", text: "The clinic will call you by 5 pm.", requires: "reminder", fixed: false }, { id: "safety_net", text: `If anything gets worse, call ${he.possessive} clinic on ${clinic.phone}; if it's closed, go to a hospital.`, requires: "none", fixed: true }, ], safetyNet: `If you have trouble breathing, chest pain, a swollen face or lips, if you faint, or feel much worse, call ${emergency} or go to the nearest hospital now.`, window: { patientTimeZone: patient.timeZone, clinicTimeZone: clinic.timeZone, clinicHours: clinic.openingHours, alertWatchHours: clinic.alertWatchHours, dayStart: alwaysOpen ? "00:00" : "08:00", dayEnd: alwaysOpen ? "24:00" : "20:00", quietHours: alwaysOpen ? { start: "00:00", end: "00:00" } : patient.quietHours, blockedTimes: patient.blockedTimes, defaultExpiryHours: 24, maxExpiryHours: 72, known: patient.kind === "connected", }, limits: patient.checkInLimits, maxQuestions: 10, maxOwnQuestions: 3, maxQuestionLength: 200, }; }