docs(planning): mark Wave-B B3 complete + report

Tick B3 in `.planning/STATE.md` against the six commit drops on
`develop` (correlation maths, schema + prompt rule,
TrendAnnotation + TrendsRow, CorrelationCard + CorrelationRow,
analytics route + page wiring, this report). Replace the stale
v1.4.16 host-load report with the v1.4.20 narrative — what
shipped, synthetic-data spot checks for each hypothesis, and
flagged-uncertain items for maintainer review (confidence-band
thresholds, eta-squared mapping, interpretation localisation
strategy).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 16:54:53 +02:00
1 parent c83cb2311d
commit bbd00ca848
2 files changed
+143 -76

No files matched your search

+14 -5
View File
@@ -125,12 +125,21 @@ commit.
### B3 — Correlation discovery + Trends row with AI annotations (~3-5 days)
- [ ] 3 pre-defined hypotheses: BP×compliance, mood×pulse,
- [x] 3 pre-defined hypotheses: BP×compliance, mood×pulse,
weight×weekday
- [ ] New `src/lib/insights/correlations.ts`
- [ ] New `src/components/insights/{correlation-card,trends-row,
trend-annotation}.tsx`
- [ ] Confidence ≥ 0.95, n ≥ 14, conservative phrasing
- [x] New `src/lib/insights/correlations.ts` (Pearson + weekday-ANOVA;
n >= 14 + p < 0.05 surfacing gate)
- [x] New `src/components/insights/{correlation-card,correlation-row,
trends-row,trend-annotation}.tsx`
- [x] Confidence interval via Fisher z-transform; conservative
phrasing locked in by code-review convention
- [x] PROMPT_VERSION 4.20.0 → 4.20.1; new `trendAnnotations` block
on the AI response schema (nullable + optional for legacy
payloads)
- [x] `/api/analytics` now emits `correlations: {bpCompliance,
moodPulse, weightWeekday}` server-side
- [x] `<CorrelationRow>` + `<TrendsRow>` mounted on `/insights`
between Daily Briefing and the Advisor card
- Detailed report: `.planning/phase-B3-report.md`
### B4 — Weekly Report + Storyboard + Mobile passes (~3-4 days)
+129 -71
View File
@@ -1,89 +1,147 @@
# Phase B3 — Admin System-Status host-load chart (v1.4.16)
# Phase B3 — Correlation discovery + Trends row with AI annotations (v1.4.20)
Marc asked for a 2-hour host-load graph above /admin/system-status — CPU,
memory, disk-IO. Phase ran in Wave-B in parallel with B4/B5a/B7 and the
verification gate.
Three pre-defined hypothesis cards (BP × medication compliance, mood ×
resting pulse, weight × weekday) and an AI-annotated Trends row land
on `/insights` between the Daily Briefing and the Advisor card.
Six atomic commits on `origin/develop`.
## Approach
Picked Option B (in-process sampler) over Option A (Coolify Sentinel
scrape). Coolify exposes no clean read endpoint for per-server metrics
without HTML scraping or private RPC; an `os.loadavg()` + `os.totalmem()`
Scope discipline locked in early: v1.4.20 ships **3 pre-defined
hypotheses**, NOT general automated correlation discovery. Full
metric-pair scan with FDR control is v1.5/v1.6 work.
- /proc/diskstats sampler is self-contained, costs one row per minute,
and works on macOS dev as well as Linux production.
Quality bar non-negotiable per the strategic plan:
- `n >= 14` paired data points
- `p < 0.05`
Below the bar each card paints an `<EmptyState>` rather than a
near-zero scatter that would imply a pattern that isn't there.
## Deliverables
1. **`HostMetric` model + migration `0032_host_metric`** —
`prisma/schema.prisma`, indexed by `captured_at`. Disk fields nullable
so non-Linux hosts don't fail.
2. **Per-minute sampler** — `src/lib/jobs/host-metric-sampler.ts`. Reads
`os.loadavg()`, `os.totalmem() - os.freemem()`, and (Linux)
/proc/diskstats. Skips loop/ram/dm-/md/sr/fd/nbd/zram pseudo-devices.
Wired into `reminder-worker.ts` with `* * * * *` cron. 7-day retention
enforced inside the worker; env override `HOST_METRIC_RETENTION_DAYS`
capped at >= 1 day.
3. **`GET /api/admin/host-metrics?since=2h`** — `requireAdmin()`-gated.
Returns `{ samples, meta }`. Server-side BPS derivation across
consecutive samples handles counter resets (host reboot) by emitting
`null` instead of negative deltas. Six `since` presets: 30m / 1h / 2h
(default) / 6h / 12h / 24h.
4. **`<HostMetricsChart>` component** —
`src/components/admin/host-metrics-chart.tsx`. Recharts wrapper with
load (yellow, left axis) + memory % (cyan, right axis) + disk-IO %
(purple, right axis, hidden when no disk data). `next/dynamic` import
from the consumer keeps the Recharts bundle off the rest of the admin
panel. 60s `refetchInterval` matches sampler cadence.
5. **System-status section wiring** — `system-status-section.tsx` now
wraps the existing card in a `space-y-6` flex with the chart above.
6. **i18n** — 9 EN+DE keys under `admin.hostMetrics.*` (title, load1,
memUsedPercent, diskBusyPercent, diskReadBps, diskWriteBps,
last2hours, empty, loadError).
1. **`src/lib/insights/correlations.ts`** — pure runners (`pearson`,
`weekdayAnova`, `correlateBpCompliance`, `correlateMoodPulse`,
`correlateWeightWeekday`). 95 % CI on r via Fisher z-transform;
eta-squared as the effect-size statistic for the weekday case.
Two-sided p-values from inline Wilson-Hilferty (F) +
Abramowitz-Stegun (z) approximations — no new dependencies.
16 unit tests pin positive / negative / no-correlation / under-n /
non-significant / no-variance branches and the Monday-spike
weekday flag.
2. **`aiInsightResponseSchema` + prompt rule 9** — added a nullable +
optional `trendAnnotations: { bp?, weight?, mood? }` block (each
string ≤ 200 chars). PROMPT_VERSION 4.20.0 → 4.20.1. EN + DE
ground rule 9 explicitly bans causal language and caps the
sentence length. 13 schema + prompt-shape tests.
3. **`<TrendAnnotation>` + `<TrendsRow>`** — pure presentational. Row
is 1-column on `<md`, 3-up on `>=md`. Annotations show a
`Sparkles` icon + the AI sentence + an optional confidence chip
(low / moderate / high). Empty state hints at the gap without
breaking the row's visual rhythm. 15 component tests.
4. **`<CorrelationCard>` + `<CorrelationRow>`** — card has tone-coloured
left bar, scatter sparkline (deferred-bundled via the existing
`<ScatterCorrelationChart>` wrapper), confidence chip, source-chip
("based on N paired readings · last 30 days"), interpretation
prose, disabled "Try a 7-day experiment" CTA with `title="Coming
soon"`. Row mounts the three cards in 2-up grid on `>=md` and
single column on `<md`; the correlation-disclaimer footer
("Patterns are observational, not causal — talk to your doctor if
BP runs high.") sits below the row once. 13 component tests.
5. **`/api/analytics` + `/insights` page wiring** — server-side
computes the three hypotheses on the trailing 30-day window and
surfaces `correlations: {bpCompliance, moodPulse, weightWeekday}`
on the response. `<CorrelationRow>` mounts above `<TrendsRow>`
between Daily Briefing and the Advisor card. The advisor hook
lifts `trendAnnotations` from the cached AI payload through
`trendAnnotationsSchema.safeParse` so legacy 4.20.0 payloads
resolve to null and trigger the per-metric empty hint. 10
source-scan tests pin imports, JSX mounts, and the wide-event
annotation.
## Tests
- 8 unit tests for the sampler (env retention parsing, disk-stats null
- throw fallbacks, create + deleteMany call shape).
- 6 integration tests against testcontainer Postgres for the API
endpoint (auth gating, 2h default window, BPS derivation across a
counter reset, `?since=30m` preset, empty-DB happy path).
- 5 component tests for the chart helper + loading skeleton render.
- All 13 unit + 6 integration tests green; i18n parity test green.
- vitest unit suite: 1833 → 1907 (+74 net)
- integration suite: 67/67, no regression
- typecheck + lint clean (no new warnings introduced)
## Cross-agent collisions
## Synthetic-data spot checks
The verification-gate stash/restore loop in this marathon repeatedly
wiped my untracked files (chart component + tests) and hijacked my
`git commit` invocations so commits 4-6 ended up landing under another
agent's commit message (`8d9f864 docs(planning): mark Wave-B B5a …`).
Functionally everything is on origin/main; commit attribution is
muddled. Commits 1-3 (db, jobs, api) landed cleanly under my titles
(`5d1ece1`, `f1bd801`, `2877710`). Commit 3 (`2877710`) is missing the
Co-Authored-By trailer due to a one-line oversight in my heredoc.
- `correlateBpCompliance({...})` for the canonical 20-row strong-fit
fixture (compliance 50→97.5 %, systolic 150→120) →
`status: "ok"`, `statistic ≈ -1.0` (perfect linear), `pValue < 0.001`,
confidence band label `"high"`, n = 20.
- `correlateMoodPulse({...})` for the 20-row low-mood/high-pulse
fixture → `status: "ok"`, negative `statistic`, p well under 0.05.
- `correlateWeightWeekday({...})` for the 4-week Monday-spike fixture
(Monday ≈ 84 kg, every other day ≈ 80 kg) → `status: "ok"`,
`outlierIndex = 0`, eta² > 0.14 (Cohen "large"), interpretation
flags Monday with the conservative "a pattern worth watching"
phrasing.
Recommendation for v1.4.17: spawn each agent in its own git worktree
(`superpowers:using-git-worktrees`) to eliminate the shared-cwd
staging race entirely. The current marathon's verification gate keeps
collapsing concurrent untracked files into shared stashes, which makes
even simple `git add path/to/file && git commit` racy.
## Did NOT ship
## Files added / modified (final state on origin/main)
- **Real "Try a 7-day experiment" scheduling.** The CTA is intentionally
disabled in v1.4.20 with the Coming-soon tooltip; live scheduling is
a v1.5 surface.
- **Storyboard / 90-day BP timeline annotations.** Still B4 work.
- **Weekly Report `/insights/report/[week]` route.** Still B4 work.
- `prisma/schema.prisma`, `prisma/migrations/0032_host_metric/migration.sql`
- `src/lib/jobs/host-metric-sampler.ts` + `__tests__/host-metric-sampler.test.ts`
- `src/lib/jobs/reminder-worker.ts` (sampler queue registration)
- `src/app/api/admin/host-metrics/route.ts`
- `tests/integration/admin-host-metrics.test.ts`
- `src/components/admin/host-metrics-chart.tsx` + `__tests__/host-metrics-chart.test.tsx`
- `src/components/admin/system-status-section.tsx` (wiring)
- `messages/en.json`, `messages/de.json` (admin.hostMetrics.\*)
- `tests/integration/setup.ts` (truncate `host_metrics`)
## Decisions that may want maintainer review
## Did NOT touch (scope guards)
- **Confidence-band-from-CI mapping.** `width(CI) <= 0.4` → "high",
`<= 0.8` → "moderate", else "low". The thresholds are heuristic; if
the user-facing chip ends up too aggressive (e.g. "high" pops on
noisy small-n results) we can tighten to 0.3 / 0.6.
- **Eta-squared confidence band for weekday.** No closed-form CI on
eta² without a non-central F distribution we don't carry. Mapped
via Cohen conventions (`eta² >= 0.14` → "high", `>= 0.06` →
"moderate") with an n-driven nudge. If marathon QA flags this as
too lenient we can drop the nudge or surface "n-low" as its own
pseudo-band.
- **Interpretation phrasing.** Locked to a small set of conservative
templates inside the runners — never reaches the AI provider —
so the EN strings live in `correlations.ts` rather than i18n. If
Marc wants German-locale interpretations, we'd need to localise
these or move them through `t()`.
- **Trend-annotation surface gating.** The annotation block is
optional — if the model omits a metric, the empty hint surfaces
("Awaiting more data to annotate this trend"). For the legacy
(4.20.0) cached payload everyone sees the empty hint until they
click Regenerate. Opt: we could auto-regenerate once on first
page-mount post-deploy; opted not to so users keep their existing
cache TTL.
- **Inline ANOVA p-value approximation.** Wilson-Hilferty for the
F-distribution is good enough for the surfacing decision, but a
reviewer should sanity-check against a small-df edge case if Marc
is worried about precision near `p ≈ 0.05`.
- `src/lib/ai/`, `src/components/insights/`,
`src/components/charts/scatter-correlation-chart.tsx` (B5a/B5c-e)
- `src/components/settings/*` (B2/B6/B7)
- audit-log section (B4)
- No new dependencies — Recharts pattern reuse only.
## Files added / modified
- `src/lib/insights/correlations.ts` (+
`__tests__/correlations.test.ts`)
- `src/lib/ai/schema.ts` — `trendAnnotationsSchema` + integration
- `src/lib/ai/prompts/insight-generator.ts` — PROMPT_VERSION 4.20.1
+ GROUND RULE 9 (EN + DE) + JSON shape entry
- `src/lib/ai/__tests__/trend-annotations-schema.test.ts`
- `src/components/insights/trend-annotation.tsx` +
`__tests__/trend-annotation.test.tsx`
- `src/components/insights/trends-row.tsx` +
`__tests__/trends-row.test.tsx`
- `src/components/insights/correlation-card.tsx` +
`__tests__/correlation-card.test.tsx`
- `src/components/insights/correlation-row.tsx` +
`__tests__/correlation-row.test.tsx`
- `src/app/api/analytics/route.ts` — three runners + Berlin-day
bucketing helpers
- `src/app/insights/page.tsx` — mounts CorrelationRow + TrendsRow
- `src/components/insights/use-insights-advisor.ts` — lifts
trendAnnotations from the cached payload
- `src/app/__tests__/insights-b3-wiring.test.ts`
- `messages/en.json`, `messages/de.json` —
`insights.{trendsRow,trendAnnotation,correlationRow}` keys