export interface DaySnapshot { date: string; adventureSlots: number; adventureTouched: number; exerciseDone: boolean; workHours: number; workTarget: number; sleepLogged: boolean; classesDone: number; teachingDone: boolean; explorationsDone: number; prayerChecks: number; prayerTotal: number; litanyChecks: number; litanyTotal: number; pagesRead: number; readingGoalWeekly: number; hadSpiritualEver: boolean; } function decayWeight(daysAgo: number): number { return Math.exp(-daysAgo / 10); } export function computeConsistencyScore(days: DaySnapshot[]): number { if (days.length === 0) return 0; let weighted = 0; let totalWeight = 0; days.forEach((d, i) => { const w = decayWeight(i); totalWeight += w; if (d.adventureSlots === 0) { weighted += w * 50; return; } weighted += w * (d.adventureTouched / d.adventureSlots) * 100; }); return Math.round(weighted / totalWeight); } export function computeDisciplineScore(days: DaySnapshot[]): number { if (days.length === 0) return 0; let weighted = 0; let totalWeight = 0; days.forEach((d, i) => { const w = decayWeight(i); totalWeight += w; const exercise = d.exerciseDone ? 100 : 0; const sleep = d.sleepLogged ? 100 : 50; const work = d.workTarget > 0 ? Math.min(100, (d.workHours / d.workTarget) * 100) : 50; weighted += w * (exercise * 0.4 + sleep * 0.3 + work * 0.3); }); return Math.round(weighted / totalWeight); } export function computeLearningScore(days: DaySnapshot[]): number { if (days.length === 0) return 0; let weighted = 0; let totalWeight = 0; days.forEach((d, i) => { const w = decayWeight(i); totalWeight += w; let dayScore = 0; if (d.classesDone > 0) dayScore += 40; if (d.teachingDone) dayScore += 30; if (d.explorationsDone > 0) dayScore += 30; weighted += w * Math.min(100, dayScore); }); return Math.round(weighted / totalWeight); } export function computeSpiritualScore(days: DaySnapshot[]): number { const hadEver = days.some((d) => d.hadSpiritualEver); if (days.length === 0) return hadEver ? 20 : 0; let weighted = 0; let totalWeight = 0; days.forEach((d, i) => { const w = decayWeight(i); totalWeight += w; const total = d.prayerTotal + d.litanyTotal; const checked = d.prayerChecks + d.litanyChecks; const rate = total > 0 ? (checked / total) * 100 : 0; weighted += w * rate; }); const score = Math.round(weighted / totalWeight); return hadEver ? Math.max(20, score) : score; } export function computeHealthScore(days: DaySnapshot[]): number { if (days.length === 0) return 0; let weighted = 0; let totalWeight = 0; days.forEach((d, i) => { const w = decayWeight(i); totalWeight += w; const exercise = d.exerciseDone ? 100 : 0; const sleep = d.sleepLogged ? 100 : 40; weighted += w * (exercise * 0.6 + sleep * 0.4); }); return Math.round(weighted / totalWeight); } export function computeReadingScore( days: DaySnapshot[], weeklyGoal: number ): number { if (days.length === 0) return 0; const recentWeek = days.slice(0, 7); const pages = recentWeek.reduce((s, d) => s + d.pagesRead, 0); const goalScore = weeklyGoal > 0 ? Math.min(100, (pages / weeklyGoal) * 100) : 50; let streakBonus = 0; let streak = 0; for (const d of days) { if (d.pagesRead > 0) streak++; else break; } if (streak >= 7) streakBonus = 20; else if (streak >= 3) streakBonus = 10; return Math.min(100, Math.round(goalScore * 0.8 + streakBonus)); } export function computeAllScores( days: DaySnapshot[], weeklyReadingGoal: number ) { return { consistencyScore: computeConsistencyScore(days), disciplineScore: computeDisciplineScore(days), learningScore: computeLearningScore(days), spiritualScore: computeSpiritualScore(days), healthScore: computeHealthScore(days), readingScore: computeReadingScore(days, weeklyReadingGoal), }; }