138 lines
3.9 KiB
TypeScript
Executable File
138 lines
3.9 KiB
TypeScript
Executable File
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),
|
|
};
|
|
}
|