Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "4f15ebaf-d311-4971-a52f-46e26a2a963d",
"createdOn": "2026-10-21",
"action": "done",
"payload": {
"id": "#VXB8XA",
"outcome": "Fixed on main (verified offline 21 Oct 2026, answer safety hardening thread). cite-mismatched-attribution no longer has a KNOWN_DIVERGENCES entry in tests/rag-adversarial-harness.test.ts, and npm run eval:rag:adversarial:offline passes 25 of 25 on main 81ba5aefa, via PR #3628 (62558e5c7, 18 Oct, document attribution fix), so the case now meets its normative fixture expectation (cites only the claim-bearing chunk). The pin was self-expiring by design, so its removal records the behaviour change.",
"baseRowFingerprint": "1f48f81cb2e3a3db5cd3994a709d63e2ff2e3d2967790a017a6c6d99e4a3ceee",
"targetUlid": "01M08C19FYVXB8XATJE3TYZJMS"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "62c5cf9f-95c6-4a40-9112-6f63ac631d9b",
"createdOn": "2026-10-21",
"action": "done",
"payload": {
"id": "#F3B2XC",
"outcome": "Done on main (verified 21 Oct 2026, answer safety hardening thread). src/lib/medications/tall-man.ts was rebuilt from the ACSQHC National Mixed-Case Lettering List (April 2024) in PR #3562 (5319dab78, 16 Oct), pinned by tests/tall-man-lettering.test.ts. All 26 lettered names were re-checked against the Commission PDF (safetyandquality.gov.au national_mixed-case_lettering_list.pdf, Tables 1 and 3) on 21 Oct and every one matches. The doubtful forms in this row (citaloPRAM, fluPENtixol, VALproate, midazolAM, temazePAM) are gone. One local exception remains, cloZAPine (not on the ACSQHC list), which the owner confirmed keeping on 21 Oct 2026 ('Go ahead with all recommendations', project chat). Brand names on the list (Largactil, Lamictal, Pristiq and others) are not lettered because only generic record names are formatted.",
"baseRowFingerprint": "ceae0146e0b33521b704f0c9830ae582d28aa18ecf652e60b4f337f204126c7f",
"targetUlid": "01M4ZM794RF3B2XCAJ2YDRJ8FG"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "d7678153-b6d7-4571-8f46-48f4a59ca47a",
"createdOn": "2026-10-21",
"action": "update",
"payload": {
"id": "#ZZ4RAP",
"detail": "UPDATE 2026-10-21 (answer safety hardening thread, verified offline on main 81ba5aefa): 18 of the 20 cases are wired into answerQualityEvalCases as high-risk-<id> on the owner go-ahead of 2026-10-10, with verbatim evidence pinned by tests/high-risk-eval-cases-draft.test.ts. Two stay out as source_needed: breastfeeding-lithium and hepatic-valproate, because no WA passage for either is held in the repository. Draft expected facts for both were sent to the owner for correction on 2026-10-21 (not approved). Next: owner corrects the drafts and approves a free read-only fetch of the KEMH lithium breastfeeding and CG.MHSP.Valproate passages into a fixture, then the two cases are wired in. PRIOR DETAIL: OWNER DECISION 2026-09-25: approved. Engineering drafts the 20 cases (question, key facts a correct answer must contain, facts it must not state, expected source); the owner reviews and corrects them before any are added to the harness. A first draft of all 20 was delivered to the owner on 2026-09-25 as a Word document (not committed: clinical content awaiting review). Verified offline by topic count over src/lib/rag/rag-eval-cases.ts (101 cases): hepatic 0, older adult/elderly 0, drug interaction 0, unsafe/false premise 0, Mental Health Act 1, QTc 2, pregnancy 3, paediatric 2. Owner recommendation (2026-09-25): start with ~20 clinician-reviewed cases (3 pregnancy/breastfeeding, 3 renal/hepatic, 2 older adults, 2 QTc, 2 lithium/clozapine monitoring, 2 overdose, 2 interactions, 2 MHA, 2 false-premise where the right answer corrects the premise); engineering drafts expected key facts + source, owner reviews. Adding golden cases touches the eval harness (RAG-protected surface): flag before editing, and live canary dispatches need approval.",
"baseRowFingerprint": "33b1e815aec207b8f894527ab4e39d00df4a2c81e69bcfb8a22cfcdf0842e01f",
"targetUlid": "01M3CJ78GDZZ4RAPXAS64MSVYH"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "e9b59bb8-6de5-4041-a086-c6d01bae8593",
"createdOn": "2026-10-21",
"action": "done",
"payload": {
"id": "#HD1C6W",
"outcome": "Done on main (verified 21 Oct 2026, answer safety hardening thread). The word-form fix for all three sealed hazard-control files landed in PR #3571 (e526f8a4d, 16 Oct) with tests in answer-verification, rag-claim-support and source-text-sanitizer; docs/clinical-hazard-controls.json was then resealed with owner approval on 21 Oct (5714539c5, comment 6313455205), and npm run check:clinical-hazard-controls passes on main 81ba5aefa. A fresh grep finds no bare escalat/contraindicat/prescrib/initiat stems left inside word boundaries in those files.",
"baseRowFingerprint": "b35fbe964bb903a420fef3a4ea99e440f64bc026c7e60818d7e569314cc0cc65",
"targetUlid": "01M3H9CJH5HD1C6WEV9E9KKASJ"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "eed86710-2875-44cf-80bf-8a79dc8fe069",
"createdOn": "2026-10-21",
"action": "update",
"payload": {
"id": "#BFCTST",
"detail": "UPDATE 2026-10-21 (answer safety hardening thread, offline): tier-rule tests added to tests/australian-source-priority.test.ts (WA ahead of national ahead of other states ahead of supplementary at equal relevance, relevance always wins across bands, supplementary padding dropped once two Australian documents suffice). Both owner additions remain unbuilt: (a) outdated sources are only penalised in rag-second-stage.ts and warned about in the sealed src/lib/source-governance.ts, so they can still carry a claim; (b) buildSourceConflictSection (src/lib/rag/source-conflict-section.ts) only compares uploaded_local with australian_public, and uploaded_local retrieval is fail-closed (#9V1TJ5), so a WA versus national public disagreement is never disclosed. Both are answer-behaviour changes: RAG notice, canary pair and hazard reseal needed; plan sent to the owner 2026-10-21. PRIOR DETAIL: OWNER DECISION 2026-09-25: confirmed the existing tier order (WA validated > Australian national > other Australian state > supplementary, within a relevance band) and asked for two additions: a superseded source is never used as evidence, and a clinically material WA-vs-national disagreement is disclosed with both sources shown. Found 2026-09-25: outdated sources are currently KEPT and shown with a danger-severity OUTDATED warning (src/lib/source-governance.ts), and authority conflict in source-authority-registry.ts means publisher-metadata mismatch, not clinical disagreement. Both additions change retrieval/answer behaviour: flag before editing and run a live canary pair (owner approval, about USD 1-2) around the change. A rule already exists: src/lib/australian-source-priority.ts ranks wa_validated < australian_national < australian_state < supplementary WITHIN a relevance band, and tracks authorityConflictCount. Only 3 eval cases touch conflict/superseded sources. Owner recommendation (2026-09-25): confirm that order, add (a) superseded versions never used as evidence and (b) disclose both sources when WA and national disagree on something clinically material. Next action: owner confirms the rule, then add conflict/currentness/attribution cases. Ranking is RAG-protected: flag before editing and require a canary pair for any ordering change.",
"baseRowFingerprint": "2d864f3aad6ae3255c85943918a427e80f2718cdf482e1b332e2fd1c2a1e4be4",
"targetUlid": "01M3CJ78QHBFCTST7YFDZZHKE3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "f2e5b2bc-8d0d-4b8c-86a6-a93dfa6cdc7e",
"createdOn": "2026-10-21",
"action": "add",
"payload": {
"pri": "P2",
"type": "issue",
"summary": "The WA valproate guideline (CG.MHSP.Valproate.pdf) is not in the live corpus, although an answer-quality eval case expects it",
"detail": "Read-only live query 2026-10-21 (owner-approved free read, answer safety hardening thread): no document in public.documents has a title or file name containing 'valpro' other than the registry JSON records. quality-valproate-pregnancy-contraindication in src/lib/rag/rag-eval-cases.ts lists CG.MHSP.Valproate.pdf as an expected file, and the high-risk case hepatic-valproate (#ZZ4RAP) cannot be sourced without it. The only hepatic valproate passage found was BMJ Best Practice 'Bipolar disorder in adults' p130, whose licence is unconfirmed. Next: owner supplies the WA guideline PDF for ingestion (same path as the CAMHS ADHD guideline, #018); no re-index of existing documents is needed.",
"source": "Answer safety hardening thread, 2026-10-21, read-only Supabase query",
"issueUlid": "01M5F5FS6D7B6MMMAM9NCTSB3M"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 2,
"id": "f4f9f0cd-7579-4f7e-8beb-6c30ee1fc5cb",
"createdOn": "2026-10-21",
"action": "done",
"payload": {
"id": "#E6142T",
"outcome": "Closed as won't fix by owner decision 2026-10-21 ('Go ahead with all recommendations', project chat, on the answer safety hardening plan). Part 1 was already closed by owner ruling 2026-09-25. Part 2 (services mode nouns in modeSearchWords) has no observable effect on main because the services ranker and universal search never read modeSearchWords; reopen only if services search starts consuming smartSearchContentTerms.",
"baseRowFingerprint": "2f70b014d807047cbc1309c4c28591101c2de8f050989661fda6168df6fffa67",
"targetUlid": "01M2R5W5F8E6142TR092JVPGF2"
}
}
43 changes: 42 additions & 1 deletion src/components/clinical-dashboard/answer-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { primaryAnswerDisplayText as canonicalPrimaryAnswerDisplayText } from "@/lib/answer-display-text";

import { Fragment, memo, type ReactNode } from "react";
import Link from "next/link";
import { Copy } from "lucide-react";

import { SafeBoldText } from "@/components/SafeBoldText";
Expand All @@ -20,8 +21,14 @@ import { SignedImage } from "@/components/clinical-dashboard/signed-image";
import { clinicalProseUsefulness } from "@/lib/source-text-sanitizer";
import { type ClaimMarkCluster, type SupportedClaim, resolveClaimMarks } from "@/lib/answer-claim-marks";
import { type SourceLink } from "@/lib/answer-render-policy";
import { documentCitationHref } from "@/lib/citations";
import { guidanceDifferenceLabel } from "@/lib/rag/guidance-difference-note";
import type { AnswerSection, AnswerSectionKind, RagAnswer, VisualEvidenceCard } from "@/lib/types";
import type { ClientBestSourceRecommendation, ClientSearchResult } from "@/lib/answer-client-payload";
import type {
ClientBestSourceRecommendation,
ClientRagAnswerPayload,
ClientSearchResult,
} from "@/lib/answer-client-payload";

export const SourceImage = memo(function SourceImage({
endpoint,
Expand Down Expand Up @@ -260,6 +267,7 @@ export function NaturalLanguageAnswer({
showCopyAction = true,
isRefusal = false,
hideSourceRail = false,
guidanceDifference,
}: {
// Raw answer text (server bold intact); this component owns display
// sanitization so <SafeBoldText> can render the high-yield emphasis.
Expand Down Expand Up @@ -315,6 +323,11 @@ export function NaturalLanguageAnswer({
* so the list of them is left off rather than shown as if it backed something.
*/
hideSourceRail?: boolean;
/**
* Owner-signed WA versus national difference (`answer.guidanceDifference`). The answer still
* follows WA; one quiet line under the prose names the national source. Approved 2026-10-21.
*/
guidanceDifference?: ClientRagAnswerPayload["guidanceDifference"];
}) {
const { preferences } = useAppPreferences();
const fragments = primaryAnswerDisplayFragments(text, { preformatted, preserveBold: true });
Expand Down Expand Up @@ -375,6 +388,7 @@ export function NaturalLanguageAnswer({
))}
</span>
</p>
{guidanceDifference && !isRefusal ? <GuidanceDifferenceLine difference={guidanceDifference} /> : null}
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
{/* The Source-only pill is NOT here any more (owner decision,
2026-09-03). It sat below the prose, apart from the three status
chips above it and built from a third disclosure mechanism, so the
Expand Down Expand Up @@ -419,6 +433,33 @@ export function NaturalLanguageAnswer({
);
}

function GuidanceDifferenceLine({
difference,
}: {
difference: NonNullable<ClientRagAnswerPayload["guidanceDifference"]>;
}) {
const label = guidanceDifferenceLabel(difference);
return (
<p
data-testid="answer-guidance-difference"
className="mt-1.5 text-xs leading-5 text-[color:var(--text-muted)] sm:text-sm sm:leading-6"
>
National guidance differs on this point. See{" "}
<Link
href={documentCitationHref({
chunk_id: difference.chunkId,
document_id: difference.documentId,
page_number: difference.page,
})}
className="font-medium text-[color:var(--text-heading)] underline decoration-[color:var(--border-strong)] decoration-1 underline-offset-[3px] hover:decoration-current focus-visible:rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"
>
{label}
</Link>
.
</p>
);
}

export function UserQuestionBubble({ query }: { query: string }) {
const cleaned = query.trim();
if (!cleaned) return null;
Expand Down
17 changes: 16 additions & 1 deletion src/components/clinical-dashboard/answer-copy-payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { isPreformattedGroundedAnswer, primaryAnswerDisplayText } from "@/compon
import { projectAnswerForMainSurface } from "@/components/clinical-dashboard/answer-section-projector";
import { composeAnswerClipboardText } from "@/lib/answer-clipboard";
import { publicFallbackReason } from "@/lib/rag/rag-fallback-reason";
import { guidanceDifferenceSentence } from "@/lib/rag/guidance-difference-note";
import type { AnswerPayload } from "@/components/clinical-dashboard/search-utils";
import type { ClientRagAnswerPayload, ClientSearchResult } from "@/lib/answer-client-payload";
import { ragAdaptiveAnswerPromptVersion } from "@/lib/rag/rag-versioning";
Expand Down Expand Up @@ -178,6 +179,20 @@ export function singleDocumentClipboardMetadata(
return withMetadata?.source_metadata ?? undefined;
}

/**
* The national guidance line travels with a copied answer, directly after the answer text, so a
* pasted WA recommendation never loses its "national guidance differs" caveat.
*/
function withGuidanceDifferenceLine(text: string, answer: ClientRagAnswerPayload): string {
if (!answer.guidanceDifference) return text;
const line = guidanceDifferenceSentence(answer.guidanceDifference);
const lead = answerTextForClipboard(answer);
const at = lead ? text.indexOf(lead) : -1;
if (at < 0) return `${text}\n\n${line}`;
const end = at + lead.length;
return `${text.slice(0, end)}\n\n${line}${text.slice(end)}`;
}

/**
* The clipboard payload for an answer. `renderCopyText` remains the primary
* product string, except its finalized answer lead is replaced with the same
Expand Down Expand Up @@ -208,7 +223,7 @@ export function buildAnswerClipboardText({
routingMode: answer.routingMode,
});
const copied = composeAnswerClipboardText({
renderCopyText: renderCopyTextWithCanonicalAnswer(renderCopyText, answer),
renderCopyText: withGuidanceDifferenceLine(renderCopyTextWithCanonicalAnswer(renderCopyText, answer), answer),
sourceOnly,
state,
degradedReason: degraded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ function StagedAnswerResultSurfaceImpl({
openSourceIndex={openSourceIndex}
showCopyAction={false}
isRefusal={refusalAnswer}
guidanceDifference={answer.guidanceDifference}
copied={copiedAnswer}
onCopy={onCopyAnswer}
/>
Expand Down
1 change: 1 addition & 0 deletions src/components/clinical-dashboard/answer-thread-turn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const PriorAnswerTurnSurface = memo(function PriorAnswerTurnSurface({
sourceLinks={leadSourceLinks}
railRows={railSources}
isRefusal={refusalAnswer}
guidanceDifference={turn.answer.guidanceDifference}
copied={copied}
onCopy={() =>
onCopy(
Expand Down
5 changes: 5 additions & 0 deletions src/data/reviewed-guidance-differences.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 1,
"note": "Owner-signed WA versus national guidance differences. An entry shows the 'National guidance differs on this point' line under any answer whose direct support cites one of its WA chunks. Only the owner adds or signs off an entry, in his own words. Expired entries are ignored.",
"differences": []
}
11 changes: 11 additions & 0 deletions src/lib/answer-client-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ export const clientAnswerFieldsSchema = z.object({
.check(z.maxLength(100)),
),
sourceCurrencyWarning: z.optional(z.enum(["supporting", "retrieved"])),
// Owner-signed WA versus national difference (src/lib/rag/guidance-difference-note.ts).
guidanceDifference: z.optional(
z.object({
differenceId: id,
title: z.string().check(z.minLength(1), z.maxLength(300)),
year: z.int().check(z.minimum(1900), z.maximum(2100)),
page: z.nullable(z.int().check(z.minimum(1), z.maximum(10_000))),
documentId: id,
chunkId: id,
}),
),
answerContractVersion: z.optional(z.literal(ragAdaptiveAnswerPromptVersion)),
renderAdaptiveAnswer: z.optional(z.boolean()),
// Disclosure persists with the answer; feedback credentials deliberately do not.
Expand Down
8 changes: 8 additions & 0 deletions src/lib/answer-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
sourceGovernanceWarnings,
} from "@/lib/source-governance";
import type { RagAnswer } from "@/lib/types";
import { guidanceDifferenceNoteForAnswer } from "@/lib/rag/guidance-difference-note";
import { carryRagProgrammeTelemetry } from "@/lib/rag/rag-programme-telemetry";
import {
classifyRagFallbackReason,
Expand All @@ -14,6 +15,12 @@ import {
publicFallbackReason,
} from "@/lib/rag/rag-fallback-reason";

/** Owner-signed WA versus national note; absent unless a signed-off WA chunk directly supports the answer. */
function withGuidanceDifference(answer: RagAnswer) {
const guidanceDifference = guidanceDifferenceNoteForAnswer(answer);
return guidanceDifference ? { guidanceDifference } : {};
}

function answerFallbackReasonCode(
answer?: Pick<
RagAnswer,
Expand Down Expand Up @@ -113,6 +120,7 @@ export function buildGovernedAnswerClientResponse(answer: RagAnswer) {
telemetryAnswer: answer,
payload: {
...toClientAnswerPayload({ ...answer, fallbackReasonCode }),
...withGuidanceDifference(answer),
fallbackReasonCode,
degradedMode: answerDegradedModeSignal({ ...answer, fallbackReasonCode }),
sourceGovernanceWarnings: warnings,
Expand Down
Loading
Loading