marmar-context / src / domain / checks / clinician-wording-rules.ts
clinician-wording-rules.ts
Raw
import type { CheckSourceId } from './sources.js';

/**
 * CHK-5 DRAFT clinician wording, apart from rules.ts so no sign-off hash changes: each rule's approved `clinical` and `why`
 * restated as facts, never instructions (clinician-guard.ts). {a} {b} medicines, {result} result and date, {threshold},
 * {age}, {allergy}, {reaction}; " Basis: ..." is added from the sources. Every entry needs the clinical lead's sign-off.
 */
export interface ClinicianWordingEntry {
  title?: string;
  why: string;
  /** For a lab rule that applies with no matching medicine (a dangerous potassium on its own). */
  whyAlone?: string;
  /** Sources the explanation states, when not the rule's own. */
  basis?: CheckSourceId[];
  review: 'draft';
}

const draft = 'draft' as const;
const ix = (why: string): ClinicianWordingEntry => ({ why, review: draft });

export const INTERACTION_WORDING: Record<string, ClinicianWordingEntry> = {
  'vka-nsaid': ix('{a} (vitamin K antagonist) with {b} (NSAID): additive bleeding risk, including gastrointestinal bleeding.'),
  'doac-nsaid': ix('{a} (direct oral anticoagulant) with {b} (NSAID): increased bleeding risk.'),
  'vka-aspirin-dose-unknown': ix('{a} (vitamin K antagonist) with {b} at a dose that is not recorded. An antiplatelet dose may be intended; analgesic doses add NSAID gastrointestinal bleeding risk.'),
  'doac-aspirin-dose-unknown': ix('{a} (direct oral anticoagulant) with {b} at a dose that is not recorded. An antiplatelet dose may be intended; analgesic doses add NSAID gastrointestinal bleeding risk.'),
  'anticoagulant-antiplatelet': ix('{a} (vitamin K antagonist) with {b} (antiplatelet): increased bleeding risk. The combination is sometimes intended; its indication and duration are not recorded here.'),
  'doac-antiplatelet': ix('{a} (direct oral anticoagulant) with {b} (antiplatelet): increased bleeding risk. The combination is sometimes intended; its indication and duration are not recorded here.'),
  'vka-metronidazole': ix('{b} inhibits the metabolism of {a} (CYP2C9), which can raise the INR markedly.'),
  'vka-cotrimoxazole': ix('{b} (co-trimoxazole) potentiates {a}, which can raise the INR.'),
  'vka-azole': ix('{b} (azole antifungal) inhibits the metabolism of {a}, which can raise the INR.'),
  'vka-fluoroquinolone': ix('{b} (fluoroquinolone) may raise the INR with {a}, as reported in some people.'),
  'vka-macrolide': ix('{b} (macrolide or strong CYP3A4 inhibitor) may raise the INR with {a}.'),
  'vka-inducer': ix('{b} (enzyme inducer) reduces the effect of {a}, so the INR may fall.'),
  'vka-amiodarone': ix('{b} potentiates {a}, often over several weeks, which raises the INR.'),
  'nsaid-nsaid': ix('{a} and {b} are both NSAIDs: a duplicate class, with added gastrointestinal and renal toxicity.'),
  'ssri-nsaid': ix('{a} (SSRI) with {b} (NSAID): increased upper gastrointestinal bleeding risk.'),
  'ssri-anticoagulant': ix('{a} (SSRI) with {b} (vitamin K antagonist): increased bleeding risk.'),
  'herbal-bleeding-anticoagulant': ix('Case reports describe potentiated anticoagulation with {a} at supplement doses alongside {b}.'),
  'herbal-bleeding-doac': ix('{a} with {b} (direct oral anticoagulant): an additive antiplatelet effect is expected from how the herb acts. No DOAC label covers it.'),
  'st-johns-wort-anticoagulant': ix('{a} induces CYP enzymes and reduces the effect of {b}.'),
  'st-johns-wort-ssri': ix('{a} with {b} (SSRI): risk of serotonin toxicity.'),
  'acei-arb': ix('{a} (ACE inhibitor) with {b} (ARB): dual renin-angiotensin blockade, with a risk of hyperkalaemia and acute kidney injury.'),
  'raas-k-sparing': ix('{a} (ACE inhibitor) with {b} (potassium-sparing diuretic): risk of hyperkalaemia.'),
  'arb-k-sparing': ix('{a} (ARB) with {b} (potassium-sparing diuretic): risk of hyperkalaemia.'),
  'raas-potassium': ix('{a} (ACE inhibitor) with {b} (potassium supplement): risk of hyperkalaemia.'),
  'arb-potassium': ix('{a} (ARB) with {b} (potassium supplement): risk of hyperkalaemia.'),
  'nsaid-acei': ix('{a} (NSAID) with {b} (ACE inhibitor): reduced antihypertensive effect and a risk of acute kidney injury, higher with a diuretic.'),
  'nsaid-arb': ix('{a} (NSAID) with {b} (ARB): reduced antihypertensive effect and a risk of acute kidney injury, higher with a diuretic.'),
  'simvastatin-strong-cyp3a4': ix('{b} (strong CYP3A4 inhibitor) raises {a} exposure. The simvastatin label lists the combination as contraindicated because of the risk of rhabdomyolysis.'),
  'atorvastatin-strong-cyp3a4': ix('{b} (strong CYP3A4 inhibitor) raises {a} exposure, with a risk of myopathy. The atorvastatin label sets dose limits with these inhibitors.'),
  'simvastatin-amiodarone': ix('{b} raises {a} exposure. The simvastatin label limits simvastatin to 20 mg a day with amiodarone.'),
  'digoxin-macrolide': ix('{b} (clarithromycin or erythromycin) raises {a} levels through P-glycoprotein, with a risk of toxicity.'),
  'digoxin-amiodarone': ix('{b} raises {a} levels, with a risk of toxicity.'),
  'pde5-nitrate': ix('{a} (PDE5 inhibitor) with {b} (nitrate): contraindicated in the label because of severe hypotension.'),
  'opioid-benzodiazepine': ix('{a} (opioid) with {b} (benzodiazepine): FDA boxed warning for profound sedation and respiratory depression.'),
  'tramadol-ssri': ix('{a} with {b} (SSRI): risk of serotonin syndrome and a lower seizure threshold.'),
  'tramadol-tca': ix('{a} with {b} (tricyclic antidepressant): serotonergic and seizure risk.'),
  'methotrexate-trimethoprim': ix('{b} (trimethoprim or co-trimoxazole) with {a}: risk of bone marrow suppression.'),
  'methotrexate-nsaid': ix('{b} (NSAID) reduces {a} clearance; the risk is higher with higher methotrexate doses.'),
  'allopurinol-azathioprine': ix('{a} inhibits xanthine oxidase, which raises {b} exposure. The azathioprine label sets a dose of about a quarter of the usual dose with allopurinol.'),
  'lithium-nsaid': ix('{b} (NSAID) reduces {a} clearance and can raise lithium levels.'),
  'lithium-acei': ix('{b} (ACE inhibitor) can raise {a} levels.'),
  'lithium-arb': ix('{b} (ARB) can raise {a} levels.'),
  'lithium-thiazide': ix('{b} (thiazide) reduces {a} clearance and can raise lithium levels.'),
  'sulfonylurea-cotrimoxazole': ix('{b} may potentiate {a} (sulfonylurea): risk of hypoglycaemia.'),
  'sulfonylurea-fluconazole': ix('{b} inhibits the metabolism of {a} (sulfonylurea): risk of hypoglycaemia.'),
  'clopidogrel-omeprazole': ix('{b} inhibits CYP2C19 activation of {a}. The clopidogrel label warns against this combination.'),
  'clopidogrel-esomeprazole': ix('{b} inhibits CYP2C19 activation of {a}. The clopidogrel label warns against this combination.'),
  'dolutegravir-rifampicin': ix('{b} induces dolutegravir metabolism and lowers {a} exposure. The dolutegravir label gives 50 mg twice daily with rifampicin.'),
  'dolutegravir-cations': ix('{b} (polyvalent cation) chelates {a} and reduces its absorption. The label sets dolutegravir 2 hours before or 6 hours after, and allows iron or calcium together with food.'),
  'lumefantrine-efavirenz': ix('{b} (CYP3A4 inducer) lowers artemether, dihydroartemisinin and lumefantrine exposure in {a}, with a risk of treatment failure.'),
  'apixaban-strong-inducer': ix('{b} (combined P-gp and strong CYP3A4 inducer) markedly lowers {a} exposure. The apixaban label warns against the combination.'),
  'apixaban-st-johns-wort': ix('{b} (combined P-gp and strong CYP3A4 inducer) lowers {a} exposure. The apixaban label warns against the combination.'),
  'rivaroxaban-strong-inducer': ix('{b} (combined P-gp and strong CYP3A4 inducer) markedly lowers {a} exposure. The rivaroxaban label warns against the combination.'),
  'rivaroxaban-st-johns-wort': ix('{b} (combined P-gp and strong CYP3A4 inducer) lowers {a} exposure. The rivaroxaban label warns against the combination.'),
  'lumefantrine-strong-inducer': ix('{b} (strong CYP3A4 inducer) with {a}: contraindicated in the artemether-lumefantrine label because of loss of efficacy.'),
  'lumefantrine-st-johns-wort': ix('{b} (CYP3A4 inducer) with {a}: contraindicated in the artemether-lumefantrine label because of loss of efficacy.'),
  'amodiaquine-efavirenz': ix('{a} with {b}: marked hepatotoxicity reported. The WHO malaria guidelines list efavirenz as a reason not to use artesunate-amodiaquine.'),
  'dolutegravir-phenytoin': ix('{b} (UGT1A1 and CYP3A inducer) lowers {a} exposure. The dolutegravir label warns against the combination.'),
  'dolutegravir-st-johns-wort': ix('{b} lowers {a} exposure. The dolutegravir label warns against the combination.'),
  'dolutegravir-carbamazepine': ix('{b} lowers {a} exposure. The dolutegravir label gives 50 mg twice daily with carbamazepine.'),
  'nevirapine-rifampicin': ix('{b} lowers {a} exposure (AUC about 58% lower). The nevirapine label warns against the combination.'),
  'simvastatin-amlodipine': ix('With {b}, the simvastatin label limits {a} to 20 mg a day because of the risk of myopathy. This applies when the simvastatin dose is above 20 mg or not recorded.'),
  'digoxin-loop-diuretic': ix('{b} (loop diuretic) can cause hypokalaemia and hypomagnesaemia, which increase the risk of {a} toxicity.'),
  'digoxin-thiazide': ix('{b} (thiazide) can cause hypokalaemia, which increases the risk of {a} toxicity.'),
  'corticosteroid-nsaid': ix('{a} (corticosteroid) with {b} (NSAID): increased risk of gastrointestinal ulceration and bleeding.'),
  'fluoroquinolone-cations': ix('{b} (polyvalent cation) chelates {a} and reduces its absorption. The labels set a gap of 2 hours before or 6 hours after.'),
  'levothyroxine-cations': ix('{b} (iron, calcium or antacid) reduces {a} absorption. The label sets a gap of about 4 hours.'),
};

/** Allergy rules: the class each one covers, for "{a} contains {ingredients}, in the {class} class". */
export const ALLERGY_CLASS_WORDS: Record<string, string> = {
  'allergy-penicillin': 'penicillin',
  'allergy-sulfa': 'sulfonamide antibiotic',
  'allergy-nsaid': 'NSAID (including aspirin)',
  'allergy-cephalosporin': 'cephalosporin',
  'allergy-opioid': 'opioid',
};

/** {subject} is "Amoxicillin is" or "Augmentin contains amoxicillin,": the medicine as recorded, with its matching ingredients when the name differs. */
export const ALLERGY_WORDING = {
  title: '{a} and a recorded {allergy} allergy',
  ingredient: '{subject} named in the recorded {allergy} allergy{reaction}.',
  class: '{subject} in the {class} class, the same class as the recorded {allergy} allergy{reaction}.',
  cross: '{subject} a cephalosporin. A {allergy} allergy is recorded{reaction}. Cross-reactivity between penicillins and cephalosporins is reported '
    + 'in a small number of people with a penicillin allergy.',
  review: draft,
} as const;

export const OLDER_WORDING: Record<string, ClinicianWordingEntry> = {
  'older-long-acting-sulfonylurea': ix('{a} at {age}: long-acting sulfonylureas (glyburide, glimepiride) are listed as potentially inappropriate in older adults '
    + 'in the 2023 Beers Criteria and STOPP/START version 3, because of prolonged hypoglycaemia.'),
  'older-first-gen-antihistamine': ix('{a} at {age}: first-generation antihistamines are listed as potentially inappropriate in older adults in the 2023 Beers '
    + 'Criteria, as strongly anticholinergic.'),
  'older-benzodiazepine': ix('{a} at {age}: benzodiazepines are listed as potentially inappropriate in older adults in the 2023 Beers Criteria (cognitive '
    + 'impairment, falls, fractures). Abrupt withdrawal carries its own risks.'),
  'older-tca': ix('{a} at {age}: tricyclic antidepressants such as amitriptyline are listed as potentially inappropriate in older adults in the 2023 Beers '
    + 'Criteria, as highly anticholinergic and sedating.'),
  'older-regular-nsaid': ix('{a} at {age}: regular NSAID use is listed as potentially inappropriate in older adults in the 2023 Beers Criteria and STOPP/START '
    + 'version 3, unless alternatives have failed and a gastroprotective agent is used.'),
};

export const CHILD_WORDING: Record<string, ClinicianWordingEntry> = {
  'child-aspirin-under-16': ix('{a} at {age}. The MHRA and NHS state aspirin is not for children under 16 unless specifically indicated (for example '
    + 'Kawasaki disease), because of the risk of Reye\'s syndrome.'),
  'child-codeine-under-12': ix('{a} at {age}. The MHRA (2013, 2015) and EMA restrict codeine under 12: unpredictable CYP2D6 conversion to morphine '
    + 'and respiratory depression.'),
  'child-tramadol-under-12': ix('{a} at {age}. The tramadol label (FDA 2017) lists it as contraindicated under 12: ultra-rapid metabolism and '
    + 'respiratory depression.'),
  'child-promethazine-under-2': ix('{a} at {age}. The promethazine label boxed warning lists it as contraindicated under 2: fatal respiratory depression.'),
  'child-cough-cold-promethazine-under-6': ix('{a} at {age}: a cough and cold product with an antihistamine (promethazine). The MHRA (2009) states these '
    + 'products are not for children under 6.'),
  'child-night-nurse-under-16': ix('{a} at {age}. Its patient leaflet states it is not for children under 16 (paracetamol, promethazine, dextromethorphan).'),
};

const lab = (why: string, extra: Partial<ClinicianWordingEntry> = {}): ClinicianWordingEntry => ({ why, review: draft, ...extra });

export const LAB_WORDING: Record<string, ClinicianWordingEntry> = {
  'lab-metformin-egfr-30': lab('{result} is below 30, the level at which the metformin label (UK SmPC, sections 4.2 and 4.3) lists metformin as contraindicated.',
    { basis: ['smpc-metformin'] }),
  'lab-metformin-egfr-45': lab('{result} is in the range where the metformin label (UK SmPC, section 4.2) limits the total daily dose to 1,000 mg (GFR 30 to 44 mL/min).',
    { basis: ['smpc-metformin'] }),
  'lab-inr-8': lab('{result} on {a}, a vitamin K antagonist. An INR of 8 or more carries a high risk of serious bleeding.'),
  'lab-inr-5': lab('{result} on {a}, a vitamin K antagonist. An INR of 5 or more is well above any usual target range.'),
  'lab-inr-high-nsaid': lab('{result} on {a} is above the target of {threshold}, with {b}, an NSAID, also recorded: added bleeding risk.'),
  'lab-inr-4-5': lab('{result} on {a}, a vitamin K antagonist. An INR of 4.5 or more is above any usual target range.'),
  'lab-inr-high': lab('{result} on {a} is above the target of {threshold}.'),
  'lab-potassium-very-high': lab('{result} on {a}, which can raise potassium. NICE CKS (hyperkalaemia) places 6.5 mmol/L or more in the range for emergency assessment.',
    { whyAlone: '{result}. NICE CKS (hyperkalaemia) places 6.5 mmol/L or more in the range for emergency assessment.' }),
  'lab-potassium-high': lab('{result} on {a}, which can raise potassium. NICE CKS (hyperkalaemia) places 6.0 to 6.4 mmol/L in the range for same-day assessment.',
    { whyAlone: '{result}. NICE CKS (hyperkalaemia) places 6.0 to 6.4 mmol/L in the range for same-day assessment.' }),
  'lab-potassium-raised': lab('{result} on {a}, which can raise potassium. NICE CKS (hyperkalaemia) places 5.5 to 5.9 mmol/L in the range for review and a repeat test.'),
};

/** Tests to ask about: "{test} · needed with {medicines}" (FND-7) and why. */
export const MONITORING_WORDING: Record<string, ClinicianWordingEntry> = {
  'monitor-warfarin-inr': ix('The INR shows the effect of a vitamin K antagonist. The cited sources give testing at least every 12 weeks when stable.'),
  'monitor-metformin-kidneys': ix('Renal function sets the metformin dose limits in the label. This rule looks for a result at least once a year.'),
  'monitor-metformin-b12': ix('Long-term metformin can lower vitamin B12. This rule sets no interval.'),
  'monitor-diabetes-hba1c': ix('HbA1c shows average glucose over the previous months. This rule looks for a result every 3 to 6 months.'),
  'monitor-acei-kidneys-potassium': ix('ACE inhibitors can change renal function and potassium. This rule looks for a result at the start, after each new dose, and at least yearly.'),
  'monitor-arb-kidneys-potassium': ix('ARBs can change renal function and potassium. This rule looks for a result at the start, after each new dose, and at least yearly.'),
  'monitor-spironolactone-potassium': ix('Potassium-sparing diuretics can raise potassium. This rule looks for a result about every 6 months.'),
  'monitor-bp-home': ix('Home blood pressure readings show the response to a calcium channel blocker. This rule looks for a reading about monthly.'),
  'monitor-statin-lipids': ix('A lipid profile shows the response to a statin. This rule looks for a result about 3 months after starting, then yearly.'),
  'monitor-lithium-level': ix('Lithium needs regular levels, with renal and thyroid tests. This rule sets no interval.'),
  'monitor-methotrexate-bloods': ix('Methotrexate needs regular blood count, liver and renal tests. This rule sets no interval.'),
  'monitor-levothyroxine-tsh': ix('TSH shows whether the levothyroxine dose matches need. This rule looks for a result about yearly.'),
};

/** Findings written in engine code (signoff.ts ENGINE_CODED_RULES). */
export const ENGINE_WORDING = {
  'dose-max': { title: '{a}: daily amount above the label maximum', why: 'As entered, {a} gives {amounts}.', review: draft },
  duplicate: { title: '{ingredient} in more than one medicine', why: '{medicines} each contain {ingredient}{total}.', review: draft },
  'paracetamol-lower-maximum': { title: 'Paracetamol near the adult daily maximum',
    why: 'About {total} mg of paracetamol a day, {reason}. This rule marks 3,000 to 4,000 mg a day in adults under 50 kg or aged 75 and over, where a lower daily maximum may apply.',
    review: draft },
} as const;