Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0537cc9
fix(rag,worker): apply surgical RAG telemetry, attribution, comparato…
BigSimmo Oct 17, 2026
c9fdbe9
fix: Record provider attempts independently of rollout
qodo-code-review[bot] Oct 17, 2026
8edf7df
fix: 2 findings — Enforce worker scope atomically during claims; Filter
qodo-code-review[bot] Oct 17, 2026
fe6daeb
fix: 2 findings — Require complete claim support for documents; Aggregat
qodo-code-review[bot] Oct 17, 2026
c65c7d9
fix: Remove the unused degradation argument
qodo-code-review[bot] Oct 17, 2026
a1f74f0
fix: resolve PR 3628 review findings and main conflicts
Oct 17, 2026
d8976b7
refactor(rag): keep document support lookup within module budget
Oct 17, 2026
a161848
merge: retain concurrent bot fixes and consolidate atomic claim migra…
Oct 17, 2026
c5c5b88
fix(db): keep one reviewed scoped claim migration
Oct 17, 2026
faf351d
test(db): seal scoped claim migration and cover mixed queues
Oct 17, 2026
8f705e5
chore(db): regenerate drift manifest for scoped claim_ingestion_jobs
BigSimmo Oct 17, 2026
8de2157
fix(worker): a malformed claim allowlist claims nothing, not every job
BigSimmo Oct 17, 2026
93ef40a
fix(rag): cite covering chunks in support lists and ignore lookup verbs
BigSimmo Oct 17, 2026
155460a
fix(viewer): label the served file by MIME type before its filename
BigSimmo Oct 17, 2026
6c170dc
Merge branch 'main' into fix/rag-ingestion-surgical-batch-20261017
BigSimmo Oct 17, 2026
91f7456
fix(claude-hooks): don't report EPIPE from a completed SessionStart d…
BigSimmo Oct 17, 2026
15c9b40
fix(rag): pick the smallest covering chunks and drop more lookup wording
BigSimmo Oct 17, 2026
03ccc17
fix(worker): validate allowlisted document IDs before the scoped clai…
BigSimmo Oct 17, 2026
4ecf438
fix(viewer): label image MIME types as images, never PDF
BigSimmo Oct 17, 2026
84fec64
chore(governance): 45-day engineering drift exception for tests/answe…
BigSimmo Oct 18, 2026
ba5f467
fix(rag): cite the smallest covering chunk set within the citation bu…
BigSimmo Oct 18, 2026
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ CHUNK_STRATEGY=page
WORKER_POLL_MS=30000
WORKER_BATCH_SIZE=3
WORKER_CONCURRENCY=1
# Optional comma-separated claim allowlists; both must match when set.
# Requires the scoped claim RPC migration; blank means unrestricted.
WORKER_ALLOWED_DOCUMENT_IDS=
WORKER_ALLOWED_FILE_TYPES=
WORKER_MAX_ATTEMPTS=3
WORKER_STALE_AFTER_MINUTES=45
WORKER_HEALTH_BACKOFF_MS=120000
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| 2026-10-17 | HEAD | 0537cc935b86824db40ca0c117894b67638e2d95 | PR 3628 review and fixes | Found atomic worker-scope, comparator-unit, telemetry-budget, and document-source retention defects; fixes prepared with regression coverage. | Independent standards and specification inspection of the pinned PR diff; follow-up working-tree targeted proof 295/295 tests and 10 local SQL scenarios; hosted CI pending. |
7 changes: 7 additions & 0 deletions docs/clinical-hazard-controls.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@
"recordedOn": "2026-10-14",
"expiresOn": "2026-10-21"
},
{
"path": "tests/answer-verification.test.ts",
"reason": "PR #3628 adds regression tests for the 'no more than' comparator/unit parsing in src/lib/answer-verification.ts (itself under the existing PR #3546 drift exception). Engineering drift exception per owner ruling 2026-10-18: the owner will review and reseal this test file before this exception expires. Not a clinical review, sign-off or reseal.",
"recordedBy": "Engineering, Grok Bot sweep session for PR #3628, per owner ruling 2026-10-18. Engineering drift record only; not clinical-owner approval or release signoff.",
"recordedOn": "2026-10-18",
"expiresOn": "2026-12-02"
},
{
"path": "src/lib/clinical-safety.ts",
"reason": "Drift predates the blocking gate. Safety-behaviour changes since the 2026-08-23 review (e.g. 41b5a025a escalate/monitoring matching, 64db243a9 stop-instruction labelling, 1aa717e88 no all-clear over a failed medication check) need clinical re-review, then a re-seal.",
Expand Down
19 changes: 19 additions & 0 deletions docs/worker-deploy-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -661,3 +661,22 @@ Redeploy the previous image tag. The worker holds no durable local state; all
artifact writes are idempotent per generation/chunk-key, and completion is
gated by the strict completion RPCs plus the edge agent, so a redeploy costs at
most one stale window of latency on any in-flight job and zero data loss.

### Scoped Node worker claims

`WORKER_ALLOWED_DOCUMENT_IDS` is a comma-separated UUID allowlist;
`WORKER_ALLOWED_FILE_TYPES` is a comma-separated MIME-type allowlist (for example,
`text/plain,application/pdf`). Blank lists leave that dimension unrestricted; a
non-blank list with no valid entries (for example `,`), or any document ID that is not
a UUID, claims no jobs at all and logs the configuration error once;
when both lists are set, a job must satisfy both. MIME types are case-insensitive
and ignore parameters such as `charset`. UUIDs are normalized to lowercase.

The worker passes active lists to the five-argument `claim_ingestion_jobs` overload.
The database filters candidates before acquiring leases, then rechecks the locked
document's MIME type. Unrelated queued jobs neither block scoped jobs nor consume
attempts. Existing workers with no scope keep using the three-argument contract.
A scoped worker requires the scope migration; a missing overload fails closed
rather than falling back to an unfiltered claim. The migration changes the live
database when merged to `main`: merge only inside the owner's specifically
approved database-change window. No live database change is authorized by this PR.
5 changes: 4 additions & 1 deletion scripts/claude-session-start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ for (const [label, command, args, timeout] of steps) {
const result = spawnSync(command, args, { cwd: repoRoot, input: payload, encoding: "utf8", timeout });
collect(result.stdout || "", label === "cloud setup");
if (result.stderr) process.stderr.write(result.stderr);
if (result.status !== 0 || result.error)
// A dependent that exits 0 without reading stdin races the payload write and leaves
// EPIPE in result.error; it still completed, so only other errors count as failure.
const failed = result.status !== 0 || (result.error && result.error.code !== "EPIPE");
if (failed)
contexts.push(`SessionStart ${label} did not complete; check its diagnostics before trusting related checks.`);
}
if (contexts.length)
Expand Down
16 changes: 9 additions & 7 deletions src/components/DocumentViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { documentDisplayTitle } from "@/components/DocumentOrganizationBadges";
import { ContextualBackLink } from "@/components/contextual-back-link";
import { documentFileKindFromType } from "@/components/clinical-dashboard/document-ui";
import { useActiveScrollOwner } from "@/components/clinical-dashboard/use-active-scroll-owner";
import { PhoneHeaderCollapsePortal } from "@/components/clinical-dashboard/phone-header-collapse-portal";
import { useDocumentViewerChromeScroll } from "@/components/clinical-dashboard/use-document-viewer-chrome-scroll";
Expand Down Expand Up @@ -1010,6 +1011,7 @@ export function DocumentViewer({
const generatedAnswerIsSummary = summaryQuery === documentSummaryQuestion;
const storedSummaryText = document?.summary?.summary ?? null;
const documentLabels = document?.labels;
const viewerFileKind = documentFileKindFromType(document?.file_type, document?.file_name, "PDF");
const formattedStoredSummary = useMemo(() => formatDocumentSummary(storedSummaryText), [storedSummaryText]);
const summaryBadges = useMemo(
() => buildDocumentSummaryBadges({ labels: documentLabels, summaryText: storedSummaryText }),
Expand Down Expand Up @@ -1332,7 +1334,7 @@ export function DocumentViewer({
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-[color:var(--surface-subtle)] text-[color:var(--text-muted)]">
<ExternalLink aria-hidden="true" className="h-4 w-4" />
</span>
Open original PDF
{`Open original ${viewerFileKind}`}
</a>
) : (
<a
Expand All @@ -1343,7 +1345,7 @@ export function DocumentViewer({
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-[color:var(--surface-subtle)] text-[color:var(--text-muted)]">
<ExternalLink aria-hidden="true" className="h-4 w-4" />
</span>
Open original PDF
{`Open original ${viewerFileKind}`}
</a>
)}
<button
Expand All @@ -1362,7 +1364,7 @@ export function DocumentViewer({
<Download aria-hidden="true" className="h-4 w-4" />
)}
</span>
{downloadingSource ? "Preparing PDF" : "Download PDF"}
{downloadingSource ? `Preparing ${viewerFileKind}` : `Download ${viewerFileKind}`}
</button>
</div>
{canUseAdministrativeApis ? (
Expand Down Expand Up @@ -1476,7 +1478,7 @@ export function DocumentViewer({
src={previewFrameSource}
controls={pdfFrameControls}
{...(effectiveLoadingDocument
? { state: "loading" as const, loadingLabel: "Preparing PDF preview" }
? { state: "loading" as const, loadingLabel: `Preparing ${viewerFileKind} preview` }
: effectiveViewerError || previewError
? {
state: "error" as const,
Expand All @@ -1488,7 +1490,7 @@ export function DocumentViewer({
effectiveLoadingDocument ? (
<ul className="mt-3 space-y-1 text-left text-xs font-medium text-[color:var(--text-muted)]">
<li>Loading source metadata</li>
<li>Preparing PDF preview</li>
<li>{`Preparing ${viewerFileKind} preview`}</li>
<li>Loading extracted tables</li>
</ul>
) : undefined
Expand All @@ -1498,7 +1500,7 @@ export function DocumentViewer({
{signedUrl ? (
<a href={signedUrl} target="_blank" rel="noreferrer" className={secondaryButton}>
<ExternalLink aria-hidden="true" className="h-4 w-4" />
Source PDF
{`Source ${viewerFileKind}`}
</a>
) : null}
{downloadSignedUrl ? (
Expand All @@ -1513,7 +1515,7 @@ export function DocumentViewer({
) : (
<Download aria-hidden="true" className="h-4 w-4" />
)}
{downloadingSource ? "Preparing PDF" : "Download PDF"}
{downloadingSource ? `Preparing ${viewerFileKind}` : `Download ${viewerFileKind}`}
</button>
) : null}
</>
Expand Down
24 changes: 24 additions & 0 deletions src/components/clinical-dashboard/document-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@ export function documentFileKind(fileName?: string | null, fallback = "PDF") {
return extension || fallback;
}

const mimeFileKinds: Record<string, string> = {
"application/pdf": "PDF",
"text/plain": "TXT",
"text/csv": "CSV",
"application/msword": "DOC",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX",
"application/vnd.ms-excel": "XLS",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
"image/png": "PNG",
"image/jpeg": "JPG",
"image/gif": "GIF",
"image/webp": "WEBP",
"image/svg+xml": "SVG",
};
Comment thread
BigSimmo marked this conversation as resolved.
Resolved

/** The served file's kind: a recognised MIME type wins; the filename extension is only a fallback. */
export function documentFileKindFromType(fileType?: string | null, fileName?: string | null, fallback = "PDF"): string {
const mime = fileType?.split(";")[0]?.trim().toLowerCase();
if (mime && mimeFileKinds[mime]) return mimeFileKinds[mime];
// The viewer renders any image/* as an image, so never fall back to "PDF" for one.
if (mime?.startsWith("image/")) return documentFileKind(fileName, "IMAGE");
return documentFileKind(fileName, fallback);
}

export function documentTileTone(kind: string): DocumentTileTone {
const normalized = kind.toUpperCase();
return normalized === "DOC" || normalized === "DOCX" ? "info" : "teal";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
DocumentActionButton,
DocumentFileTile,
DocumentMetaRow,
documentFileKind,
documentFileKindFromType,
documentTileTone,
} from "@/components/clinical-dashboard/document-ui";
import { cn, panel, floatingControl, ignoreUnavailableActivation, primaryControl } from "@/components/ui-primitives";
Expand All @@ -20,7 +20,7 @@ const primaryButton = primaryControl;
const secondaryButton = floatingControl;

function compactDocumentType(document: ClinicalDocument) {
return documentFileKind(document.file_name, "PDF");
return documentFileKindFromType(document.file_type, document.file_name, "PDF");
}

// Derive the header eyebrow from the document's real type instead of asserting
Expand Down
19 changes: 11 additions & 8 deletions src/lib/answer-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ export type LabelledNumericBandConflict = {
};

const clinicalQuantityPattern =
/\b(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most)\s+)?(\d+(?:[.,]\d+)?)(?:\s*[-–—]\s*(\d+(?:[.,]\d+)?))?\s*(ug|µg|μg|mcg|micrograms?|mg|g|kg|ml|l|mmol|mol|umol|µmol|ng|units?|iu|milliseconds?|msecs?|ms|tablets?|puffs?|hours?|hrs?|h|days?|weeks?|wk|months?|minutes?|mins?|years?|°c|mmhg|bpm|%)\b((?:\s*\/\s*(?:kg|m2|dose|seconds?|secs?|minutes?|mins?|hours?|hrs?|h|days?|weeks?|wk|ml|l)){0,3})/giu;
/\b(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than)\s+)?(\d+(?:[.,]\d+)?)(?:\s*[-–—]\s*(\d+(?:[.,]\d+)?))?\s*(ug|µg|μg|mcg|micrograms?|mg|g|kg|ml|l|mmol|mol|umol|µmol|ng|units?|iu|milliseconds?|msecs?|ms|tablets?|puffs?|hours?|hrs?|h|days?|weeks?|wk|months?|minutes?|mins?|years?|°c|mmhg|bpm|%)\b((?:\s*\/\s*(?:kg|m2|dose|seconds?|secs?|minutes?|mins?|hours?|hrs?|h|days?|weeks?|wk|ml|l)){0,3})/giu;
const scientificQuantityPattern =
/(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)\s*[×x]\s*10\^?(\d+)\s*\/\s*([a-z])/giu;
/(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)\s*[×x]\s*10\^?(\d+)\s*\/\s*([a-z])/giu;
const unitlessThresholdPattern =
/(?:\b(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most)\s+|(<=|>=|<|>|≤|≥)\s*)(\d+(?:[.,]\d+)?)(?:\s*[-–—]\s*(\d+(?:[.,]\d+)?))?/giu;
/(?:\b(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than)\s+|(<=|>=|<|>|≤|≥)\s*)(\d+(?:[.,]\d+)?)(?:\s*[-–—]\s*(\d+(?:[.,]\d+)?))?/giu;
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
// Infer omitted years only inside explicit adjacent population-age grammar.
// Closed continuations exclude an unknown measurement/category noun after the value.
const populationAgePrefixPattern = /\b(?:children|adolescents|adults|people|young[ \t]+people)(?:[ \t]+aged?)?[ \t]+$/i;
Expand Down Expand Up @@ -344,11 +344,11 @@ type LabelledNumericBand = {
};

const labelledNumericBandPattern =
/\b(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\b(?:\s+(?:scores?|ranges?|bands?|categor(?:y|ies)|zones?|severit(?:y|ies)|risks?|levels?))?\s*(?:(?:is|are|of|:|=|\|)\s*|(?:ranges?\s+)?from\s*)?\(?\s*(?:scores?\s*)?(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s*\)?/giu;
/\b(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\b(?:\s+(?:scores?|ranges?|bands?|categor(?:y|ies)|zones?|severit(?:y|ies)|risks?|levels?))?\s*(?:(?:is|are|of|:|=|\|)\s*|(?:ranges?\s+)?from\s*)?\(?\s*(?:scores?\s*)?(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s*\)?/giu;
const numericBeforeLabelBandPattern =
/(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s*(?:\(\s*|\|\s*)(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\s*(?:\)|\|)/giu;
/(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s*(?:\(\s*|\|\s*)(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\s*(?:\)|\|)/giu;
const numericBandPredicatePattern =
/(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s+(?:is|are|means?|indicates?|corresponds?\s+to)\s+(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\b/giu;
/(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s+(?:is|are|means?|indicates?|corresponds?\s+to)\s+(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\b/giu;
const labelFirstBetweenBandPattern =
/\b(very\s+(?:high|low|severe)|borderline|moderate|medium|minimal|normal|high|low|mild|severe|green|amber|red)\b(?:\s+(?:scores?|ranges?|bands?|categor(?:y|ies)|zones?|severit(?:y|ies)|risks?|levels?))?\s*(?:(?:is|are|of|:|=|\|)\s*)?\(?\s*(?:scores?\s*)?between\s+(\d+(?:[.,]\d+)?)\s+and\s+(\d+(?:[.,]\d+)?)(?:\s*(%|points?|mg|mcg|ug|µg|μg|g|kg|mmol(?:\s*\/\s*l)?|mol(?:\s*\/\s*l)?|ng(?:\s*\/\s*ml)?))?\s*\)?/giu;
const betweenFirstLabelBandPattern =
Expand Down Expand Up @@ -902,7 +902,7 @@ type NumericBandReference = {
};

const numericBandReferencePattern =
/\bbetween\s+(\d+(?:[.,]\d+)?)\s+and\s+(\d+(?:[.,]\d+)?)|(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?/giu;
/\bbetween\s+(\d+(?:[.,]\d+)?)\s+and\s+(\d+(?:[.,]\d+)?)|(?:(below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than|<=|>=|<|>|≤|≥)\s*)?(\d+(?:[.,]\d+)?)(?:\s*(?:[-–—]|\bto\b|\bthrough\b)\s*(\d+(?:[.,]\d+)?))?/giu;

function numericBandReferences(text: string): NumericBandReference[] {
return Array.from(text.matchAll(numericBandReferencePattern), (match) => {
Expand Down Expand Up @@ -1287,7 +1287,10 @@ function clinicalValueAtomKey(atom: ClinicalValueAtom) {
function clinicalValueAtomDisplay(atom: ClinicalValueAtom) {
if (atom.kind !== "quantity") return atom.canonicalValue;
return normalizeNumericToken(
atom.rawText.replace(/^\s*(?:below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most)\s+/i, ""),
atom.rawText.replace(
/^\s*(?:below|under|less\s+than|above|over|greater\s+than|at\s+least|at\s+most|no\s+more\s+than)\s+/i,
"",
),
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/rag/rag-answer-shape-checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,13 @@ const openingSentenceActionPattern =
const drinkLimitQuantityPeriodPattern = String.raw`\d+(?:\.\d+)?\s+standard\s+drinks?\s+(?:a\s+(?:week|day)|per\s+(?:week|day)|on\s+any\s+one\s+day)`;

const openingDrinkLimitImperativePattern = new RegExp(
String.raw`^(?:for\s+[^,.;!?]{1,140},\s*)?drink\s+no\s+more\s+than\s+${drinkLimitQuantityPeriodPattern}(?:\s+and\s+no\s+more\s+than\s+${drinkLimitQuantityPeriodPattern})?(?:;\s+less\s+drinking\s+further\s+lowers\s+risk)?[.!?]["')\]]*$`,
String.raw`^(?:for\s+[^,.;!?]{1,140},\s*)?drink\s+(?:no\s+more\s+than|at\s+most)\s+${drinkLimitQuantityPeriodPattern}(?:\s+and\s+(?:no\s+more\s+than|at\s+most)\s+${drinkLimitQuantityPeriodPattern})?(?:;\s+less\s+drinking\s+further\s+lowers\s+risk)?[.!?]["')\]]*$`,
"i",
);

// The recorded adult instruction shares its upper-limit verb across both periods.
const openingSharedDrinkLimitImperativePattern =
/^for healthy adults aged \d+ years and over, drink no more than \d+ standard drinks a week and \d+ standard drinks on any one day;? drinking less further reduces risk\.$/i;
/^for healthy adults aged \d+ years and over, drink (?:no more than|at most) \d+ standard drinks a week and \d+ standard drinks on any one day;? drinking less further reduces risk\.$/i;

/** First sentence. */
export function firstSentence(value: string) {
Expand Down
3 changes: 3 additions & 0 deletions src/lib/rag/rag-answer-telemetry-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { buildCrossDocumentFusionBrief, buildCrossDocumentSynthesisPlan } from "
import { buildAnswerScoreExplanations, buildIndexingQuality, collectMemoryCards } from "@/lib/rag/rag-answer-support";
import { memoryCardChunkScore } from "@/lib/rag/rag-candidate-sources";
import {
generationDegradationLogMetadata,
readObservedGenerationDegradation,
type RagGenerationDegradationReason,
} from "@/lib/rag/rag-generation-degradation";
import type { RagAnswer, RagQueryClass } from "@/lib/types";

export { generationDegradationLogMetadata };

type AnswerLatencyTimings = NonNullable<RagAnswer["latencyTimings"]>;

/**
Expand Down
Loading
Loading