From baa1ff250cd35b57150b318e652d4b7da9368224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 1 Jun 2026 21:13:03 +0200 Subject: [PATCH] refactor(insights): table-drive the mood correlations The five mood-metric correlations and the measurement query's type filter were two hand-kept copies of the same channel list. Introduce a CORRELATION_METRICS map and single-source both the correlations object and the fetch filter from it so they cannot drift. --- src/lib/insights/mood-aggregates.ts | 70 +++++++++++------------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/src/lib/insights/mood-aggregates.ts b/src/lib/insights/mood-aggregates.ts index 1a7f746d6..7f17e4886 100644 --- a/src/lib/insights/mood-aggregates.ts +++ b/src/lib/insights/mood-aggregates.ts @@ -478,6 +478,23 @@ export function computeMoodMetricCorrelation( // --- Orchestrated aggregate shape --- +/** + * v1.8.5 — the cross-metric channels the mood page correlates against, + * mapping the correlation key the UI reads to its measurement type. This + * is the single source for both the `correlations` object below and the + * `fetchMoodAggregates` measurement `in:[...]` filter, so the two can + * never drift out of sync. + */ +export const CORRELATION_METRICS = { + sleep: "SLEEP_DURATION", + steps: "ACTIVITY_STEPS", + pulse: "PULSE", + weight: "WEIGHT", + bloodPressureSystolic: "BLOOD_PRESSURE_SYS", +} as const; + +type CorrelationKey = keyof typeof CORRELATION_METRICS; + export interface MoodAggregates { summary: { points: number; @@ -503,13 +520,7 @@ export interface MoodAggregates { structuredTags: StructuredTagRow[]; /** v1.8.5 — recent notes timeline (qualitative-reflection feed). */ notesTimeline: NoteTimelineEntry[]; - correlations: { - sleep: MoodMetricCorrelation; - steps: MoodMetricCorrelation; - pulse: MoodMetricCorrelation; - weight: MoodMetricCorrelation; - bloodPressureSystolic: MoodMetricCorrelation; - }; + correlations: Record; } /** @@ -583,33 +594,12 @@ export function computeMoodAggregates(args: { tags: computeTagSummary(entries, now), structuredTags: computeStructuredTagSummary(entries, now), notesTimeline: computeNotesTimeline(entries), - correlations: { - sleep: computeMoodMetricCorrelation( - moodDaily, - metricDaily("SLEEP_DURATION"), - now, - ), - steps: computeMoodMetricCorrelation( - moodDaily, - metricDaily("ACTIVITY_STEPS"), - now, - ), - pulse: computeMoodMetricCorrelation( - moodDaily, - metricDaily("PULSE"), - now, - ), - weight: computeMoodMetricCorrelation( - moodDaily, - metricDaily("WEIGHT"), - now, - ), - bloodPressureSystolic: computeMoodMetricCorrelation( - moodDaily, - metricDaily("BLOOD_PRESSURE_SYS"), - now, - ), - }, + correlations: Object.fromEntries( + Object.entries(CORRELATION_METRICS).map(([key, type]) => [ + key, + computeMoodMetricCorrelation(moodDaily, metricDaily(type), now), + ]), + ) as MoodAggregates["correlations"], }; } @@ -678,15 +668,9 @@ export async function fetchMoodAggregates( userId, deletedAt: null, measuredAt: { gte: windowCutoff }, - type: { - in: [ - "WEIGHT", - "BLOOD_PRESSURE_SYS", - "PULSE", - "SLEEP_DURATION", - "ACTIVITY_STEPS", - ], - }, + // v1.8.5 — single-sourced from CORRELATION_METRICS so the fetched + // channels and the computed correlations never drift apart. + type: { in: Object.values(CORRELATION_METRICS) }, }, orderBy: { measuredAt: "desc" }, take: 5000,