// Reading the engine's optional clinician fields (feat/engine-clinician-context). Each is
// optional: with the options off, Context falls back to the API's own wording.
import type {
CheckRun,
EngineCalculatedEgfr,
EngineFindingExtras,
EngineKidneyLimit,
EngineResultExtras,
EngineTestExtras,
RuleReview,
} from "../api/types";
import { formatDate } from "../flow";
import { notCheckedLines } from "./checks";
import { listNames, tierOf } from "./format";
type Result = CheckRun["assessment"]["contextual"];
type Finding = Result["findings"][number];
type Test = Result["tests_to_ask_about"][number];
type Dose = Result["dose_check"][number];
export const findingExtras = (f: Finding) => f as Finding & EngineFindingExtras;
export const resultExtras = (r: Result) => r as Result & EngineResultExtras;
export const testExtras = (t: Test) => t as Test & EngineTestExtras;
export const doseExtras = (d: Dose) => d as Dose & { kidney_limit?: EngineKidneyLimit };
/** Clinician wording first (CHK-5), then the API's, then the engine's patient title. Wording not signed off is a draft. */
export function findingWording(f: Finding, detail: CheckRun["details"][string] | undefined) {
const x = findingExtras(f);
return {
title: x.clinician_title ?? detail?.clinicianTitle ?? f.title,
why: x.clinician_why ?? detail?.clinicianExplanation ?? f.clinical,
draft: x.clinician_wording_review === "draft" || detail?.wording === "derived" || detail?.wording === "engine",
};
}
const reviewLabels: Record<RuleReview, string> = {
signed_off: "Rule signed off",
draft: "Rule is a draft",
changed_since_sign_off: "Rule changed since sign-off",
not_in_accepted_sign_off: "Rule not yet signed off for real patients",
not_yet_reviewed: "Rule not yet reviewed",
unknown_rule: "Rule not recognised",
};
/** The rule's review state, from the engine when it says, else from the API. Null when signed off. */
export function ruleReviewLabel(f: Finding, apiReviewed: boolean | undefined): string | null {
const review = findingExtras(f).rule_review;
if (review) return review === "signed_off" ? null : reviewLabels[review];
return apiReviewed === false ? reviewLabels.not_yet_reviewed : null;
}
const reactionWords: Record<string, string> = {
swelling: "read as swelling",
breathing: "read as breathing trouble",
blistering: "read as blistering skin",
severity_severe: "recorded as severe",
not_severe: "read as not severe",
unknown: "not known, so treated as severe",
};
/** CHK-6a: "Recorded reaction: hives (read as not severe). Without it: Review today." */
export function allergyReactionLine(f: Finding): string | null {
const x = findingExtras(f);
if (!x.allergy_reaction) return null;
const r = x.allergy_reaction;
const what = r.reaction ? r.reaction.toLowerCase() : "not recorded";
const extra = [r.onset, r.severity, r.year ? `in ${r.year}` : null].filter(Boolean).join(", ");
const before = x.tier_without_reaction && x.tier_without_reaction !== f.tier ? ` Without the reaction: ${tierOf(x.tier_without_reaction).label}.` : "";
return `Recorded reaction to ${r.allergy}: ${what}${extra ? ` (${extra})` : ""}, ${reactionWords[r.reaction_class]}.${before}`;
}
/** FND-7: "Needed with Metformin", from the engine when it names the medicines. */
export function testMedicines(t: Test, fallback: string[]): string[] {
const named = testExtras(t).medicines?.map((m) => m.name);
return named?.length ? [...new Set(named)] : fallback;
}
/** CHK-10: an eGFR worked out from creatinine is always labelled calculated, with its inputs. */
export function calculatedEgfrLine(c: EngineCalculatedEgfr | undefined): string | null {
if (!c) return null;
if (c.status === "not_calculated") return c.plain;
const k = c.inputs.creatinine;
return `eGFR ${c.value} ${c.unit}, calculated from creatinine ${k.value} ${k.unit} (${formatDate(k.observed_at.slice(0, 10))}), age ${c.inputs.age}, ${c.inputs.sex}, with ${c.formula}.`;
}
/** CHK-11: the kidney-adjusted maximum behind a dose line. */
export function kidneyLimitLine(k: EngineKidneyLimit | undefined): string | null {
if (!k) return null;
const egfr = `eGFR ${k.egfr_comparator ?? ""}${k.egfr}${k.egfr_calculated ? " (calculated)" : ""}, ${formatDate(k.egfr_observed_at.slice(0, 10))}`;
const limit = k.max_daily_mg === null ? "the label lists it as not for use in this band" : `the label's maximum in this band is ${k.max_daily_mg.toLocaleString("en")} mg a day`;
const amount = k.daily_mg === null ? "" : `; this list adds up to ${k.daily_mg.toLocaleString("en")} mg a day`;
return `${egfr}: band ${k.band} mL/min, ${limit}${amount}. Section ${k.section}.`;
}
/** CHK-3: the engine's own "not checked" lines when it gives them, else the API's; plus rules skipped as unsigned. */
export function notCheckedFor(check: CheckRun): string[] {
const r = resultExtras(check.assessment.contextual);
const lines = r.coverage?.lines?.length ? [...r.coverage.lines] : notCheckedLines(check.notChecked, check.versions.rulesReviewed);
const skipped = r.signed_off_only?.skipped ?? [];
if (skipped.length)
lines.push(
`Rules waiting for review, not run: ${listNames(skipped.map((s) => s.rule_id))}${skipped.some((s) => s.medicines.length) ? ` (${listNames([...new Set(skipped.flatMap((s) => s.medicines.map((m) => m.name)))])})` : ""}.`,
);
if (!lines.some((l) => /check-in/i.test(l))) lines.push("Answers from check-ins: no rule reads them.");
return lines;
}