mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
feat(mood-insights): promote mood to a discovery outcome and surface it
Two changes that turn the descriptive relations surfaces into a statistically-gated discovery view on the mood page. Mood as an outcome: the FDR-controlled all-pairs correlation discovery engine treated mood only as a behaviour (a lag source). Promote it to an outcome channel too, so the scan also surfaces "behaviour today → next-day mood" relations (e.g. more daylight today → better mood tomorrow) alongside the existing "mood today → next-day outcome" direction. The self-pair is skipped and Benjamini-Hochberg already controls the larger pair family this opens up. The discovery route now feeds the shared mood daily-mean series into both roles. Surface the discovery slice: the engine was computed but never rendered. The mood Insights page now shows the mood-involving pairs that survive the false-discovery correction, each phrased observationally with its paired-day count, r, and BH q-value, plus the honest "X of N tested" footer. The surface self-fetches the correlation route and degrades to nothing when the operator has disabled it. Observational only, with the standing disclaimer.
This commit is contained in:
1 parent
f1edbd17d1
commit
4e6599d85f
6 files changed
+339
-13
No files matched your search
@@ -95,3 +95,48 @@ describe("discoverCorrelations", () => {
|
||||
expect(result.discovered).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ── F3 — MOOD as a discoverable OUTCOME channel ─────────────────────
|
||||
|
||||
import { DISCOVERY_OUTCOMES } from "../correlation-discovery";
|
||||
|
||||
describe("MOOD as outcome (F3)", () => {
|
||||
it("registers MOOD in the outcome matrix", () => {
|
||||
expect(DISCOVERY_OUTCOMES).toContain("MOOD");
|
||||
});
|
||||
|
||||
it("surfaces a behaviour → next-day MOOD relation", () => {
|
||||
// 30 contiguous days; daylight on day D drives mood on day D+1.
|
||||
const n = 30;
|
||||
const daylight = Array.from({ length: n }, (_, i) => 60 + (i % 10) * 5);
|
||||
// mood[D+1] tracks daylight[D] with light noise → strong lagged r.
|
||||
const mood = Array.from({ length: n }, (_, i) => {
|
||||
const driver = i === 0 ? 60 : 60 + ((i - 1) % 10) * 5;
|
||||
return 1 + (driver - 60) / 45 + (i % 2 === 0 ? 0.02 : -0.02);
|
||||
});
|
||||
const result = discoverCorrelations([
|
||||
{ key: "TIME_IN_DAYLIGHT", role: "behaviour", points: series(daylight) },
|
||||
{ key: "MOOD", role: "outcome", points: series(mood) },
|
||||
]);
|
||||
const pair = result.discovered.find(
|
||||
(p) => p.behaviour === "TIME_IN_DAYLIGHT" && p.outcome === "MOOD",
|
||||
);
|
||||
expect(pair).toBeDefined();
|
||||
expect(pair!.r).toBeGreaterThan(0.5);
|
||||
});
|
||||
|
||||
it("never tests the MOOD → MOOD self-pair", () => {
|
||||
const n = 30;
|
||||
const mood = Array.from({ length: n }, (_, i) => 3 + (i % 3));
|
||||
const result = discoverCorrelations([
|
||||
{ key: "MOOD", role: "behaviour", points: series(mood) },
|
||||
{ key: "MOOD", role: "outcome", points: series(mood) },
|
||||
]);
|
||||
expect(
|
||||
result.discovered.find(
|
||||
(p) => p.behaviour === "MOOD" && p.outcome === "MOOD",
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(result.pairsTested).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -249,6 +249,13 @@ export const DISCOVERY_OUTCOMES = [
|
||||
"HEART_RATE_VARIABILITY",
|
||||
"RESTING_HEART_RATE",
|
||||
"WEIGHT",
|
||||
// v1.11.5 (F3) — mood is now an OUTCOME channel too, not only a behaviour.
|
||||
// The discovery loop skips the MOOD→MOOD self-pair (`b.key === o.key`), so
|
||||
// promoting it lets the FDR scan surface "behaviour today → next-day mood"
|
||||
// relations (e.g. more daylight today → better mood tomorrow) in addition
|
||||
// to the existing "mood today → next-day outcome" direction. BH-FDR
|
||||
// already controls the larger pair family this opens up.
|
||||
"MOOD",
|
||||
// Wrist temperature is a credible future OUTCOME channel (near-daily, so
|
||||
// n ≥ 20 is reachable; "did a hard workout / late alcohol raise next-night
|
||||
// temperature?"), but it is deliberately NOT a channel yet: it is
|
||||
|
||||
Reference in new issue
Block a user