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
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD
| 2026-07-13 | codex/fix-48h-review-findings-current | 49735663370735a60870d065ed0de3b9d34e077f | last-48-hours PR remediation | Revalidated the last-48-hours findings on current main after PRs #538 and #540; retained only unique fixes across auth/cache isolation, stale-response protection, upload/routing/UI behavior, RAG coalescing, telemetry, worktree tooling, and SAST enforcement. No remaining high-confidence local defect was found in the changed scope. The approved live drift check reported only the five differences already explained by unapplied migrations from #540. | Focused Vitest 107/107; `npm run verify:pr-local` (1,762 passed, 1 skipped; production build and client-bundle scan; offline RAG 60/60); critical Chromium 8/8; live `check:drift`; `git diff --check`. Full Chromium remains advisory after the earlier runner hang; the required critical subset passed on current main. |
| 2026-07-13 | codex/public-anonymous-access | 7f3eded3d17c9daf6a443c9cac3f0553e4e9321b | production UI design and accessibility review | Fixed the fullscreen clinical-table focus leak and divergent modal implementation, removed the non-native table-surface control, and lifted meaningful production metadata from 8-10px to the 11px floor with stronger muted contrast. No remaining high-confidence defect was found in the reviewed visual scope. | Baseline/final screenshots at 1440x1000 and 390x820; focused Chromium table expansion 3/3; focused Vitest 6/6; `npm run typecheck`; targeted ESLint; type-scale and focused Prettier checks; `git diff --check`. `verify:cheap` timed out in full lint/test execution; full `verify:ui` deferred under the API confirmation boundary. |
| 2026-07-13 | main (PR #570 squash, glass header) | cc6bfc1c80902ca3c91e5ba2ebe78a80f3fd9e14 | post-merge review: CSS/visual/a11y/perf + logic/regression | No P0/P1. P2s confirmed and fixed in follow-up: build pipeline dropped ALL hand-authored backdrop-filter declarations (manual -webkit- duplicates confused Lightning CSS — header scrim, bottom dock, and composer pill were tint-only in every engine); scrim retuned to carry the bar's frost alone (header backdrop-root removed) with masks fading to true zero; private-scope alert made sticky inside main (was scroll-away in non-answer modes); scroll-hide reporter reset on breakpoint-gate change; non-forced fallback backgrounds layered to preserve the utility-wins contract. | Ran custom Playwright probe (`node scratchpad/probe-blur.mjs`, Chromium 390x844): baseline showed `getComputedStyle(.edge-glass-header-backdrop).backdropFilter === "none"` on all three passes, after fix `blur(14px)/blur(20px)/blur(26px)` — passed; Ran `node scripts/run-playwright.mjs tests/ui-smoke.spec.ts --project=chromium -g "glass header\|collapse hide\|private-scope alert\|phone (short\|long) answer stays"`: 6/6 passed; Ran `node scripts/run-playwright.mjs tests/ui-smoke.spec.ts --project=chromium` (full file): 71 passed, 4 failed (pre-existing `/privacy` heading test, fails identically on clean main); Ran `npm run verify:cheap`: exit 0 (1935 unit tests passed); Ran `npm run format:check`: passed; Not run: WebKit/Safari real-device check (no WebKit runner in this environment — served client chunk verified to pair `-webkit-backdrop-filter` with each declaration for Safari <= 17) |
| 2026-07-13 | codex/rag-review-followup | 755ac9e517a3b81f8e12a119f80f3769dd58ae4e | PR #575 post-merge review finding remediation | Fixed the P1 path that could combine a medication amount and route from separate chunks, expanded the shared explicit amount/route/frequency intent detector, corrected route-only failure classification, and added microgram-symbol coverage. Requested attributes must now be co-located with the medication subject before the text fast path is accepted. No additional high-confidence defect was found in the changed scope after integrating the production answer-budget fix from PR #580. | Focused Vitest 143/143; `npm run eval:rag:offline` (21 files, 265/265); `npm run typecheck`; targeted ESLint; full `npm test` (211 files passed, 1 skipped; 1,946 tests passed, 1 skipped); PR-local dry-run selected runtime, format, lint, typecheck, full tests, build, and offline RAG; `git diff --check`. `verify:cheap` passed all pre-test stages but its 10-minute host bound expired during the full suite; the same suite then passed independently with a longer bound. |
64 changes: 59 additions & 5 deletions src/lib/clinical-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,15 +749,15 @@ function evidenceDensityBoost(result: SearchResult, tokens: string[]) {
/** Has dose evidence support. */
export function hasDoseEvidenceSupport(result: SearchResult) {
const haystack = clinicalResultEvidenceHaystack(result);
return /\b(?:dose|dosage|dosing|mg|mcg|microgram|route|oral|intramuscular|subcutaneous|subcut|sublingual|\bim\b|\bpo\b|\bsc\b|\bsl\b|\bprn\b|administer\w*|titration|titrate|frequency|maximum|tablet|injection|antipsychotic|benzodiazepine|olanzapine|lorazepam|haloperidol|droperidol|promethazine|diazepam)\b/i.test(
return /\b(?:dose|dosage|dosing|mg|mcg|micrograms?|milligrams?|ug|route|oral|intramuscular|subcutaneous|subcut|sublingual|\bim\b|\bpo\b|\bsc\b|\bsl\b|\bprn\b|administer\w*|titration|titrate|frequency|maximum|tablet|injection|antipsychotic|benzodiazepine|olanzapine|lorazepam|haloperidol|droperidol|promethazine|diazepam)\b|[µμ]g/i.test(
haystack,
);
}

/** Has medication dose amount evidence. */
function hasMedicationDoseAmountEvidence(result: SearchResult) {
const haystack = clinicalResultEvidenceHaystack(result);
return /\b\d+(?:\.\d+)?\s?(?:mg|mcg|microgram|micrograms)\b/i.test(haystack);
return /\b\d+(?:\.\d+)?\s?(?:mg|mcg|micrograms?|milligrams?|ug|[µμ]g)\b/i.test(haystack);
}

// A passage carrying a real dose/threshold figure (a numeric table row, or a
Expand Down Expand Up @@ -1105,30 +1105,69 @@ export function normalizedClinicalSearchTokens(query: string) {

const genericMedicationDoseQueryTokens = new Set([
"administer",
"administered",
"administering",
"administration",
"amount",
"chart",
"dose",
"dosage",
"dosing",
"drug",
"frequency",
"frequent",
"frequently",
"given",
"guidance",
"listed",
"management",
"many",
"max",
"maximum",
"medication",
"medicine",
"mcg",
"mg",
"microgram",
"milligram",
"min",
"minimum",
"much",
"often",
"oral",
"orally",
"intramuscular",
"intramuscularly",
"subcutaneous",
"subcutaneously",
"subcut",
"sublingual",
"sublingually",
"route",
"shown",
"table",
"used",
"using",
"usual",
"ug",
"once",
"twice",
"daily",
"nightly",
"weekly",
"monthly",
"hourly",
"prn",
"bd",
"tds",
"qds",
"qid",
"every",
"hour",
"day",
"week",
"time",
"mouth",
"im",
"po",
"patient",
Expand All @@ -1144,11 +1183,26 @@ export function medicationDoseQuerySubjectTokens(query: string) {
return normalizedClinicalSearchTokens(query).filter((token) => !genericMedicationDoseQueryTokens.has(token));
}

const medicationDoseAmountQueryPattern =
/\b(?:dose|doses|dosage|dosages|dosing|amount|amounts|maximum|max|min|minimum|mg|mcg|micrograms?|milligrams?|ug)\b|[µμ]g|\bhow\s+much\b/i;
const medicationDoseRouteQueryPattern =
/\b(?:route|routes|oral|orally|intramuscular|intramuscularly|subcutaneous|subcutaneously|subcut|sublingual|sublingually|im|po|sc|sl|by\s+mouth)\b/i;
const medicationDoseFrequencyQueryPattern =
/\b(?:frequency|frequencies|frequent|frequently|how\s+often|how\s+frequently|once|twice|daily|nightly|weekly|monthly|hourly|prn|bd|tds|qds|qid|every\s+\d+(?:\.\d+)?\s*(?:hours?|days?|weeks?)|\d+\s+times?\s+(?:a|per)\s+(?:day|week|hour))\b/i;

/** Which explicit medication evidence attributes the query requests. */
export function medicationDoseEvidenceQueryIntent(query: string) {
return {
asksAmount: medicationDoseAmountQueryPattern.test(query),
asksRoute: medicationDoseRouteQueryPattern.test(query),
asksFrequency: medicationDoseFrequencyQueryPattern.test(query),
};
}

/** Whether the query explicitly asks for dose, route, or frequency evidence. */
export function isMedicationDoseEvidenceQuery(query: string) {
return /\b(?:dose|doses|dosage|dosages|dosing|route|oral|intramuscular|subcutaneous|subcut|sublingual|im|po|sc|sl|frequency|mg|mcg|microgram|maximum|minimum|prn)\b/i.test(
query,
);
const intent = medicationDoseEvidenceQueryIntent(query);
return intent.asksAmount || intent.asksRoute || intent.asksFrequency;
}

/** Require dose evidence to carry the medication question's clinical subject. */
Expand Down
49 changes: 35 additions & 14 deletions src/lib/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import {
hasDoseEvidenceSupport,
hasStructuredThresholdEvidence,
isMedicationDoseEvidenceQuery,
medicationDoseEvidenceQueryIntent,
medicationDoseQueryContext,
normalizedClinicalSearchTokens,
rankClinicalResults,
Expand Down Expand Up @@ -2761,7 +2762,7 @@ function hasRiskFlowchartActionEvidence(query: string, results: SearchResult[],

/** Has dose amount evidence for gate. */
function hasDoseAmountEvidenceForGate(result: SearchResult) {
return /\b\d+(?:\.\d+)?\s?(?:mg|mcg|microgram|micrograms)\b/i.test(evidenceTextForGate(result));
return /\b\d+(?:\.\d+)?\s?(?:mg|mcg|micrograms?|milligrams?|ug|[µμ]g)\b/i.test(evidenceTextForGate(result));
}

/** Has route evidence for gate. */
Expand All @@ -2771,6 +2772,13 @@ function hasRouteEvidenceForGate(result: SearchResult) {
);
}

/** Has administration frequency evidence for gate. */
function hasFrequencyEvidenceForGate(result: SearchResult) {
return /\b(?:once|twice|daily|nightly|weekly|monthly|hourly|prn|bd|tds|qds|qid|every\s+\d+(?:\.\d+)?\s*(?:hours?|days?|weeks?)|\d+\s+times?\s+(?:a|per)\s+(?:day|week|hour))\b/i.test(
evidenceTextForGate(result),
);
}

/** Has direct source image evidence. */
function hasDirectSourceImageEvidence(result: SearchResult) {
const sourceImageIds = new Set(
Expand Down Expand Up @@ -2926,9 +2934,7 @@ export function evaluateEvidenceCoverageGate(
}

if (queryClass === "medication_dose_risk") {
const asksDoseAmount = /\b(?:dose|doses|dosage|dosages|dosing|mg|mcg|microgram|maximum|minimum)\b/i.test(query);
const asksRoute =
/\b(?:route|oral|intramuscular|subcutaneous|subcut|sublingual|\bim\b|\bpo\b|\bsc\b|\bsl\b)\b/i.test(query);
const { asksAmount, asksRoute, asksFrequency } = medicationDoseEvidenceQueryIntent(query);
const agitationOk = !/\bagitation|arousal\b/i.test(query) || /\bagitation|arousal\b/i.test(evidenceText);
const hasContextualDoseEvidence = top.some(
(result) => hasDoseEvidenceSupport(result) && medicationDoseQueryContext(query, result).matched,
Expand All @@ -2945,24 +2951,39 @@ export function evaluateEvidenceCoverageGate(
hasRouteEvidenceForGate(result) &&
medicationDoseQueryContext(query, result).matched,
);
const accepted =
hasContextualDoseEvidence &&
(!asksDoseAmount || hasContextualDoseAmount) &&
(!asksRoute || hasContextualRoute) &&
agitationOk;
const hasContextualFrequency = top.some(
(result) =>
hasDoseEvidenceSupport(result) &&
hasFrequencyEvidenceForGate(result) &&
medicationDoseQueryContext(query, result).matched,
);
const hasCoLocatedRequestedEvidence = top.some(
(result) =>
hasDoseEvidenceSupport(result) &&
medicationDoseQueryContext(query, result).matched &&
(!asksAmount || hasDoseAmountEvidenceForGate(result)) &&
(!asksRoute || hasRouteEvidenceForGate(result)) &&
(!asksFrequency || hasFrequencyEvidenceForGate(result)),
);
const requestedAttributeCount = Number(asksAmount) + Number(asksRoute) + Number(asksFrequency);
const accepted = hasCoLocatedRequestedEvidence && agitationOk;
return {
accepted,
reason: accepted
? "dose_route_amount_evidence_gate"
: !hasDoseAmount
: asksAmount && !hasDoseAmount
? "missing_dose_amount_evidence"
: !hasContextualDoseEvidence || (asksDoseAmount && !hasContextualDoseAmount)
: !hasContextualDoseEvidence || (asksAmount && !hasContextualDoseAmount)
? "missing_dose_query_context"
: !hasContextualRoute && asksRoute
? "missing_route_evidence"
: !agitationOk
? "missing_agitation_context"
: "missing_dose_evidence",
: !hasContextualFrequency && asksFrequency
? "missing_frequency_evidence"
: requestedAttributeCount > 1 && !hasCoLocatedRequestedEvidence
? "missing_co_located_medication_evidence"
: !agitationOk
? "missing_agitation_context"
: "missing_dose_evidence",
strategy: "text_fast_path",
sourceImageRequired,
sourceImageSatisfied,
Expand Down
78 changes: 77 additions & 1 deletion tests/retrieval-query-variants.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, it } from "vitest";
import { analyzeClinicalQuery, buildClinicalTextSearchQuery, rankClinicalResults } from "../src/lib/clinical-search";
import {
analyzeClinicalQuery,
buildClinicalTextSearchQuery,
isMedicationDoseEvidenceQuery,
medicationDoseEvidenceQueryIntent,
rankClinicalResults,
} from "../src/lib/clinical-search";
import { expandClinicalVocabularyText } from "../src/lib/clinical-vocabulary";
import { selectRetrievalEvidence } from "../src/lib/retrieval-selection";
import {
Expand Down Expand Up @@ -310,6 +316,53 @@ describe("retrieval query variants", () => {
).toEqual({ returnFastPath: false, reason: "missing_dose_query_context" });
});

it.each([
["How much lorazepam should be given?", { asksAmount: true, asksRoute: false, asksFrequency: false }],
["How many micrograms of clonidine are used?", { asksAmount: true, asksRoute: false, asksFrequency: false }],
["Is clonidine 100 µg listed?", { asksAmount: true, asksRoute: false, asksFrequency: false }],
["How often should lorazepam be administered?", { asksAmount: false, asksRoute: false, asksFrequency: true }],
["Is olanzapine administered intramuscularly?", { asksAmount: false, asksRoute: true, asksFrequency: false }],
])("detects explicit medication evidence intent in %s", (query, expected) => {
expect(isMedicationDoseEvidenceQuery(query)).toBe(true);
expect(medicationDoseEvidenceQueryIntent(query)).toEqual(expected);
});

it("routes natural amount and frequency questions through the contextual gate", () => {
expect(
decideTextFastPath(
"How much lorazepam should be given?",
[result({ content: "Lorazepam may be used with clinical review.", similarity: 0.9 })],
"medication_dose_risk",
),
).toEqual({ returnFastPath: false, reason: "missing_dose_amount_evidence" });

expect(
decideTextFastPath(
"How often should lorazepam be administered?",
[result({ content: "Lorazepam 1 mg may be used with clinical review.", similarity: 0.9 })],
"medication_dose_risk",
),
).toEqual({ returnFastPath: false, reason: "missing_frequency_evidence" });

expect(
decideTextFastPath(
"How often should lorazepam be administered?",
[result({ content: "Lorazepam 1 mg may be administered every 6 hours.", similarity: 0.9 })],
"medication_dose_risk",
),
).toEqual({ returnFastPath: true, reason: "dose_evidence_text_match" });
});

it("accepts microgram-symbol dose evidence when it is co-located with the subject", () => {
expect(
evaluateEvidenceCoverageGate(
"How many micrograms of clonidine are used?",
[result({ content: "Clonidine 100 µg may be used with monitoring.", similarity: 0.9 })],
"medication_dose_risk",
),
).toMatchObject({ accepted: true, reason: "dose_route_amount_evidence_gate" });
});

it("keeps flowchart zone-action fast paths gated on action evidence", () => {
const query = "In the clinical flowchart, what is the next step after red-zone risk?";

Expand Down Expand Up @@ -690,6 +743,29 @@ describe("retrieval query variants", () => {
).toMatchObject({ accepted: true, reason: "dose_route_amount_evidence_gate" });
});

it("requires requested dose and route evidence to be co-located", () => {
expect(
evaluateEvidenceCoverageGate(
"How much lorazepam is administered intramuscularly?",
[
result({ id: "amount", content: "Lorazepam 1 mg may be used with monitoring.", similarity: 0.9 }),
result({ id: "route", content: "Lorazepam may be administered intramuscularly.", similarity: 0.88 }),
],
"medication_dose_risk",
),
).toMatchObject({ accepted: false, reason: "missing_co_located_medication_evidence" });
});

it("reports missing route evidence for route-only questions", () => {
expect(
evaluateEvidenceCoverageGate(
"Which route is listed for lorazepam?",
[result({ content: "Lorazepam is recommended with clinical monitoring.", similarity: 0.9 })],
"medication_dose_risk",
),
).toMatchObject({ accepted: false, reason: "missing_route_evidence" });
});

it("accepts SC and SL route evidence for dose-route fast gates", () => {
expect(
evaluateEvidenceCoverageGate(
Expand Down
Loading