mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 12:22:03 +00:00
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:
1 parent
c83cb2311d
commit
bbd00ca848
2 files changed
+143
-76
No files matched your search
+14
-5
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user