fix(rag): route the medication_dose_risk class to the strong route before the deadline (A1 R1, #231) - #2035
Conversation
…fore the deadline (A1 R1) Packet S1b, mitigation-ladder rung 3 for issue #231's residual R1: after S1, "Lithium dosing?" stayed 4/4 source-only, 3/4 as provider_timeout, because the fast-routed attempt failed the quality gates and fast_unsupported_retry_strong launched a strong generation into the fast route's leftover ~10-13s. chooseAnswerRoute now returns the strong route (new distinct reason medication_dose_risk_strong_route) at the routed-generation fallthrough for the medication_dose_risk class, so the deadline is created with the strong 35s budget and the class gets one fully budgeted strong attempt instead of a doomed leftover-budget retry. All unsupported refusals and provider-free extractive paths fire before the new branch and are unchanged; table_threshold and every other class keep byte-identical routing (pinned by new tests). No budget, threshold, quality-gate, or shouldRetryWithStrongAfterFast change. Knock-ons in the same change: the LAI-management and agitation-typo-dosing validated-extractive short-circuits now key on the new strong signature so their measured provider-free timeout skips keep firing (with negative tests for the retired fast signature), and the six supported dosing-class golden cases additionally allow the strong route ("unsupported" stays excluded). The budget-cap contract test burns 10s of retrieval fake-time so its reserve assertion stays discriminating below every plausible timeout cap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NWGr4LHgBRDVM1sGVdfCSa
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:7 minutes Limit details: You’ve used all 1 included review currently available under your plan. You completed 94 included PR reviews in the past 7 days; at that activity level, included reviews refill at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
Comment |
…2035) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NWGr4LHgBRDVM1sGVdfCSa
Uh oh!
There was an error while loading. Please reload this page.
…e conflict resolution The branch merged origin/main twice while open. The baseline record now names the current base and carries the re-measured offline numbers: the contract list grew to 25 suites / 602 tests because two suites were added concurrently - tests/rag-adversarial-fixtures.test.ts here and tests/search-route-round-trip-budget.test.ts on main - with the remaining test movement coming from packet S1b (PR #2035). ragEvalCases is still 44 and answerQualityEvalCases still 30, so both provider-backed gate denominators are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWiDFShT7JuZAjTJHhJ7Wq
Summary
medication_dose_riskquery class to the strong answer route inchooseAnswerRoute(src/lib/rag/rag-routing.ts) at the routed-generation fallthrough, before the route deadline is created. Evidence: after S1 (PR fix(rag): stop verification rejecting verbatim-faithful answers (#231 S1) #2022), "Lithium dosing?" remained 4/4 source-only, 3/4 asprovider_timeout, because the fast-routed attempt failed the quality gates andfast_unsupported_retry_stronglaunched a strong generation into the fast route's leftover ~10–13 s. Routing the class to strong up front gives the attempt the strong route's 35 s budget and removes that unbudgeted-retry failure mode. The new route carries the distinct reasonmedication_dose_risk_strong_routefor telemetry and canary attribution.broad_summary/comparisonas its exemplar classes; extending rung 3 tomedication_dose_riskis the owner decision of 2026-08-17 (R1 before S2) plus S1's recorded residual R1 — not a claim that the README already names the class. Per refuted-approach 4, strong routing cannot manufacture evidence retrieval did not surface: this PR removes the unbudgeted-retryprovider_timeoutfailure mode; it does not promise fallback elimination for evidence-absent dosing cases.drug_dosingis not routed on: it is aClinicalQueryIntent, not aRagQueryClass, assigned only within themedication_dose_risk/table_thresholdclasses. Routing on it would widenchooseAnswerRoute's signature and would only addtable_threshold-class queries with dose wording — which S1's probe evidence does not cover and which already have extractive paths plusstrongClinicalEscalationPatterncoverage. Excluded per the packet's own condition ("drug_dosingonly if evidence supports").fast/clinical_fast_grounded_synthesissignature for this class (hasValidatedGenericLaiManagementExtractiveAnswer,hasValidatedAgitationArousalTypoDosingExtractiveAnswerinsrc/lib/rag/rag-extractive-first.ts) now match the new strong route, so their provider-free timeout skips keep firing; and the six supported dosing-class golden cases whoseallowedRouteswere["extractive","fast"](clozapine-monitoring,agitation-arousal-pharmacological-management,long-acting-injectables,clozapine-monitoring-paraphrase,clozapine-missed-dose-table,agitation-arousal-typo-dosing) additionally allow"strong", because a strong route is now a by-design legitimate outcome for this class;"unsupported"remains excluded, so fail-closed regressions are still caught. The membership list was produced by classifying every golden question withclassifyRagQuery, not by hand. No alias-tier, comparator, imputation-formula, or scoring edit.RAG impact: behaviour change — canary pair 32025082010 (
2bd146eed) -> planned post-merge. The eval-canary workflow is repository_dispatch frommainonly, so the post run cannot execute pre-merge; the owner approves the dispatch after merge. Baseline is S1's green post run (document/content recall 1.0/1.0, zero per-case rr regressions, answer gate 45/45) unless a newer green canary exists at merge time.Non-inferiority argument for non-dosing classes: routing for every class other than
medication_dose_riskis byte-identical — the only edited return inchooseAnswerRouteis the block-final fast fallthrough of themedication_dose_risk/table_thresholdblock, and it is guarded byqueryClass === "medication_dose_risk";table_thresholdstill reaches the original fast return (pinned by a new test). No score threshold (0.32/0.48/0.64/0.76) moved. Allunsupportedrefusals and all provider-free extractive routes fire before the new branch and are unchanged. For the dosing class itself, mode only changes fast→strong at the fallthrough, which raises the generation budget 25 s→35 s and replaces the leftover-budget strong retry with a fully budgeted strong attempt; quality gates are unchanged, so the fallback rate for the dosing class cannot increase by this mechanism, and non-dosing classes cannot change at all. The two measured timeout-skip short-circuits for this class remain active under the new route signature (regression-tested), so their queries stay provider-free.Scope fences honoured (packet stop conditions):
shouldRetryWithStrongAfterFastunchanged;answerRouteBudgetMsandOPENAI_ANSWER_TIMEOUT_MSuntouched (#231 stop condition stands, re-confirmed by S1); no quality gate weakened; no generation-fallback caching introduced;rag.tsuntouched (4,362-line no-growth budget respected);rag-route-budget.tsuntouched;src/lib/rag/rag-row-contracts.tsuntouched (#212 sibling stream).Verification
npm run verify:pr-local— heavy fail-closed scope for this RAG-surface diff; decisive lines:completed: check:runtime, check:installed-lock-parity, format:changed, lint, typecheck, test, build, eval:rag:offline, check:medication-interactions, check:medication-lexicon-report/failed: (none)/not reached: (none), exit 0npx vitest run tests/rag-routing.test.ts tests/rag-extractive-first.test.ts—Test Files 2 passed (2)/Tests 103 passed (103)(npx vitest runused directly becausetest:focusedfails closed on test-file edits)npx vitest run tests/rag-answer-fallback.test.ts—Tests 90 passed (90)(full-file order, catching the cross-test env-freeze mode)npm run eval:rag:offline—Test Files 23 passed (23)/Tests 586 passed (586)/Offline RAG fixture and production-contract checks passed.npm run check:rag:fixtures—Offline RAG fixture and manifest validation passed (36 golden cases, 23 suites).npm run check:production-readiness— 2 PASS / 5 WARN / 2 FAIL, where both FAILs are the documented offline-container provider capability gaps (NEXT_PUBLIC_SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY,OPENAI_API_KEYabsent by design in this environment); no routing-related findingnpm run eval:retrieval:quality(provider-backed; retrieval, ranking, selection, chunking, and scoring are untouched — answer routing only). Verification not run:npm run eval:rag -- --limit 15andnpm run eval:quality -- --rag-only(provider-backed; generation prompt and post-processing untouched; the owner-approved post-merge canary pair is the live validation for this behaviour change). UI verification not run: no UI, routing-page, or styling change.scripts/probe-generation-quality.ts "Lithium dosing?") were not run in this session — provider-gated; the owner can probe post-merge to confirm the route now enters strong pre-deadlineRisk and rollout
Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes
caps a generation attempt so source-backed recovery fits inside the route budget) was made cap-independent: full-file runs ofrag-answer-fallback.test.tspinOPENAI_ANSWER_TIMEOUT_MSat its 30 000 ms default via an earlier frozen env import (stubs stop applying), which under the 35 s strong budget would have made the reserve assertion non-discriminating (min(cap, budget−reserve) = cap = what a reserve-free revert also grants). The retrieval mock now burns 10 000 ms of fake time once, so the deadline term (23 000 ms) is the binding one below every plausible cap in both isolated and full-file runs, and a reserve-free revert (25 000 ms) still fails the assertion.npm run ledger:appendin this PR.Generated by Claude Code