import type { ProposalKind } from './recordProposals'; /** * The fallback for whatever the note did not describe. * * SPENCER, 2026-09-03: *"they have the final option sitting there to 'Add anything else to the patient's * record?' which is where they can open the epic style UI to search/find whatever else they want to add."* * And on what shapes the short list: *"Both - specialty on day one, recents as they build up."* * * A SEARCH THAT RESOLVES TO ACTIONS, NOT PAGES. Epic's answer to "too many things" is never a better * menu, and it is the one part of this we are deliberately copying: you type what you want to do and land * on the thing that does it. * * NO FLAT CATALOGUE. A list of every resource we route is the fourteen-section chart sidebar relocated * into a smaller box - which is the thing this whole surface exists to escape. The short list is short, * and search reaches the rest. */ export interface RecordAction { kind: ProposalKind; /** * The THING, named as the drawing names it - "Prescription", "Test or lab order". * * The verb lives in the heading above the list ("Something the visit did not cover"), so repeating * it on every row would say "add" eleven times and make each label longer than the thing it names. * What matters is that a row resolves to something you can ADD rather than a page you navigate to, * and that is true either way. */ label: string; } /** * Everything that can be added. Search matches across all of it; the short list never shows all of it. * * ORDER HERE IS NOT PRECEDENCE - `shortList` decides that. This is only the set. */ export const RECORD_ACTIONS: RecordAction[] = [ { kind: 'medication', label: 'Prescription' }, { kind: 'condition', label: 'Diagnosis' }, { kind: 'referral', label: 'Referral' }, { kind: 'labOrder', label: 'Test or lab order' }, { kind: 'immunization', label: 'Vaccination' }, { kind: 'allergy', label: 'Allergy' }, { kind: 'diet', label: 'Diet order' }, { kind: 'vision', label: 'Glasses or contacts prescription' }, { kind: 'risk', label: 'Risk assessment' }, { kind: 'procedure', label: 'Procedure' }, { kind: 'observation', label: 'Reading or measurement' }, ]; /** * What a clinician sees before typing anything. * * The drawn four, and they are family medicine's. Whether each specialty gets its own four is an open * question - it was put to Spencer during the design run and is not answered, so this is the default * rather than a mapping pretending to be one. Supplying `specialty` moves those kinds to the front; the * absence of a mapping is why nothing here invents one. */ export const DEFAULT_SHORT_LIST: ProposalKind[] = ['medication', 'condition', 'referral', 'labOrder']; /** How many the resting list shows. Short is the point; the drawing shows four. */ export const SHORT_LIST_SIZE = 4; /** * The short list: recents first, then the specialty's own, then the default. * * SPENCER RULED BOTH RATHER THAN EITHER, and the reason each alone fails is the reason: *"Specialty alone * is wrong the first week; recents alone is empty on day one."* So recents take precedence as they * accumulate and the specialty seeds it until they do - which means a new clinician sees something useful * immediately and an established one sees their own habits. * * MOST RECENT FIRST, and duplicates in `recents` collapse to their newest position. A clinician who added * three prescriptions today has one prescription entry at the top, not three. */ export const shortList = ( recents: readonly ProposalKind[] = [], specialty: readonly ProposalKind[] = [], ): RecordAction[] => { const ordered: ProposalKind[] = []; for (const kind of [...recents, ...specialty, ...DEFAULT_SHORT_LIST]) { if (!ordered.includes(kind)) ordered.push(kind); } return ordered .map((kind) => RECORD_ACTIONS.find((action) => action.kind === kind)) .filter((action): action is RecordAction => action !== undefined) .slice(0, SHORT_LIST_SIZE); }; /** * What typing finds. * * MATCHED ON THE LABEL THE CLINICIAN READS, never on the resource name. Somebody looking for a diet order * types "diet", not "NutritionOrder" - and a search that only answers to our internal vocabulary is a * menu that requires you to already know the menu. */ export const matchActions = (query: string): RecordAction[] => { const needle = query.trim().toLowerCase(); if (!needle) return []; return RECORD_ACTIONS.filter((action) => action.label.toLowerCase().includes(needle)); };