Skip to content

feat(answer): compose follow-up chips from the S2 menu, gate them on evidence (packet S3 / A4) - #2108

Merged
BigSimmo merged 4 commits into
mainfrom
claude/s3-follow-up-suggestions-95e160
Aug 18, 2026
Merged

feat(answer): compose follow-up chips from the S2 menu, gate them on evidence (packet S3 / A4)#2108
BigSimmo merged 4 commits into
mainfrom
claude/s3-follow-up-suggestions-95e160

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

  • Packet S3 (A4) refines the existing follow-up suggestion chips in src/lib/answer-follow-up.ts in place, closing Track A of the RAG improvement programme. buildAnswerFollowUpSuggestions now derives its candidates from the S2 related-information menu (buildRelatedInformationMenu, src/lib/rag/answer-composition.ts) for the answer's own query class and heuristic intent, instead of the previous fixed template list keyed off a regex over the question text.
  • Every candidate must now be supported by the retrieved evidence the client actually received — source snippets, quote cards, and the server-derived safety findings. The subject may also come from the query analysis (medications, canonical terms), but query-derived terms deliberately cannot vouch for corpus coverage: only retrieved evidence can. A chip that names a topic this corpus never surfaced is exactly what this gate exists to prevent.
  • Suggestions the answer already covered are suppressed: a menu kind the answer emitted as an answerSections entry is skipped, and so is a question whose concept words already appear in the answer body. When nothing survives, the function returns an empty list and no chip row renders. That is the intended conservative outcome for a clinical surface, not a regression.
  • Phrasing stays deterministic, the four-suggestion cap and the thread de-duplication are unchanged, there is no provider call, and the added cost is one bounded lowercase string scan inside the render memo that already existed. The subject is also read back out of the clinician's own question so acronyms keep their casing ("ADHD", not "adhd"), and an answer-derived medication no longer hijacks the subject of an unrelated question (an agitation question no longer produces "…for olanzapine?" chips).
  • Query classes whose S2 menu is deliberately none (document_lookup, unsupported_or_general) keep the previous narrow templates, now run through the same evidence gate and suppression, so §A2's "stays narrow" rule holds without deleting the surface for document lookups.
  • Scope boundaries observed: no second follow-up module, no new RagAnswer field, no new render block, no generation-prompt change, and no edit to anything under src/lib/rag/** — that directory is only imported from. ClinicalDashboard.tsx is unchanged because the input contract did not move: queryClass and queryAnalysis.intent already ride on RagAnswer. buildAnswerFollowUpQuery is untouched.
  • New focused proof: tests/answer-follow-up.test.ts covers menu derivation per class and intent, the evidence gate with positive and discriminating negative cases, both suppression paths, and an alignment assertion over all 48 class×intent cells so a future S2 menu edit fails offline instead of silently dropping a chip. tests/answer-follow-up-chips.dom.test.tsx renders the chip component with each call site's exact props — the desktop answer surface and the phone composer dock — and pins both call sites to the same function.
  • Docs: docs/rag-improvement/HANDOVER.md opens the S3 status row and corrects the stale S2 row (S2 merged 2026-08-18 as squash dda4956ff, canary pair 32100681177 -> 32111839806 green, eval:answer-quality neutral with the owner's blinded read pending). docs/rag-behaviour/behaviour-map.md §8 records that S3 is a read-only consumer of the composition menu.

RAG impact: no retrieval behaviour change — deterministic follow-up composition only; generation prompt untouched

Verification

  • npm run verify:pr-local

Heavy/executable scope was selected as expected for a src/lib/** change. Decisive lines:

PR-local verification summary:
- completed: check:runtime, check:installed-lock-parity, format:changed, sitemap:check, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues, check:ledger-write-discipline, lint, typecheck
- failed: test (exit 1)
- not reached: build, eval:rag:offline, eval:rag:adversarial:offline, check:medication-interactions, check:medication-lexicon-report

  Test Files  6 failed | 656 passed | 2 skipped (664)
       Tests  8 failed | 7079 passed | 29 skipped (7116)

# the five not-reached steps, run directly afterwards on the same commit:
Compiled successfully in 2.6min                                   # npm run build
Client bundle secret surface check passed.
Offline RAG fixture and manifest validation passed (36 golden cases, 26 suites).
  Test Files  26 passed (26)
       Tests  623 passed (623)                                    # eval:rag:offline
  Test Files  1 passed (1)
       Tests  25 passed (25)
Offline adversarial fixture validation and regression harness passed.
[medication-interactions] data/medication-interaction-index.json is up to date (525 rows).
[lexicon-report] docs/medication-interaction-lexicon-review.md is up to date (37 catalogue terms).

The unit suite's 8 failures are in 6 files this diff does not touch, and none of them import the changed module:

tests/session-start-hook.test.ts       AssertionError (POSIX vs Windows temp path)
tests/codex-cloud-setup.test.ts        Error: Test timed out in 30000ms
tests/design-sync-contract.test.ts     Error: Test timed out in 30000ms / 120000ms
tests/test-runner-safety.test.ts       Error: Test timed out in 30000ms
tests/universal-search.test.ts         AssertionError: expected [ 4 ] to deeply equal [ 3, 4, 5 ]
tests/document-viewer-page-virtualization.dom.test.tsx  Error: Test timed out in 60000ms

Proved rather than asserted. tests/session-start-hook.test.ts fails identically at the merge base
(e1749bf8d, checked out in a separate detached worktree, never a stash): Test Files 1 failed | 4 passed (5).
The other five pass when re-run in isolation on this branch — Test Files 3 passed (3) / Tests 72 passed (72)
and Test Files 2 passed (2) / Tests 45 passed (45) — so those are Windows parallel-load timeouts in a
7,116-test run, not regressions. The five gate steps the failing test step blocked were then run directly on the
same commit and all passed, as shown above.

Focused proof, run first and re-run after npm run format:

  Test Files  1 passed (1)
       Tests  27 passed (27)          # npx vitest run tests/answer-follow-up.test.ts

  Test Files  1 passed (1)
       Tests  6 passed (6)            # npx vitest run tests/answer-follow-up-chips.dom.test.tsx

The S2 composition menu is unchanged by this PR, proved directly:

  Test Files  1 passed (1)
       Tests  9 passed (9)            # npx vitest run tests/answer-composition.test.ts
  • UI verification not run: npm run verify:ui and npm run verify:phone-chrome were deliberately skipped. No shared composer chrome, layout, reserve, or styling changed — the chip components, their props, their layouts, and the phone dock reserve wiring (hasAnswerFollowUps) are all byte-identical. The only change reaching the DOM is which strings the existing chip row receives, and that is proved in jsdom on both surfaces by tests/answer-follow-up-chips.dom.test.tsx.
  • No provider-backed command was run and none is required: no live probe, no canary pair, no eval:answer-quality, no eval:rag, no Supabase read. This change is deterministic client-side composition over an answer that already exists; retrieval, ranking, selection, routing, and the generation prompt are all untouched.
  • npm run check:production-readiness was not run: answer generation, ingestion, source governance, privacy, and environment behaviour are unchanged by this diff. verify:pr-local already ran eval:rag:offline and eval:rag:adversarial:offline for the RAG-adjacent scope.

Risk and rollout

  • Risk: low-to-moderate and confined to a presentational clinical surface. The realistic failure mode is under-suggestion — a chip row that renders fewer chips, or none, when the retrieved evidence is thin or the client's bounded source snippets do not mention the kind's subject matter. That is deliberate: the client only ever receives a ≤900-character snippet per source (trimSourceForClient), so the gate can under-suggest but never over-suggest. Nothing about the answer, its citations, its verification, or its render trust ladder changes.
  • Rollback: revert this single commit. The function returns to its previous template list immediately; there is no cache, migration, prompt version, or stored state involved.
  • Provider or production effects: None.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

The chips are clinical output: each one asserts that this corpus can answer that question next. The evidence gate is what makes that assertion honest, and the suppression rules stop the surface from offering back a question the answer already answered. No claim rendering, citation requirement, or degradation path changed, and the failure direction is conservative (fewer chips, never an unsupported one). Deployment classification is unaffected: this adds no new clinical decision-support behaviour — it narrows an existing suggestion surface.

Notes

Before / after for the ten requested questions

Generated offline from hand-built RagAnswer fixtures — realistic query classes, intents, answer bodies, emitted sections, and short source snippets — by a scratchpad-only harness run twice: once at the merge base (e1749bf8d) before any edit, then again on this change. These are fixture outputs, not live answers; no provider call was made. The fixtures are not committed, so this table is illustrative evidence of the composition change, and the committed tests are the durable contract.

# Question (fixture class / intent) Before After
1 Lithium dosing? (medication_dose_risk / drug_dosing) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? What monitoring is required for lithium? · What cautions or contraindications apply to lithium? · What should trigger stopping or escalating lithium? · How is lithium dosed in renal or hepatic impairment?
2 What is the maximum dose of sertraline? (medication_dose_risk / drug_dosing) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? What cautions or contraindications apply to sertraline? · How is sertraline dosed in renal or hepatic impairment?
3 Clozapine monitoring requirements? (medication_dose_risk / protocol) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? What should trigger stopping or escalating clozapine?
4 What IM or PO options are listed for agitation? (broad_summary / protocol) What monitoring is required for olanzapine? · What are the main cautions for olanzapine? · What should I document for olanzapine? · What would change the management plan? What is the first-line management for agitation? · What should I document for agitation?
5 Summarize the discharge guidance (document_lookup / document_lookup) What are the key action points? · What monitoring or follow-up is documented? · Are there any contraindications noted? · Summarise the practical steps for discharge. Summarise the practical steps for discharge.
6 What metformin renal dosing limits apply? (medication_dose_risk / drug_dosing) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? What monitoring is required for metformin?
7 What valproate pregnancy contraindication guidance is indexed? (medication_dose_risk / drug_dosing) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? What monitoring is required for valproate?
8 Lamotrigine rash action? (medication_dose_risk / escalation_risk) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? Who should be contacted or referred for lamotrigine? · What should I document for lamotrigine?
9 Lithium toxicity action? (medication_dose_risk / escalation_risk) What about renal impairment? · What monitoring is required? · What are the elderly dosing considerations? · What about pregnancy or breastfeeding? What thresholds trigger those actions for lithium? · Who should be contacted or referred for lithium? · What should I document for lithium?
10 ADHD medication monitoring? (broad_summary / protocol) What monitoring is required for methylphenidate? · What are the main cautions for methylphenidate? · What should I document for methylphenidate? · What would change the management plan? What is the first-line management for ADHD? · What should I document for ADHD?

What the table shows, case by case:

  • Rows 1, 8 and 9 are the menu working: the same class now yields the dosing menu or — on the escalation_risk intent — the escalation menu, which is S2's only intent refinement.
  • Rows 2, 3, 6 and 7 are the two new filters doing their job. Row 3 drops the monitoring chip because the answer emitted a monitoring_timing section, and drops the renal chip because that fixture's clozapine snippet never mentions renal or hepatic dosing. Row 6 drops the renal chip because the answer itself gave the eGFR limits, and row 7 drops the contraindications chip because the answer emitted a contraindications_cautions section. Before, all four questions received the identical four chips regardless of what the corpus held or what the answer had already said — including "What about pregnancy or breastfeeding?" on a metformin renal-dosing answer whose sources say nothing about pregnancy.
  • Rows 4 and 10 show the subject fix: the chips now name what was asked (agitation, ADHD) rather than a medication the answer happened to list, and the acronym keeps the clinician's casing.
  • Row 5 is the none-menu path staying narrow: the monitoring/follow-up chip is suppressed because the answer already mentions follow-up, and the key-action-points chip is gated out because that fixture's snippet does not use those words.

Follow-up

Track A of the programme closes here. Outstanding owner decisions are all in S7+: the Gate B verdict for the Docling lab, B5/B6, and the B7 dataset. Issue #SDQSFD (ci-change-scope rag_eval_changed misses src/lib/rag/**) is queued for its own PR and deliberately untouched here.

…evidence (packet S3 / A4)

buildAnswerFollowUpSuggestions previously offered the same four templated
questions for every medication answer, keyed off a regex over the question
text. It could name a subject the corpus never surfaced, and it asked again for
something the answer had already given.

Refine it in place per docs/rag-improvement/README.md A4:

- candidates come from the S2 related-information menu
  (buildRelatedInformationMenu) for the answer's own query class and intent,
  with one deterministic question per menu item, index-aligned with the menu;
- each candidate must be supported by the retrieved evidence the client
  actually received (bounded source snippets, quote cards, safety findings).
  Query-derived canonical terms vouch for the SUBJECT only, never for corpus
  coverage, so the gate can under-suggest but never over-suggest;
- a kind the answer already emitted as an answerSections entry, or whose
  concept words are already in the answer body, is suppressed. When nothing
  survives, no chip row renders;
- classes whose menu is "none" keep the previous narrow templates, now gated
  and suppressed the same way;
- the subject is read back out of the clinician's question, so acronyms keep
  their casing and an answer-derived medication no longer hijacks the subject
  of an unrelated question.

The four-chip cap, thread de-duplication and deterministic phrasing are
unchanged; there is no provider call and no new render path. Nothing under
src/lib/rag/** is edited - answer-composition.ts is only imported.
ClinicalDashboard.tsx is unchanged: queryClass and queryAnalysis.intent already
ride on RagAnswer, so the input contract did not move.

RAG impact: no retrieval behaviour change - deterministic follow-up composition
only; generation prompt untouched.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 53 minutes

Limit details: You’ve used all 1 included review currently available under your plan. You completed 102 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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e614034-6030-4b98-b110-f25c9f10380d

📥 Commits

Reviewing files that changed from the base of the PR and between 72aa188 and 3d28816.

📒 Files selected for processing (6)
  • docs/branch-review-records/cc149b75e900816f35298154ab35a6e553ac49a88cacbd0b7ea4de568250259a.record.md
  • docs/rag-behaviour/behaviour-map.md
  • docs/rag-improvement/HANDOVER.md
  • src/lib/answer-follow-up.ts
  • tests/answer-follow-up-chips.dom.test.tsx
  • tests/answer-follow-up.test.ts

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Aug 18, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo enabled auto-merge (squash) August 18, 2026 11:00
@BigSimmo
BigSimmo merged commit 511d22f into main Aug 18, 2026
24 checks passed
@BigSimmo
BigSimmo deleted the claude/s3-follow-up-suggestions-95e160 branch August 18, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant