/** * DID THEY SAY HOW THEY WANT TO BE SEEN, IN THEIR OWN WORDS? * * The modality step, re-scoped after the design was read rather than guessed at. The two settled * LLM designs say "It never auto-selects and never auto-books" and "Never filter, never hide, never * auto-book" - so PRE-ANSWERING the Where control is forbidden. A visible, unselected suggestion beside * an unchanged control selects nothing, hides nothing and filters nothing, and is the shape the design * endorses. The difference between "video visit is chosen for you" and "you said 'video visit'" IS the * boundary. * * NO MODEL IN THIS PATH, DELIBERATELY. The contradiction checker's whole safety argument is that the * model must return a span that really appears in the reason, because a copied quote makes an inferred * intent structurally impossible. A local match keeps that property by construction - the quote IS the * matched text - and adds no second grounding rule to reason about, no call to fail open, and no * latency on a control the person is already looking at. * * SO IT IS DELIBERATELY LITERAL AND WILL MISS THINGS. Somebody who writes "I can't get to the * surgery" has stated a modality to a human and not to this function. That is the right failure: this * is a suggestion, its absence costs one unchanged click, and a looser match would start speaking on * bookings where nothing was said - which control 4 of the card forbids in as many words. */ export type StatedModality = 'video' | 'in-person'; export interface ModalityStatement { readonly modality: StatedModality; /** * THE SPAN AS THEY TYPED IT, sliced out of the original text rather than rebuilt from the pattern, * so casing and spacing are theirs. Showing somebody their own words back is what makes this a * reminder rather than an assertion about what they meant. */ readonly quote: string; } /** * Ordered, and the order is load-bearing where phrases overlap: "not in person" must be read as video * before "in person" is read as in-person. Longest and most-negated first. */ const PATTERNS: ReadonlyArray<{ modality: StatedModality; re: RegExp }> = [ { modality: 'video', re: /\bnot\s+in[-\s]person\b/i }, { modality: 'video', re: /\bvideo\s+(?:visit|call|appointment)\b/i }, { modality: 'video', re: /\b(?:by|over|on|via)\s+video\b/i }, { modality: 'video', re: /\bvirtual\s+(?:visit|appointment)\b/i }, { modality: 'video', re: /\btelehealth\b/i }, { modality: 'video', re: /\btelemedicine\b/i }, { modality: 'in-person', re: /\bin[-\s]person\b/i }, { modality: 'in-person', re: /\bin\s+the\s+office\b/i }, { modality: 'in-person', re: /\boffice\s+visit\b/i }, { modality: 'in-person', re: /\bcome\s+in\b/i }, ]; /** * The modality they stated, or nothing. * * NOTHING WHEN THEY STATED BOTH. "video visit or in person, whichever is sooner" is a person saying * they do not mind, and leading them toward one of the two would be inventing a preference they took * care not to express. Silence is the honest reading, and it is also the one that cannot be wrong. */ export const statedModality = (reasonText: string | undefined): ModalityStatement | undefined => { if (!reasonText || !reasonText.trim()) return undefined; const hits: ModalityStatement[] = []; // The spans already accounted for. A later pattern matching INSIDE one of them is not a second // statement - it is part of the first, and this is the whole reason `PATTERNS` is ordered. // // THE CASE THAT FORCED IT: "not in person" matches the negation AND, inside it, the bare // in-person phrase. Without this the function saw two contradictory statements and fell silent on // the sentence whose meaning is least ambiguous to a human. Ordering alone could not fix it, // because both hits were still collected. const consumed: Array<{ start: number; end: number }> = []; for (const { modality, re } of PATTERNS) { const match = re.exec(reasonText); if (!match) continue; const start = match.index; const end = start + match[0].length; if (consumed.some((span) => start < span.end && end > span.start)) continue; // Only the FIRST hit per modality is kept, so "video visit, by video" is one statement rather // than an argument with itself. if (hits.some((hit) => hit.modality === modality)) continue; consumed.push({ start, end }); hits.push({ modality, quote: match[0] }); } if (hits.length !== 1) return undefined; return hits[0]; };