import { averageMoodScore, getMoodById, MOOD_CATALOG } from "../mood-catalog"; import { toDayKey } from "../day-key"; export type JournalEntryPoint = { id: string; recordedAt: string; moods: string[]; stress: number | null; pillsTaken: boolean | null; }; export function entryMoodScore(entry: Pick): number | null { return averageMoodScore(entry.moods); } export function computeDayStreak(entryDays: string[]): number { if (entryDays.length === 0) return 0; const days = new Set(entryDays); const todayKey = toDayKey(new Date()); let cursor = days.has(todayKey) ? new Date() : addDays(new Date(), -1); let streak = 0; while (days.has(toDayKey(cursor))) { streak += 1; cursor = addDays(cursor, -1); } return streak; } export function topMoodId(entries: Pick[]): string | null { const counts = new Map(); for (const entry of entries) { for (const moodId of entry.moods) { counts.set(moodId, (counts.get(moodId) ?? 0) + 1); } } let best: string | null = null; let bestCount = 0; for (const [id, count] of counts) { if (count > bestCount) { best = id; bestCount = count; } } return best; } export function averageStress(entries: Pick[]): number | null { const values = entries.map((e) => e.stress).filter((v): v is number => typeof v === "number"); if (values.length === 0) return null; return values.reduce((sum, v) => sum + v, 0) / values.length; } export function pillsCorrelation(entries: JournalEntryPoint[]) { const withPills = entries.filter((e) => e.pillsTaken === true); const withoutPills = entries.filter((e) => e.pillsTaken === false); const avgWith = averageEntryMoodScores(withPills); const avgWithout = averageEntryMoodScores(withoutPills); return { withPillsCount: withPills.length, withoutPillsCount: withoutPills.length, avgMoodWithPills: avgWith, avgMoodWithoutPills: avgWithout, }; } export function stressMoodCorrelation(entries: JournalEntryPoint[]) { const points = entries .map((entry) => { const moodScore = entryMoodScore(entry); if (moodScore === null || entry.stress === null) return null; return { stress: entry.stress, moodScore }; }) .filter((point): point is { stress: number; moodScore: number } => point !== null); if (points.length === 0) { return { points: [], avgStress: null, avgMood: null }; } const avgStress = points.reduce((sum, p) => sum + p.stress, 0) / points.length; const avgMood = points.reduce((sum, p) => sum + p.moodScore, 0) / points.length; return { points, avgStress, avgMood }; } export function weekOverWeekTrend( entries: JournalEntryPoint[], now = new Date(), ): { current: number | null; previous: number | null; delta: number | null } { const currentStart = startOfWeekMonday(now); const currentEnd = addDays(currentStart, 7); const previousStart = addDays(currentStart, -7); const currentScores = entries .filter((e) => inRange(new Date(e.recordedAt), currentStart, currentEnd)) .map(entryMoodScore) .filter((v): v is number => v !== null); const previousScores = entries .filter((e) => inRange(new Date(e.recordedAt), previousStart, currentStart)) .map(entryMoodScore) .filter((v): v is number => v !== null); const current = currentScores.length > 0 ? currentScores.reduce((sum, v) => sum + v, 0) / currentScores.length : null; const previous = previousScores.length > 0 ? previousScores.reduce((sum, v) => sum + v, 0) / previousScores.length : null; const delta = current !== null && previous !== null ? current - previous : null; return { current, previous, delta }; } export function moodCatalogForClient() { return MOOD_CATALOG.map(({ id, label, emoji, color }) => ({ id, label, emoji, color })); } export function moodLabel(id: string): string { return getMoodById(id)?.label ?? id; } function averageEntryMoodScores(entries: JournalEntryPoint[]): number | null { const scores = entries.map(entryMoodScore).filter((v): v is number => v !== null); if (scores.length === 0) return null; return scores.reduce((sum, v) => sum + v, 0) / scores.length; } function addDays(date: Date, delta: number): Date { const next = new Date(date); next.setDate(next.getDate() + delta); return next; } function startOfWeekMonday(date: Date): Date { const next = new Date(date); const day = next.getDay(); const diffToMonday = (day + 6) % 7; next.setHours(0, 0, 0, 0); next.setDate(next.getDate() - diffToMonday); return next; } function inRange(date: Date, start: Date, end: Date): boolean { return date >= start && date < end; }