feat(insights): enrich the mood page with calendar, distribution, weekday, and correlation views

Lift the mood aggregates the AI status generator already computes —
tag frequency + per-tag average, in-target share, and the
mood × metric Pearson correlations — into a shared
`mood-aggregates.ts` so the prose and the new charts read off one
implementation. `mood-status.ts` now consumes the shared helpers
instead of its own inline copies.

Add a cached `/api/mood/insights` endpoint that returns the
pre-computed shapes (summary, heatmap, distribution, weekday, tags,
correlations) behind a 60-second server cache and a bounded 365-day
read — a cheap read-only path, never a synchronous model call. The
bucket invalidates alongside the mood-analytics cache on a mood write.

Render the shapes on the mood Insights page:

- a calendar heatmap forked from the compliance heatmap, coloured by
  the same mood-score bands as the line chart, with an adaptive
  window (30 days until ~90 days of history, then 90, then a full
  year past 180 days) so the grid is never mostly empty;
- a mood distribution bar chart (share of days per level);
- average mood by weekday with a best/toughest-day callout;
- a tag / trigger breakdown (frequency bars + per-tag average);
- mood vs. sleep / activity / pulse correlation cards reusing the
  existing scatter chart.

Add the supporting i18n keys across all six locales.
This commit is contained in:
Marc-André Bombeck committed 2026-06-01 19:54:26 +02:00
1 parent 1fcc5ba96b
commit 84e592b17e
20 files changed
+2003 -96

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
import { apiSuccess } from "@/lib/api-response";
import { apiHandler, requireAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { cached, caches, type ServerCache } from "@/lib/cache/server-cache";
import {
fetchMoodAggregates,
type MoodAggregates,
} from "@/lib/insights/mood-aggregates";
export const dynamic = "force-dynamic";
/**
* v1.8.5 — pre-computed mood-insights aggregates for the Mood Insights
* page (heatmap, distribution, weekday pattern, tag breakdown,
* cross-metric correlations, summary headline).
*
* The page stays a client component for the interactive line chart but
* reads the heavy aggregates from here: the compute runs server-side
* once per 60-s cache window, the browser only ever receives pre-shaped
* data (no raw mood rows). This follows the v1.8.3 anti-freeze posture
* — a cheap cached read, never a synchronous LLM call.
*/
export const GET = apiHandler(async () => {
const { user } = await requireAuth();
const result = await cached(
caches.moodInsights as ServerCache<MoodAggregates>,
user.id,
() => fetchMoodAggregates(user.id),
annotate,
);
annotate({
action: { name: "mood.insights.read" },
meta: {
total_entries: result.summary.totalEntries,
heatmap_window_days: result.heatmap.windowDays,
tag_count: result.tags.length,
},
});
return apiSuccess(result);
});