// The engine's optional clinician fields (feat/engine-clinician-context) and how Context shows them.
import { describe, expect, it } from "vitest";
import type { CheckRun, EngineCalculatedEgfr, EngineKidneyLimit } from "../src/api/types";
import {
allergyReactionLine,
calculatedEgfrLine,
findingWording,
kidneyLimitLine,
notCheckedFor,
ruleReviewLabel,
testMedicines,
} from "../src/logic/engine-extras";
import { draftWordingPill } from "../src/logic/pills";
import { SPECIFIC_REFERENCES } from "./fixture/engine-options";
type Finding = CheckRun["assessment"]["contextual"]["findings"][number];
const finding = (extra: Record<string, unknown> = {}): Finding =>
({
id: "f1",
kind: "allergy",
tier: "today",
ruleId: "allergy-penicillin",
medicines: [{ id: "m1", name: "Amoxicillin" }],
title: "Call today: Amoxicillin and their Penicillin allergy",
why: "",
what_to_do: [],
watch_for: [],
numbers: null,
evidence_label: "",
source_ids: [],
questions_for_doctor: [],
allow_stop: false,
clinical: "Amoxicillin with documented Penicillin allergy (same class).",
teach_back: null,
...extra,
}) as Finding;
describe("clinician wording (CHK-5)", () => {
it("prefers the engine's clinician wording and marks a draft", () => {
const f = finding({ clinician_title: "Amoxicillin and a recorded Penicillin allergy", clinician_why: "Same class.", clinician_wording_review: "draft" });
expect(findingWording(f, undefined)).toEqual({ title: "Amoxicillin and a recorded Penicillin allergy", why: "Same class.", draft: true });
});
it("falls back to the API's wording, then the engine's clinical line", () => {
const detail = { clinicianTitle: "From the API", clinicianExplanation: "API why" } as CheckRun["details"][string];
expect(findingWording(finding(), detail)).toMatchObject({ title: "From the API", why: "API why", draft: false });
expect(findingWording(finding(), undefined).why).toBe("Amoxicillin with documented Penicillin allergy (same class).");
});
it("marks wording nobody signed off: the rule's own note (derived) or the engine's", () => {
for (const wording of ["derived", "engine"] as const) {
const detail = { wording, clinicianTitle: "From the API", clinicianExplanation: "The rule's note" } as CheckRun["details"][string];
expect(findingWording(finding(), detail)).toMatchObject({ why: "The rule's note", draft: true });
}
expect(draftWordingPill).toMatchObject({ label: "Not yet signed off for clinicians", tone: "grey" });
});
});
describe("rule review and the reaction (CHK-6a, CHK-7)", () => {
it("labels unsigned rules and says nothing for a signed-off one", () => {
expect(ruleReviewLabel(finding({ rule_review: "not_in_accepted_sign_off" }), true)).toBe("Rule not yet signed off for real patients");
expect(ruleReviewLabel(finding({ rule_review: "changed_since_sign_off" }), true)).toBe("Rule changed since sign-off");
expect(ruleReviewLabel(finding({ rule_review: "signed_off" }), false)).toBeNull();
expect(ruleReviewLabel(finding(), false)).toBe("Rule not yet reviewed");
});
it("says how the reaction was read and what the tier was without it", () => {
const urgent = finding({
tier: "get_help_now",
tier_without_reaction: "today",
allergy_reaction: { allergy: "Penicillin", reaction: null, reaction_kind: null, onset: null, severity: null, year: null, reaction_class: "unknown" },
});
expect(allergyReactionLine(urgent)).toBe("Recorded reaction to Penicillin: not recorded, not known, so treated as severe. Without the reaction: Review today.");
const mild = finding({ tier_without_reaction: "today", allergy_reaction: { allergy: "Penicillin", reaction: "Hives", reaction_kind: null, onset: "Within a day", severity: null, year: 2019, reaction_class: "not_severe" } });
expect(allergyReactionLine(mild)).toBe("Recorded reaction to Penicillin: hives (Within a day, in 2019), read as not severe.");
expect(allergyReactionLine(finding())).toBeNull();
});
it("carries the specific references the engine branch records (FND-2)", () => {
expect(SPECIFIC_REFERENCES["lab-metformin-egfr-45"][0]).toMatchObject({ sourceId: "smpc-metformin", section: expect.stringContaining("4.2") });
expect(SPECIFIC_REFERENCES["allergy-penicillin"].map((r) => r.sourceId)).toEqual(["smpc-amoxicillin", "nice-cg183"]);
});
});
describe("calculated eGFR, kidney limits and monitoring (CHK-10, CHK-11, FND-7)", () => {
it("labels a calculated eGFR with its inputs, or says why it was not calculated", () => {
const calculated: EngineCalculatedEgfr = {
status: "calculated",
value: 52,
value_unrounded: 51.8,
unit: "mL/min/1.73m2",
label: "calculated",
formula: "CKD-EPI 2021",
observed_at: "2026-10-20",
inputs: { creatinine: { value: 1.2, unit: "mg/dL", value_mg_dl: 1.2, observed_at: "2026-10-20" }, age: 60, sex: "female" },
};
expect(calculatedEgfrLine(calculated)).toBe("eGFR 52 mL/min/1.73m2, calculated from creatinine 1.2 mg/dL (20 Oct 2026), age 60, female, with CKD-EPI 2021.");
expect(calculatedEgfrLine({ status: "not_calculated", reason: "sex_not_recorded", plain: "Sex: needed to calculate eGFR from creatinine (CKD-EPI 2021).", formula: "CKD-EPI 2021" })).toContain("Sex: needed");
expect(calculatedEgfrLine(undefined)).toBeNull();
});
it("names the kidney band and the label's maximum", () => {
const k: EngineKidneyLimit = { ingredient: "metformin", egfr: 38, egfr_comparator: null, egfr_observed_at: "2026-10-12", egfr_calculated: false, band: "30-44", max_daily_mg: 1000, daily_mg: 1000, source_id: "smpc-metformin", section: "4.2" };
expect(kidneyLimitLine(k)).toBe("eGFR 38, 12 Oct 2026: band 30-44 mL/min, the label's maximum in this band is 1,000 mg a day; this list adds up to 1,000 mg a day. Section 4.2.");
expect(kidneyLimitLine({ ...k, egfr: 25, band: "under 30", max_daily_mg: null, egfr_calculated: true })).toContain("(calculated)");
});
it("names the medicines a test is needed with", () => {
const test = { test: "Kidney function (eGFR)", why: "", when: "", source_id: "label-metformin", status: "up_to_date", last_result_at: null, medicines: [{ id: "m1", name: "Metformin" }] } as unknown as CheckRun["assessment"]["contextual"]["tests_to_ask_about"][number];
expect(testMedicines(test, [])).toEqual(["Metformin"]);
expect(testMedicines({ ...test, medicines: undefined } as typeof test, ["Lisinopril"])).toEqual(["Lisinopril"]);
});
});
describe("what was not checked (CHK-3)", () => {
const check = (contextual: Record<string, unknown>): CheckRun =>
({
assessment: { contextual: { findings: [], ...contextual } },
notChecked: { productsWithoutRules: ["agbo"], unconfirmedFacts: 1, rulesAwaitingReview: 8, checkInAnswers: true, other: [] },
versions: { engine: "2.2.2", rules: "2026-10-15", rulesReviewed: false },
}) as unknown as CheckRun;
it("uses the engine's coverage lines when it gives them", () => {
const lines = notCheckedFor(check({ coverage: { lines: ["Agbo: Recorded, not checked: no rules for this product yet.", "Answers from check-ins are recorded as notes; no rule reads them."] } }));
expect(lines).toEqual(["Agbo: Recorded, not checked: no rules for this product yet.", "Answers from check-ins are recorded as notes; no rule reads them."]);
});
it("falls back to the API's statement and lists rules skipped as unsigned", () => {
const lines = notCheckedFor(check({ signed_off_only: { accepted_sign_offs: [], skipped: [{ rule_id: "lab-potassium-raised", kind: "table", review: "not_in_accepted_sign_off", medicines: [{ id: "m", name: "Lisinopril" }] }], plain: "" } }));
expect(lines[0]).toBe("agbo: recorded, not checked. No rules for this product yet.");
expect(lines).toContain("Rules waiting for review, not run: lab-potassium-raised (Lisinopril).");
expect(lines.some((l) => /check-ins/.test(l))).toBe(true);
});
});