import { normalizeText } from './normalize.js';
/**
* CHK-5: clinician wording states a fact and its basis, never an instruction (PRD principle 1). Every draft is
* checked here in tests, and each rendered text again at run time with the medicine names masked; a text with a
* problem is left out, never shown. MARMAR never decides, prescribes, diagnoses or recommends treatment.
*/
const word = (source: string) => new RegExp(`(?<![a-z0-9])(?:${source})(?![a-z0-9])`);
const RULES: Array<[problem: string, pattern: RegExp]> = [
// Telling anyone what to do: a clause that starts with a verb of action.
['instruction', /^(?:please )?(?:stop|start|restart|resume|change|switch|swap|replace|substitute|reduce|increase|decrease|lower|raise|adjust|titrate|halve|double|give|take|use|avoid|withhold|hold|omit|skip|discontinue|continue|keep|prescribe|consider|check|recheck|monitor|measure|test|refer|order|repeat|call|phone|contact|ask|tell|inform|advise|counsel|review|reassess|confirm|arrange|admit|send|seek|go|add|remove|do|don ?t|never|ensure|make sure|be)(?![a-z0-9])/],
['second_person', word('you|your|yours|yourself')],
['advice', word('should|shouldnt|must|mustnt|ought to|need to|needs to|has to|have to|had better|it is best|it would be (?:best|better|wise)|is advised|are advised|is recommended|are recommended|please')],
['advice', word('recommend|recommends|recommended|recommending|advise|advises|advised|advising|suggest|suggests|suggested|suggesting|consider|considers|considered|considering')],
['decision', word('decide|decides|decided|deciding|decision|diagnose|diagnoses|diagnosed|diagnosing|diagnosis|prescribe|prescribes|prescribing')],
['stop_or_change', word('stop|stops|stopped|stopping|discontinu[a-z]*|withh(?:o|e)ld[a-z]*|switch(?:es|ed|ing)?|swap(?:s|ped|ping)?|substitut[a-z]*|taper[a-z]*|avoid[a-z]*|halv(?:e|es|ed|ing)|do not|dont|never')],
['dose_change', word('(?:reduc|increas|decreas|lower|rais|adjust|chang|alter|titrat|halv|doubl|cut)[a-z]*(?: [a-z]+){0,2} (?:dose|doses|dosage|dosing)')],
['dose_change', word('(?:dose|doses|dosage)(?: [a-z]+){0,2} (?:reduc|increas|decreas|lower|rais|adjust|chang|alter|titrat|halv|doubl)[a-z]*')],
['reassurance', word('safe|safely|safer|safest|safety|harmless|no problem|not a problem|nothing to worry|fine|ok|okay|alright|all right|reassur[a-z]*')],
['patient_voice', word('call 112|call 999|call 911|get help now|before the next dose|pharmacist today|doctor today')],
];
const escape = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
/** Problems with a clinician title or explanation; empty means it may be shown. Names are masked first: they are data. */
export function clinicianWordingProblems(text: string, names: readonly string[] = []): string[] {
let said = ` ${normalizeText(text)} `;
for (const name of [...new Set(names.map(normalizeText).filter(Boolean))].sort((a, b) => b.length - a.length)) {
said = said.replace(new RegExp(`(?<![a-z0-9])${escape(name)}(?![a-z0-9])`, 'g'), ' medicine ');
}
said = said.replace(/\s+/g, ' ').trim();
// Clauses: split where the original text has sentence or clause punctuation.
const clauses = text.split(/[.;:!?()]+/).map((part) => {
let clause = ` ${normalizeText(part)} `;
for (const name of names.map(normalizeText).filter(Boolean)) clause = clause.replace(new RegExp(`(?<![a-z0-9])${escape(name)}(?![a-z0-9])`, 'g'), ' medicine ');
return clause.replace(/\s+/g, ' ').trim();
}).filter(Boolean);
const problems = new Set<string>();
for (const [problem, pattern] of RULES) {
if (problem === 'instruction') {
if (clauses.some((clause) => pattern.test(clause))) problems.add(problem);
} else if (pattern.test(said)) problems.add(problem);
}
return [...problems];
}