Skip to content

eval: answer-targeting metric + retrieval-eval process gate + quote-truncation flag (Stage 1: measurement & guardrails)#243

Merged
BigSimmo merged 3 commits into
mainfrom
claude/rag-measurement-guardrails
Jul 3, 2026
Merged

eval: answer-targeting metric + retrieval-eval process gate + quote-truncation flag (Stage 1: measurement & guardrails)#243
BigSimmo merged 3 commits into
mainfrom
claude/rag-measurement-guardrails

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Stage 1 of the safe-execution plan — measurement and guardrails, zero live behavior change. Every item is additive/opt-in; no default answer or retrieval behavior changes; no re-index; nothing depends on the parked metadata backfill.

  • P3 — measurement (additive, opt-in). New scoreAnswerTargeting: a structural per-intent targeting check (dose→figure/regimen, red-result→withhold action + threshold, monitoring→schedule, contraindication→avoid, referral→criteria, doc-lookup→named/cited document) — stronger than the loose keyword cue, informational (never a hard gate), n/a for unsupported/fail-closed cases. New standalone scripts/eval-answer-quality.ts (npm run eval:answer-quality) finally runs the previously-dormant 30-case answerQualityEvalCases fixture and reports the 5 quality metrics + per-intent targeting. Separate/opt-in so it adds no spend to any existing eval. The existing scoreAnswerQualityEvalCase 5-metric contract and its test are untouched.
  • P2 — process gate (docs only). PR template now requires eval:retrieval:quality (23/23) for retrieval/ranking/selection/chunking PRs and eval:rag + eval:quality for answer-generation PRs (CI can't run them — needs live keys). process-hardening.md documents the rule and the measured PR-118 regression (governance-metadata weighting buried correct docs, recall 1.0→0.76) + the relevance-first standing constraint.
  • P4c — provenance (additive display). QuoteCard.isTruncated set by bestQuoteFromContent when a quote is cut at the 340-char cap; the quote card shows a "truncated — open the source" note so a value past the cut is never read as the complete passage.

First run of the new tool already earned its keep: it surfaced that during a provider-slowness episode a chunk of answers correctly degraded to the labeled source-only timeout pointer (relevance 0.53, red-result targeting 0/3) — a transient OpenAI-latency signal, not a code regression (golden retrieval 24/24, verify:cheap green). A clean baseline should be re-run when the provider is healthy.

Verification

  • npm run verify:cheap (986 passed / lint + typecheck clean)
  • npm run format:check (changed files prettier-clean)
  • npm run eval:retrieval:quality24/24, document_recall@5 1.0, content_recall@5 1.0, top_k 1.0, mrr@10 0.75 (ran because this touches evidence.ts; no retrieval regression)
  • npm run eval:rag / eval:quality — n/a: no answer-generation or post-processing logic changed (quote-truncation is display-only; eval additions don't affect the answer path)
  • npm run check:production-readiness — n/a: no clinical workflow / env / Supabase / source-governance behavior changed

Clinical Governance Preflight

  • Source-backed claims still require linked source verification — unchanged; adds a truncation disclosure so a cut quote is never read as complete
  • No patient-identifiable workflow introduced or expanded
  • Supabase target unchanged (Clinical KB Database)
  • Service-role/private access unchanged
  • Demo/synthetic separation unchanged
  • Source metadata / review-status / outdated behavior unchanged (this PR reinforces the relevance-first, unknown≠bad constraint in the process docs)
  • No clinical decision-support behavior changed → no TGA SaMD impact

Notes

  • New opt-in eval:answer-quality uses the same live-key + owner-scope bootstrap as eval:rag; it does not run in CI or in any existing gate.

🤖 Generated with Claude Code

…uote truncation flag

Stage 1 of the safe-execution plan — measurement and guardrails, zero live behavior change.

P3 (measurement, additive, opt-in):
- scoreAnswerTargeting: structural per-intent targeting check (dose→figure/regimen,
  red-result→withhold action, monitoring→schedule, contraindication→avoid, referral→criteria,
  doc-lookup→named/cited document) — stronger than the loose keyword cue; informational, never a
  hard gate; unsupported/fail-closed cases are n/a. Existing scoreAnswerQualityEvalCase and its
  5-metric contract untouched.
- New standalone scripts/eval-answer-quality.ts (npm run eval:answer-quality) runs the previously
  dormant 30-case answerQualityEvalCases fixture and reports the 5 metrics + per-intent targeting.
  Separate/opt-in so it adds no spend to any existing eval run.

P2 (process gate, docs only):
- PR template: require eval:retrieval:quality (23/23) for retrieval/ranking/selection/chunking PRs
  and eval:rag + eval:quality for answer-generation PRs (CI can't run them — needs live keys).
- process-hardening.md: document the gate + the measured PR-118 regression (governance metadata
  weighting buried correct docs 1.0->0.76) and the relevance-first standing constraint.

P4c (provenance, additive display):
- QuoteCard.isTruncated (optional) set by bestQuoteFromContent when a quote is cut at 340 chars;
  quote card shows a "truncated — open the source" note so a value past the cut is never read as
  the complete passage.

verify:cheap green (984 passed); format:check clean. New unit tests for scoreAnswerTargeting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0fb1fe4f11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/rag-eval-cases.ts Outdated
Comment thread src/lib/rag-eval-cases.ts Outdated
Comment thread src/lib/rag-eval-cases.ts Outdated
Comment thread src/lib/rag-eval-cases.ts Outdated
Comment thread src/components/clinical-dashboard/evidence-panels.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b364def046

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/rag-eval-cases.ts Outdated
@BigSimmo
BigSimmo enabled auto-merge (squash) July 3, 2026 12:05
@BigSimmo
BigSimmo merged commit de6d211 into main Jul 3, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55b30e7f70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/rag-eval-cases.ts
Comment on lines +171 to +173
const hasMonitoringRange =
/\b\d+(?:\.\d+)?\s*(?:-|to|–)\s*\d+(?:\.\d+)?\s*(?:mmol\/l|mmol|mg\/l|microgram\/l|mcg\/l|ng\/ml)\b/i.test(text) ||
/\b(?:mmol\/l|mmol|mg\/l|microgram\/l|mcg\/l|ng\/ml)\b/i.test(text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require a numeric monitoring range

For monitoring-range questions such as quality-lithium-monitoring-range, this bare-unit fallback makes hasMonitoringRange true for an answer that only says something like “lithium levels are reported in mmol/L” without giving the requested 0.4–0.8-style range. That lets generic answers pass the new targeting metric and masks the non-specific failures the metric is intended to surface; require a numeric level/range rather than a unit token alone.

Useful? React with 👍 / 👎.

Comment thread src/lib/rag-eval-cases.ts
? { score: 1, applicable: true, reason: "carries a dose figure/regimen" }
: { score: 0, applicable: true, reason: "no dose figure/regimen" };
case "red_result_action":
return hasRedAction && (!asksForThreshold || hasNumber)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require actual threshold values for red-result targeting

For threshold questions, this accepts any digit anywhere in the answer as the threshold signal, so an answer like “Step 1: withhold clozapine and arrange review” passes targeting for an ANC-threshold case without giving an ANC/WBC/count value. That hides the same generic/non-specific red-result failures this metric is supposed to expose; require a real threshold pattern instead of hasNumber.

Useful? React with 👍 / 👎.

- [ ] `npm run verify:ui` when UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed
- [ ] `npm run verify:release` before release or handoff confidence claims
- [ ] `npm run format:check`
- [ ] **`npm run eval:retrieval:quality` (must stay 23/23) when retrieval, ranking, selection, chunking, or scoring behavior changed** — CI cannot run it (needs live keys), so run it locally and paste the summary. A metadata/governance-weighting change once buried correct docs (recall 1.0→0.76) and only this eval caught it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the retrieval gate to require all 24 cases

The checked-in scripts/fixtures/rag-retrieval-golden.json currently has 24 cases, and this PR's verification summary reports eval:retrieval:quality as 24/24, but the new PR checkbox tells reviewers the gate only needs to stay 23/23. For retrieval/ranking PRs this can make a one-case regression look acceptable; require zero failures or the current 24/24 count instead of the stale 23/23 number.

Useful? React with 👍 / 👎.

Comment thread src/lib/rag-eval-cases.ts
Comment on lines +98 to +101
alternative
.split(/\s+/)
.filter((token) => token.length > 2)
.every((token) => text.includes(token)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require explicit document names for lookup targeting

For document-lookup cases where an alias is also the clinical topic, this fallback marks generic topic repetition as naming the expected document. For example CG.MHSP.PtSafetyPlan.pdf has the alias “patient safety plan”, so an answer that only says “the patient safety plan should be documented” passes without naming or citing the source document; require an expected citation or an explicit document/form/guideline title cue instead of token presence anywhere in the answer.

Useful? React with 👍 / 👎.

Comment on lines +49 to +50
if (token === "--limit") args.limit = Number.parseInt(value, 10);
if (token === "--targeting-floor") args.targetingFloor = Number.parseFloat(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate numeric eval CLI options

When --limit or --targeting-floor is omitted, typoed, or provided in an unsupported form like --limit=5, these assignments store NaN. The limit is then ignored by the truthy check below, so the script runs all 30 live answer calls, and a NaN floor silently disables the requested gate; reject missing/non-positive numeric values before calling the provider.

Useful? React with 👍 / 👎.

@BigSimmo
BigSimmo deleted the claude/rag-measurement-guardrails branch July 5, 2026 11:43
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