mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +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
@@ -75,11 +75,15 @@ export const GET = apiHandler(async () => {
|
||||
const tz = profile?.timezone ?? "Europe/Berlin";
|
||||
const since = new Date(Date.now() - WINDOW_DAYS * MS_PER_DAY);
|
||||
|
||||
// Behaviour channels are measurement-backed except MOOD (mood entries).
|
||||
// MOOD is backed by mood entries, not measurements — it appears as both a
|
||||
// behaviour and (v1.11.5 F3) an outcome channel, so filter it out of the
|
||||
// measurement type list on either side.
|
||||
const behaviourTypes = DISCOVERY_BEHAVIOURS.filter(
|
||||
(k) => k !== "MOOD",
|
||||
) as MeasurementType[];
|
||||
const outcomeTypes = [...DISCOVERY_OUTCOMES] as MeasurementType[];
|
||||
const outcomeTypes = DISCOVERY_OUTCOMES.filter(
|
||||
(k) => k !== "MOOD",
|
||||
) as MeasurementType[];
|
||||
|
||||
const [measurements, moodEntries] = await Promise.all([
|
||||
prisma.measurement.findMany({
|
||||
@@ -108,23 +112,23 @@ export const GET = apiHandler(async () => {
|
||||
measurementsByType.set(m.type, list);
|
||||
}
|
||||
|
||||
// MOOD's daily-mean series is shared between its behaviour and outcome
|
||||
// roles (computed once).
|
||||
const moodDaily = toDailyMeans(
|
||||
moodEntries.map((e) => ({ value: e.score, at: e.moodLoggedAt })),
|
||||
tz,
|
||||
);
|
||||
|
||||
const series: NamedSeries[] = [];
|
||||
for (const key of DISCOVERY_BEHAVIOURS) {
|
||||
const points =
|
||||
key === "MOOD"
|
||||
? toDailyMeans(
|
||||
moodEntries.map((e) => ({ value: e.score, at: e.moodLoggedAt })),
|
||||
tz,
|
||||
)
|
||||
: toDailyMeans(measurementsByType.get(key) ?? [], tz);
|
||||
key === "MOOD" ? moodDaily : toDailyMeans(measurementsByType.get(key) ?? [], tz);
|
||||
series.push({ key, role: "behaviour", points });
|
||||
}
|
||||
for (const key of DISCOVERY_OUTCOMES) {
|
||||
series.push({
|
||||
key,
|
||||
role: "outcome",
|
||||
points: toDailyMeans(measurementsByType.get(key) ?? [], tz),
|
||||
});
|
||||
const points =
|
||||
key === "MOOD" ? moodDaily : toDailyMeans(measurementsByType.get(key) ?? [], tz);
|
||||
series.push({ key, role: "outcome", points });
|
||||
}
|
||||
|
||||
const result = discoverCorrelations(series);
|
||||
|
||||
Reference in new issue
Block a user