Skip to content

Commit 5070ee0

Browse files
committed
review: add weekly insights (avg mood/energy, trend, suggestions)
1 parent c322cc2 commit 5070ee0

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

app/review/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useMemo, useState } from 'react';
44
import { useAppStore } from '../../src/state/store';
5+
import { computeWeeklyInsights } from '../../src/engine/insights';
56

67
const LOCAL_USER_ID = 'local-user';
78

@@ -25,6 +26,7 @@ export default function ReviewPage() {
2526
};
2627

2728
const recent = useMemo(() => moods.filter((m) => m.userId === LOCAL_USER_ID).slice(-7).reverse(), [moods]);
29+
const insights = useMemo(() => computeWeeklyInsights(moods.filter((m) => m.userId === LOCAL_USER_ID)), [moods]);
2830

2931
return (
3032
<main>
@@ -67,6 +69,23 @@ export default function ReviewPage() {
6769
)}
6870
</div>
6971
</section>
72+
73+
<section style={{ marginTop: 24 }}>
74+
<h2>Insights</h2>
75+
<div style={{ display: 'grid', gap: 8 }}>
76+
{insights.length === 0 ? (
77+
<p style={{ color: 'var(--muted)' }}>No insights yet.</p>
78+
) : (
79+
insights.map((i, idx) => (
80+
<div key={idx} style={{ border: '1px solid rgba(255,255,255,0.12)', borderRadius: 8, padding: 8 }}>
81+
<strong style={{ textTransform: 'capitalize' }}>{i.metric.replace('_', ' ')}</strong>
82+
<span style={{ marginLeft: 8 }}>{i.value}</span>
83+
{i.note && <div style={{ color: 'var(--muted)' }}>{i.note}</div>}
84+
</div>
85+
))
86+
)}
87+
</div>
88+
</section>
7089
</main>
7190
);
7291
}

src/engine/insights.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { MoodLog } from '../models/mood';
2+
3+
export type WeeklyInsight = {
4+
metric: 'avg_mood' | 'avg_energy' | 'mood_trend' | 'suggestion';
5+
value: number;
6+
note?: string;
7+
};
8+
9+
function average(values: number[]): number {
10+
if (values.length === 0) return 0;
11+
return values.reduce((s, n) => s + n, 0) / values.length;
12+
}
13+
14+
export function computeWeeklyInsights(moods: MoodLog[], windowDays = 7): WeeklyInsight[] {
15+
if (!moods || moods.length === 0) return [];
16+
const byDate = [...moods].sort((a, b) => a.date.localeCompare(b.date));
17+
const recent = byDate.slice(-windowDays);
18+
const avgMood = average(recent.map((m) => m.mood));
19+
const avgEnergy = average(recent.map((m) => m.energy));
20+
const trend = (recent.at(-1)?.mood ?? 0) - (recent[0]?.mood ?? 0);
21+
22+
const insights: WeeklyInsight[] = [
23+
{ metric: 'avg_mood', value: Number(avgMood.toFixed(2)) },
24+
{ metric: 'avg_energy', value: Number(avgEnergy.toFixed(2)) },
25+
{ metric: 'mood_trend', value: Number(trend.toFixed(2)) }
26+
];
27+
28+
// Gentle suggestions
29+
if (avgMood <= 4) {
30+
insights.push({ metric: 'suggestion', value: 1, note: 'Consider reducing planned load and adding more recovery time this week.' });
31+
}
32+
if (trend <= -2) {
33+
insights.push({ metric: 'suggestion', value: 1, note: 'Mood trend is down. Try shorter deep blocks or fewer per day.' });
34+
}
35+
if (avgEnergy <= 4.5) {
36+
insights.push({ metric: 'suggestion', value: 1, note: 'Energy is low. Schedule movement breaks and lighter tasks after lunch.' });
37+
}
38+
if (insights.filter((i) => i.metric === 'suggestion').length === 0) {
39+
insights.push({ metric: 'suggestion', value: 1, note: 'You seem stable. Maintain buffers and keep protecting sleep and breaks.' });
40+
}
41+
return insights;
42+
}
43+
44+

0 commit comments

Comments
 (0)