diff --git a/src/lib/answer-render-policy.ts b/src/lib/answer-render-policy.ts index 12f7b88497..ed8e43576b 100644 --- a/src/lib/answer-render-policy.ts +++ b/src/lib/answer-render-policy.ts @@ -493,6 +493,23 @@ function blockDecisions(args: { } satisfies Record; } +// P4b: the copy/paste block previously emitted the bare enum ("strong"/"moderate"/"limited"/"none" +// support) — which reads oddly ("none support") and gives no plain-English cue about how well the +// source matched. Gloss it into a clinician-readable phrase so pasted drafts carry interpretable +// source strength. +export function describeSourceStrengthForCopy(strength: SourceStrength | "none"): string { + switch (strength) { + case "strong": + return "strong match"; + case "moderate": + return "moderate match"; + case "limited": + return "limited match"; + default: + return "match strength not rated"; + } +} + export function formatAnswerRenderCopyText(args: { answerText: string; trust: AnswerRenderTrust; @@ -501,7 +518,8 @@ export function formatAnswerRenderCopyText(args: { }) { const sourceLines = args.primarySources.length ? args.primarySources.map( - (source, index) => `${index + 1}. ${source.label} | ${source.sourceStrength} support | ${source.href}`, + (source, index) => + `${index + 1}. ${source.label} | ${describeSourceStrengthForCopy(source.sourceStrength)} | ${source.href}`, ) : ["No policy-approved sources were attached."]; const warningLines = args.warnings.length ? args.warnings.map((warning) => `- ${warning}`) : ["- None"]; diff --git a/tests/answer-render-policy.test.ts b/tests/answer-render-policy.test.ts index d53edabe91..5cf6f2a00f 100644 --- a/tests/answer-render-policy.test.ts +++ b/tests/answer-render-policy.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { buildAnswerRenderModel } from "../src/lib/answer-render-policy"; +import { + buildAnswerRenderModel, + describeSourceStrengthForCopy, + formatAnswerRenderCopyText, +} from "../src/lib/answer-render-policy"; import type { BestSourceRecommendation, Citation, @@ -262,4 +266,40 @@ describe("answer render policy", () => { expect(model.quoteCards).toHaveLength(0); expect(model.allowedBlocks).not.toContain("quoteCards"); }); + + describe("copy-text source-strength gloss (P4b)", () => { + it("glosses each strength into a clinician-readable phrase and avoids the odd 'none support'", () => { + expect(describeSourceStrengthForCopy("strong")).toBe("strong match"); + expect(describeSourceStrengthForCopy("moderate")).toBe("moderate match"); + expect(describeSourceStrengthForCopy("limited")).toBe("limited match"); + expect(describeSourceStrengthForCopy("none")).toBe("match strength not rated"); + }); + + it("renders the glossed strength in the copy block, not the bare enum", () => { + const link = ( + overrides: Partial[0]["primarySources"][number]>, + ) => ({ + id: "s1", + chunk_id: "c1", + document_id: "d1", + title: "T", + file_name: "f.pdf", + page_number: 4, + href: "/documents/doc-1?page=4", + label: "Clozapine Monitoring (AKG)", + sourceStrength: "strong" as const, + reason: "selected", + ...overrides, + }); + const text = formatAnswerRenderCopyText({ + answerText: "Withhold clozapine for a red-range result.", + trust: "high", + primarySources: [link({}), link({ label: "Legacy Note", href: "/documents/doc-2", sourceStrength: "none" })], + warnings: [], + }); + expect(text).toContain("Clozapine Monitoring (AKG) | strong match | /documents/doc-1?page=4"); + expect(text).toContain("Legacy Note | match strength not rated | /documents/doc-2"); + expect(text).not.toContain("none support"); + }); + }); });