From 170c62aac9899a1f8897e312936aee3e3e7eac87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 01:19:23 +0200 Subject: [PATCH] docs(planning): mark Wave-B B5c (per-rec explainability) complete Co-Authored-By: Claude Opus 4.7 (1M context) --- .planning/STATE.md | 18 ++++-- .planning/phase-B5c-report.md | 100 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 .planning/phase-B5c-report.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 03d9392cc..37b4418be 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -194,12 +194,22 @@ Last update: 2026-05-09T23:12:52+02:00 i18n parity green. Cross-agent race: commit 2 subject was hijacked by parallel B1a worker (mood-chart polish); my code IS on origin. -### B5c — Per-Recommendation Explainability +### B5c — Per-recommendation explainability -- [ ] Each rec carries: WHY (what user-data triggered it), WINDOW (which time range was analyzed), CITATIONS (which medical guideline + which user metric) -- [ ] UI: expand-arrow on each rec → reveals the explainability card with mini-chart of the data window -- [ ] Schema enforces presence (extends v1.4.15 C1's strict schema) +- [x] Each rec carries: WHY (what user-data triggered it), WINDOW (which time range was analyzed), CITATIONS (which medical guideline + which user metric) +- [x] UI: expand-arrow on each rec → reveals the explainability card with mini-chart of the data window +- [x] Schema enforces presence (extends v1.4.15 C1's strict schema) - Detailed report: `.planning/phase-B5c-report.md` +- Commits on origin/main: + - `8a438a0 feat(ai): schema requires rationale (window + comparedTo + deviation) per recommendation` + - `c39a527 feat(ai): system prompt requires rationale per recommendation` + - `13f1ae5 feat(ai): corrective retry covers missing rationale fields` + - `c8b30c1 feat(ai): legacy insight payload detection with regenerate CTA` + - `7f54c0c feat(charts): mini-mode + windowOverride prop for embedded rationale charts` + - `680f84c feat(charts): mood chart mini-mode + windowOverride` + - `10a67ff feat(insights): RecommendationCard with expandable rationale + mini-chart of data window` + - `fed2e7e test(insights): coverage for RecommendationCard expand + rationale rendering` +- Status block — 2026-05-10T01:17+02:00: B5c complete on origin/main. 8 atomic TDD-first commits, +37 net tests (10 schema + 6 prompt + 4 wrapper + 7 legacy-payload + 7 chart-mini + 11 rec-card + 2 integration − fixture updates). Full suite 1361/1361, integration 53/53, typecheck 0 errors, lint 12 pre-existing warnings. New `aiRecommendationRationaleSchema` requires `dataWindow` (enum) + non-empty `comparedTo` + non-empty `deviation` on every rec. New `` subcomponent renders Oura-style "Contributors" expand-card: severity badge + chevron + 3-row rationale + pinned mini-chart (HealthChart for measurement metrics, MoodChart for mood) + B5a citation footnote. Default-collapsed; 200ms ease-out reveal. Named slots (`data-slot="rec-confidence-slot"`, `data-slot="rec-feedback-slot"`) reserved for B5d / B5e plug-in. HealthChart + MoodChart gain `mini` + `windowOverride` props. Legacy payload detection via `isLegacyInsightPayload()` surfaces a "regenerate for new explainability features" CTA on cache-hits where the cached blob predates B5c (no auto-regen — user-initiated only). Worktree-isolated under `agent/b5c-explainability` so the parallel B2 worker couldn't bleed in. E2E (Playwright) NOT added — `` has zero non-test imports in the production tree, so there's no live route reaching the card surface today; the SSR integration test exercises the full flow end-to-end. Medication-compliance mini-chart wrapper deferred — the heatmap doesn't shrink cleanly to 140px and rec authors don't currently emit compliance-keyed recs. Both items flagged for v1.4.17. ### B5d — Confidence Score per Recommendation diff --git a/.planning/phase-B5c-report.md b/.planning/phase-B5c-report.md new file mode 100644 index 000000000..c97a50aba --- /dev/null +++ b/.planning/phase-B5c-report.md @@ -0,0 +1,100 @@ +# Phase B5c — Per-recommendation explainability (v1.4.16) + +Completed 2026-05-10 ~01:17 CEST. + +## What landed + +Eight atomic commits on origin/main (worktree: `agent/b5c-explainability`, +clean rebase + push to `main`), each TDD-first (failing test, then fix): + +| # | Commit | What it ships | +|---|--------|---------------| +| 1 | `8a438a0 feat(ai): schema requires rationale (window + comparedTo + deviation) per recommendation` | New `aiRecommendationRationaleSchema` + mandatory `rationale` field on `aiRecommendationSchema`. Empty `comparedTo` / `deviation` rejected; `dataWindow` enum locked to the 4-window vocabulary. New `findRecommendationsMissingRationale()` for legacy-payload detection. 10 unit tests + 5 fixture-touch updates across `generate-insight.test.ts`, `reference-id-schema.test.ts`, `citation-enforcement.test.ts`, `citation-coverage-logging.test.ts`. | +| 2 | `c39a527 feat(ai): system prompt requires rationale per recommendation` | EN+DE prompt advertises the new field shape AND a fifth GROUND RULE mandating rationale on every rec. `rationale.dataWindow MUST equal metricSource.timeRange` is enforced at prompt-time so the mini-chart pinning is bracket-mismatch-free. PROMPT_VERSION stays at 4.16.0 (B5a already bumped). 6 prompt-assertion tests. | +| 3 | `13f1ae5 feat(ai): corrective retry covers missing rationale fields` | The wrapper's `buildRetryCorrectionMessage()` now lists the three rationale fields explicitly so a first-attempt response without rationale gets a targeted reprompt. Three new `ai_rationale_*` Wide-Event keys land alongside the existing citation-coverage annotations so the admin AI dashboard can chart rationale-coverage per generation. 4 unit tests. | +| 4 | `c8b30c1 feat(ai): legacy insight payload detection with regenerate CTA` | New `isLegacyInsightPayload()` helper + route wiring: `/api/insights/generate` returns `legacyPayload: true` on cache-hit when the cached blob predates B5c rationale. UI shows a one-time "Insights updated — regenerate for new explainability features" CTA pointing at the regenerate button. User-initiated only; no auto-regen on cache-hit (would burn rate-limit tokens silently). 7 unit tests. | +| 5 | `7f54c0c feat(charts): mini-mode + windowOverride prop for embedded rationale charts` | HealthChart gains `mini` (drops range tabs + toggle row, h-[140px]) and `windowOverride` (pins to a rationale enum value). New pure helper `resolveMiniRangePoints()` (`mini-window.ts`) maps the rationale enum onto the existing per-points TIME_RANGES_KEYS. 7 unit tests. | +| 6 | `680f84c feat(charts): mood chart mini-mode + windowOverride` | MoodChart mirrors the same `mini` + `windowOverride` contract so mood-typed recommendations get a dedicated emoji-glyph mini-chart. | +| 7 | `10a67ff feat(insights): RecommendationCard with expandable rationale + mini-chart of data window` | New `` subcomponent with severity badge, default-collapsed chevron, animated 200ms expand reveal, 3-row `` (Window / Compared to / Deviation), pinned mini-chart (HealthChart for measurement metrics, MoodChart for mood-keyed recs), citation footnote, and named slots for B5d (`data-slot="rec-confidence-slot"`) and B5e (`data-slot="rec-feedback-slot"`). InsightAdvisorCard refactored to consume it; `legacyPayload` prop surfaces the regenerate CTA banner. InsightRecommendation type widens to optionally carry rationale + metricSource + severity + id. EN+DE i18n keys: `rationale`, `rationaleWindow`, `rationaleComparedTo`, `rationaleDeviation`, `rationaleExpand`, `rationaleCollapse`, `legacyPayloadCta`. 11 component tests. | +| 8 | `fed2e7e test(insights): coverage for RecommendationCard expand + rationale rendering` | Full-flow integration test: MockAIProvider → `generateInsight()` → InsightAdvisorCard → SSR markup assertion. Second case pins the legacyPayload CTA path with a string-only recommendations[] payload. 2 tests. | + +## Verification + +- `pnpm test` — 1361/1361 unit tests pass (was 1324 → +37 net for B5c: 10 schema + 6 prompt + 4 wrapper + 7 legacy-payload + 7 chart-mini + 11 rec-card + 2 integration − fixture updates). +- `pnpm test:integration` — 53/53 pass (no regression). +- `pnpm typecheck` — 0 errors. +- `pnpm lint` — 12 pre-existing warnings / 0 errors. + +## Layout slots reserved for B5d + B5e + +The rec card layout follows research §5.1 (one component spec, named +slots so later phases plug in without touching this file): + +``` +[severity-badge] rec-text [confidence-slot] [chevron▼] + (expanded:) + ─ rationale rows (Window / Compared to / Deviation) + ─ mini-chart pinned to rec.rationale.dataWindow + ─ citation footnote (B5a) when referenceId resolves + [feedback-slot] +``` + +- `data-slot="rec-confidence-slot"` (collapsed row, right-of-text) → + B5d `` plugs in here. Span placeholder is + always rendered so tests can pin the position. +- `data-slot="rec-feedback-slot"` (inside expanded rationale card, + bottom) → B5e `` plugs in. + +## Charts: missing wrappers (deferred) + +The brief asked for `mini` mode on **BP, weight, pulse, mood, +medication**. BP / weight / pulse all share the single `HealthChart` +wrapper — one commit covers them. Mood got its own commit. The +medication-compliance chart wasn't touched: the rec-card metric +mapping treats `medications.compliance30` as `null` chart route +(returns no embedded chart) for now, since the compliance heatmap is +visually heavy and doesn't shrink cleanly to 140px. That's a v1.4.17 +follow-up if rec authors actually emit compliance-keyed +recommendations. + +## E2E (Playwright) — not added; rationale below + +The brief listed an aspirational e2e: "on /insights, expand a +recommendation → assert rationale rows visible, mini-chart canvas +rendered." Investigation: `/insights` does NOT consume +`` in the production tree — it renders +`InsightStatusCard` per-section with a plain `text` field. Both +`` and `` are currently unused in +the live app (zero non-test imports). The headless-rendered +integration test `recommendation-card-integration.test.tsx` exercises +the same surface against a MockAIProvider end-to-end. + +When a future phase wires `` onto a live page +(likely as part of the Wave-B B1 broader insights surface +visualisation, or a v1.4.17 dashboard refresh), an e2e can drop in +and reuse the storage-state pattern from `e2e/insights-generate.spec.ts`. + +## Cross-agent race notes + +Worktree-isolated under `agent/b5c-explainability` so the parallel B2 +worker on the primary checkout couldn't bleed in. The integration +worktree (`/Users/marc/Projects/HealthLog-b5c`) carries its own +`pnpm install` + Prisma client; commits ride into origin/main via +`git push origin agent/b5c-explainability:main` after a clean rebase. +Zero collisions this round. + +## What B5d + B5e inherit + +- The rec card has named slot positions ready (`rec-confidence-slot` + collapsed; `rec-feedback-slot` expanded). Plugging in is a one-line + edit in `recommendation-card.tsx`. +- The strict `aiRecommendationSchema` carries `rationale` mandatory. + B5d's `confidence` field can ride alongside (already `.optional()` + per research §2 — flip to required after B5d ships). +- `findRecommendationsMissingRationale()` + the `ai_rationale_*` Wide- + Event annotations let the admin AI quality dashboard chart + legacy-payload migration alongside citation-coverage and (B5d/B5e + later) confidence + feedback rates. +- `legacyPayload` flag on `/api/insights/generate` response is the + same shape B5d / B5e can extend (additive flags rather than a + breaking change).