Skip to content

Wire differentials, services and medications catalogues into search modes - #323

Merged
BigSimmo merged 7 commits into
mainfrom
claude/search-differentials-services-meds-wr5cab
Jul 6, 2026
Merged

Wire differentials, services and medications catalogues into search modes#323
BigSimmo merged 7 commits into
mainfrom
claude/search-differentials-services-meds-wr5cab

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Differentials search mode now searches the imported catalogue (201 diagnoses + 31 presentations). Previously the results panel rendered a hard-coded "acute confusion" demo fixture regardless of the query. src/lib/differentials.ts gains scored, alias-aware rankers (rankDifferentialRecords, rankPresentationWorkflows) and an adaptive composer (composeDifferentialSearchResults) that leads with a presentation workflow when it matches about as strongly as the best diagnosis (followed by its candidate diagnoses in ranked order), otherwise interleaves by score. Urgency (emergent/urgent) only nudges tie-breaks — it never outranks a stronger text match.
  • GET /api/differentials returns scored matches ({record|workflow, score, reasons}) for both kinds, mirroring the medications API, and ranks live owner rows directly instead of the previous O(n²) snapshot-intersect. records/presentations/total/governance keep their existing semantics (additive change).
  • New useDifferentialSearch(query) hook fetches diagnosis + presentation matches in parallel with the sibling hooks' auth/401 handling; the Differentials results view now renders real query-driven results with loading/empty/error states, functional All/Presentations/Diagnoses filters with real counts, real tag-overflow chips (no more fake +2), and accurate catalogue copy (the "synthetic demonstration content" banner is replaced by differentials-catalogue-notice).
  • Dashboard wiring: the results view is gated on search submission (persists with zero document evidence), and an empty document-corpus result in differentials mode applies empty evidence instead of erroring — catalogue results are the primary content. The /api/search document-evidence call is kept and runs in parallel.
  • Medications (prescribing) polish: the hard-coded acamprosate row highlight and alcohol/acamprosate/baclofen filter literals are replaced with rank-driven equivalents, and rankMedicationRecords gains a +5 name-prefix boost (reason "name prefix").
  • Services: already wired to the imported 219-service catalogue with scored client-side ranking — no change.

Verification

  • npm run verify:cheap — pass (lint, typecheck, 1108 vitest tests; only pre-existing warnings)
  • npm run verify:ui — targeted equivalent: full tests/ui-tools.spec.ts on chromium against the live dev server, 36/36 passed (includes the rewritten differentials search spec asserting a real catalogue result row); prescribing smoke spec also passed
  • npm run verify:release — not run (not a release/handoff change)
  • npm run format:check — pass
  • npm run eval:retrieval:quality — not run: needs live keys unavailable in this environment. Note: this change does not modify document-retrieval ranking/selection/chunking/scoring (src/lib/rag.ts, retrieval-selection.ts untouched); it adds catalogue-side rankers and client wiring only. Please run locally before merge if you want the 23/23 confirmation.
  • npm run eval:rag / eval:quality — not applicable (answer generation untouched)
  • npm run check:production-readiness — runs clean apart from expected missing-secret findings in this no-secrets environment (no Supabase/OpenAI env configured)
  • npm run check:deployment-readiness — not applicable

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use — the document-evidence panel and "Run source search" flows are preserved; catalogue results are labelled as imported catalogue content with source counts from the indexed library
  • No patient-identifiable document workflow was introduced or expanded
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy) — no env/project changes
  • Service-role keys and private document access remain server-only — API routes unchanged in their access model
  • Demo/synthetic content remains clearly separated from real clinical sources — this PR removes a synthetic demo fixture from the search flow and replaces it with the imported, locally reviewed catalogue; catalogue copy states the provenance
  • Source metadata, review status, and outdated/unknown-source behaviour remain conservative — governance payloads untouched
  • Deployment classification/TGA SaMD impact checked: ranking of the existing curated decision-support catalogue changed from static demo to query-relevance ordering; the decision-support disclaimers and review-before-use notices remain in place

Notes

  • Ranker scoring mirrors the existing rankMedicationRecords/rankServiceRecords pattern (field-weighted term matching with reasons), plus catalogue alias expansion (e.g. "confused" → confusion/delirium/encephalopathy) surfaced as a symptom alias reason.
  • Follow-ups deliberately out of scope: query-filtering the /differentials/presentations and /differentials/diagnoses stream pages (still static cards), and debouncing the per-keystroke catalogue fetches (matches the existing medications behaviour).

🤖 Generated with Claude Code

https://claude.ai/code/session_015UuyyMMegXxTeEJsyR741t


Generated by Claude Code


Note

Medium Risk
Changes clinical decision-support result ordering and copy from static demo to relevance-ranked catalogue content; document-evidence flows are preserved but users may rely on ranked catalogue rows before source search.

Overview
Differentials search replaces the hard-coded acute-confusion demo with query-driven results from the imported catalogue (diagnoses and presentations). New rankDifferentialRecords / rankPresentationWorkflows scoring (alias expansion, urgency tie-breaks) and composeDifferentialSearchResults adapt ordering (presentation-led when it matches strongly). GET /api/differentials now returns optional matches with score and reasons, ranking owner rows in one pass instead of intersecting snapshot search.

The UI adds useDifferentialSearch, real loading/empty/error states, All / Presentations / Diagnoses filters, and catalogue copy instead of demonstration banners. ClinicalDashboard shows ranked results after submit even with zero document evidence, and treats an empty corpus in differentials mode as empty evidence rather than a blocking error.

Prescribing drops hard-coded medication highlights/filters in favor of rank-based selection and adds a name-prefix boost to rankMedicationRecords. Tests and UI specs updated accordingly.

Reviewed by Cursor Bugbot for commit eda08d6. Configure here.

…odes
Differentials search mode previously rendered a hard-coded acute-confusion
demo fixture regardless of the query, and never touched the imported
232-record catalogue. Now:
- Add scored, alias-aware rankers (rankDifferentialRecords,
rankPresentationWorkflows) and an adaptive composer that leads with a
presentation workflow when it matches about as strongly as the best
diagnosis, otherwise interleaves by score.
- Return scored `matches` from GET /api/differentials for both kinds and
rank live owner rows directly (replacing the snapshot-intersect).
- Add useDifferentialSearch hook and rewire the Differentials search view
to render real query-driven catalogue results with loading/empty/error
states, functional kind filters, and accurate catalogue copy.
- Accept empty document-evidence payloads in differentials mode instead of
erroring, and gate the results view on search submission.
- Medications: replace the hard-coded acamprosate row highlight and filter
literals with rank-driven equivalents; add a name-prefix boost.
- Services already searched the imported catalogue; unchanged.
Verified: focused vitest suites, verify:cheap (1108 tests),
ui-tools.spec.ts chromium (36 passed) against the live dev server.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015UuyyMMegXxTeEJsyR741t
@supabase

supabaseBot commented Jul 6, 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 ↗︎.

The phone gap assertion measured medication-home one animation frame
after visibility, racing the post-hydration check that hides the
smart-search hint/prompt rows (restored in #318) and shrinks the
portaled composer from 111px to 61px. CI measured the transient state
(bottomGap 110.95 < topGap 134) and failed deterministically; fresh
local loads reproduced it 3 in 4 runs. Poll until two consecutive
measurements match so the guard asserts the settled layout at both
widths. Verified 5/5 passes with --repeat-each=5 against a dev server
running with the CI env vars.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015UuyyMMegXxTeEJsyR741t
@BigSimmo
BigSimmo marked this pull request as ready for review July 6, 2026 12:38
@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.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Stale source evidence after query edit
    • Source evidence now tracks the query that produced documentMatches and is hidden when the live composer text no longer matches.
  • ✅ Fixed: Filter treats first row as best
    • Result rows now use the global best-match id and catalogue rank instead of the filtered list index.
  • ✅ Fixed: Catalogue failure shows empty-match UI
    • The empty-results state is skipped when catalogue search fails so only the failure alert is shown.

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit eda08d6. Configure here.

Comment threadsrc/components/clinical-dashboard/differentials-home.tsx
Comment threadsrc/components/clinical-dashboard/differentials-home.tsx
Comment threadsrc/components/clinical-dashboard/differentials-home.tsx
cursoragentand others added 3 commits July 6, 2026 12:47
…re UI
- Hide source evidence when the composer query no longer matches the query
that produced documentMatches
- Style and rank rows by global catalogue position, not filtered-list index
- Skip empty-match UI when catalogue search fails so only the alert shows
… state
- Track the query the current document evidence was fetched for and treat
evidence from a different query as pending, so live-edited catalogue
results never render under a stale "Source-backed" panel.
- Style the overall top-ranked result as best match instead of whichever
row is first in a kind-filtered list.
- Render a distinct catalogue-failure card (retry/browse links, alert
role) instead of showing the "No catalogue matches" empty state when
the catalogue fetch errors or is unauthorized.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015UuyyMMegXxTeEJsyR741t
Both this branch and the Bugbot autofix addressed the same three review
findings. Resolution keeps this branch's implementation (the dashboard
states which query the document evidence belongs to, rather than
inferring it from match-array identity, and the catalogue-failure card
carries the browse/retry actions) and adopts the autofix's global-rank
numbering for kind-filtered rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015UuyyMMegXxTeEJsyR741t
@BigSimmo
BigSimmo enabled auto-merge July 6, 2026 13:20
claudeand others added 2 commits July 6, 2026 13:43
…ankers
Main's #325 added its own snapshot-only rankDifferentialRecords(query)
on the shared rankCatalogRecords primitive while this branch shipped a
records-parameter variant that the /api/differentials route uses to
rank live owner rows. Resolution keeps one ranker built on the shared
primitive with the records-first signature (matching the medication and
service rankers): title x8 + hinge/safety x3 fields, alias expansion
surfaced as a "symptom alias" reason via a new `expanded` signal,
exact/compact/phrase bonuses, and urgency as a tie-break only. The
primitive also gains an optional prefixValues/prefixBonus so the
medication ranker keeps this branch's name-prefix boost, and the
universal-search differentials domain passes the snapshot explicitly.
Test suites from both sides are retained on the unified signature.
Verified post-merge: verify:cheap (1152 tests), format:check, chromium
ui-tools 36/36 and ui-universal-search 3/3 against the live dev server.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015UuyyMMegXxTeEJsyR741t
@BigSimmo
BigSimmo merged commit 7c62a40 into mainJul 6, 2026
5 checks passed
@BigSimmo
BigSimmo deleted the claude/search-differentials-services-meds-wr5cab branch July 8, 2026 16:22
BigSimmo added a commit that referenced this pull request Aug 18, 2026
Sign up for freeto 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.

3 participants

@BigSimmo@claude@cursoragent