From 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:54:23 +0800 Subject: [PATCH 01/28] docs: align readiness runtime version --- docs/production-readiness-checklist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/production-readiness-checklist.md b/docs/production-readiness-checklist.md index e38e98c7f..1596dc639 100644 --- a/docs/production-readiness-checklist.md +++ b/docs/production-readiness-checklist.md @@ -2,9 +2,9 @@ This is the runbook to make the app publishable in one focused pass. -Last reviewed: 2026-07-04. Applies to any feature branch or release candidate. +Last reviewed: 2026-07-10. Applies to any feature branch or release candidate. -- Runtime target: Next.js 16.2.9, Node 24.x, npm 11.x. +- Runtime target: Next.js 16.2.10, Node 24.x, npm 11.x. - Supabase target: `sjrfecxgysukkwxsowpy` (`Clinical KB Database`). ## Immediate completion targets From 79526097404129a4ddb31aed824479b16b316fd3 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 02:20:52 +0800 Subject: [PATCH 02/28] fix: strip bullet-glyph artifacts and de-duplicate extractive answer stitching Adds a shared normalizeInlineBulletGlyphs sanitizer (bullet glyphs and the PDF/Word sub-bullet "o") applied across the extractive answer path, answer prose polish, source snippets, and display text; sub-bullets become fact boundaries in evidence splitting with short headings merged as context; and sentenceFromFact completes facts before entity prefixing so "For lithium, ... The guidance is that for lithium," can no longer compound. Co-Authored-By: Claude Fable 5 --- .../clinical-dashboard/display-text.ts | 5 +- src/lib/rag-answer-text.ts | 3 +- src/lib/rag-extractive-answer.ts | 98 ++++++++++++++++--- src/lib/smart-rag-api.ts | 4 +- src/lib/source-text-sanitizer.ts | 64 ++++++++---- 5 files changed, 136 insertions(+), 38 deletions(-) diff --git a/src/components/clinical-dashboard/display-text.ts b/src/components/clinical-dashboard/display-text.ts index a21b08f88..55948dedf 100644 --- a/src/components/clinical-dashboard/display-text.ts +++ b/src/components/clinical-dashboard/display-text.ts @@ -1,5 +1,6 @@ import { normalizeExtractedGlyphs, + normalizeInlineBulletGlyphs, sourceTextForCompactDisplay, sourceTextForClinicalProse, sourceTextForClinicalProsePreservingBreaks, @@ -35,7 +36,9 @@ export function looksLikeDisplayArtifact(value: string) { export function sanitizeDisplayText(value: string, options: DisplayTextSanitizeOptions = {}) { const normalized = normalizeDisplayText( - options.compactSource ? sourceTextForCompactDisplay(value) : sourceTextForClinicalProse(value), + options.compactSource + ? sourceTextForCompactDisplay(value) + : normalizeInlineBulletGlyphs(sourceTextForClinicalProse(value)), ); if (!normalized) return ""; const artifactStart = normalized.search( diff --git a/src/lib/rag-answer-text.ts b/src/lib/rag-answer-text.ts index 060624e34..6314ac9da 100644 --- a/src/lib/rag-answer-text.ts +++ b/src/lib/rag-answer-text.ts @@ -1,6 +1,7 @@ import { clinicalProseUsefulness, isLowYieldClinicalText, + normalizeInlineBulletGlyphs, sourceTextForClinicalProse, } from "@/lib/source-text-sanitizer"; @@ -204,7 +205,7 @@ function separateSettingRunOns(value: string): string { } export function polishClinicalAnswerProse(value: string) { - const cleaned = normalizeSectionText(value) + const cleaned = normalizeInlineBulletGlyphs(normalizeSectionText(value)) .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(productCatalogueFragmentPattern, " ") .replace(brandOrFormularyFragmentPattern, " ") diff --git a/src/lib/rag-extractive-answer.ts b/src/lib/rag-extractive-answer.ts index 1a6f22c8b..ad6c1dfcc 100644 --- a/src/lib/rag-extractive-answer.ts +++ b/src/lib/rag-extractive-answer.ts @@ -24,7 +24,11 @@ import { splitBalancedWords, } from "@/lib/rag-answer-text"; import { ragProviderMode } from "@/lib/rag-provider"; -import { isLowYieldClinicalText, sourceTextForClinicalProse } from "@/lib/source-text-sanitizer"; +import { + isLowYieldClinicalText, + normalizeInlineBulletGlyphs, + sourceTextForClinicalProse, +} from "@/lib/source-text-sanitizer"; import type { AnswerSection, AnswerSectionKind, @@ -65,9 +69,33 @@ type ExtractedClinicalFact = { const extractiveLabelPattern = /\b(?:Medication point|Table evidence|Threshold\/action|Risk\/escalation|Workflow step|Section summary|Source point|Dose detail|Monitoring)\s*:\s*/gi; +// Section labels that read as boilerplate rather than clinical context — never +// rewritten into "For