Implement ST-203 budget logic and tests
This commit is contained in:
parent
2c9344a94f
commit
07afbc3213
11 changed files with 1057 additions and 9 deletions
60
lib/check-in/budget.test.ts
Normal file
60
lib/check-in/budget.test.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
BUDGET_FORMULA_VERSION,
|
||||
deriveBudgetSnapshot,
|
||||
deriveDailyBudget,
|
||||
deriveEnergyLevel,
|
||||
formatEnergyLevelLabel,
|
||||
} from "./budget";
|
||||
|
||||
describe("deriveEnergyLevel", () => {
|
||||
it("maps scores 1 and 2 to zeer_laag", () => {
|
||||
expect(deriveEnergyLevel(1)).toBe("zeer_laag");
|
||||
expect(deriveEnergyLevel(2)).toBe("zeer_laag");
|
||||
});
|
||||
|
||||
it("maps scores 3 and 4 to laag", () => {
|
||||
expect(deriveEnergyLevel(3)).toBe("laag");
|
||||
expect(deriveEnergyLevel(4)).toBe("laag");
|
||||
});
|
||||
|
||||
it("maps scores 5 and 6 to midden", () => {
|
||||
expect(deriveEnergyLevel(5)).toBe("midden");
|
||||
expect(deriveEnergyLevel(6)).toBe("midden");
|
||||
});
|
||||
|
||||
it("maps scores 7 and 8 to redelijk", () => {
|
||||
expect(deriveEnergyLevel(7)).toBe("redelijk");
|
||||
expect(deriveEnergyLevel(8)).toBe("redelijk");
|
||||
});
|
||||
|
||||
it("maps scores 9 and 10 to hoog", () => {
|
||||
expect(deriveEnergyLevel(9)).toBe("hoog");
|
||||
expect(deriveEnergyLevel(10)).toBe("hoog");
|
||||
});
|
||||
});
|
||||
|
||||
describe("deriveDailyBudget", () => {
|
||||
it("keeps v1 daily budget equal to the energy score", () => {
|
||||
expect(deriveDailyBudget(1)).toBe(1);
|
||||
expect(deriveDailyBudget(5)).toBe(5);
|
||||
expect(deriveDailyBudget(10)).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deriveBudgetSnapshot", () => {
|
||||
it("returns a stable snapshot with formula version", () => {
|
||||
expect(deriveBudgetSnapshot(6)).toEqual({
|
||||
energyLevel: "midden",
|
||||
dailyBudget: 6,
|
||||
budgetFormulaVersion: BUDGET_FORMULA_VERSION,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatEnergyLevelLabel", () => {
|
||||
it("returns readable Dutch labels", () => {
|
||||
expect(formatEnergyLevelLabel("zeer_laag")).toBe("Zeer laag");
|
||||
expect(formatEnergyLevelLabel("redelijk")).toBe("Redelijk");
|
||||
});
|
||||
});
|
||||
56
lib/check-in/budget.ts
Normal file
56
lib/check-in/budget.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import type { EnergyLevel } from "@/lib/check-in/types";
|
||||
|
||||
export const BUDGET_FORMULA_VERSION = 1;
|
||||
|
||||
export type BudgetSnapshot = {
|
||||
energyLevel: EnergyLevel;
|
||||
dailyBudget: number;
|
||||
budgetFormulaVersion: number;
|
||||
};
|
||||
|
||||
export function deriveEnergyLevel(energyScore: number): EnergyLevel {
|
||||
if (energyScore <= 2) {
|
||||
return "zeer_laag";
|
||||
}
|
||||
|
||||
if (energyScore <= 4) {
|
||||
return "laag";
|
||||
}
|
||||
|
||||
if (energyScore <= 6) {
|
||||
return "midden";
|
||||
}
|
||||
|
||||
if (energyScore <= 8) {
|
||||
return "redelijk";
|
||||
}
|
||||
|
||||
return "hoog";
|
||||
}
|
||||
|
||||
export function deriveDailyBudget(energyScore: number): number {
|
||||
return energyScore;
|
||||
}
|
||||
|
||||
export function deriveBudgetSnapshot(energyScore: number): BudgetSnapshot {
|
||||
return {
|
||||
energyLevel: deriveEnergyLevel(energyScore),
|
||||
dailyBudget: deriveDailyBudget(energyScore),
|
||||
budgetFormulaVersion: BUDGET_FORMULA_VERSION,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatEnergyLevelLabel(energyLevel: EnergyLevel): string {
|
||||
switch (energyLevel) {
|
||||
case "zeer_laag":
|
||||
return "Zeer laag";
|
||||
case "laag":
|
||||
return "Laag";
|
||||
case "midden":
|
||||
return "Midden";
|
||||
case "redelijk":
|
||||
return "Redelijk";
|
||||
case "hoog":
|
||||
return "Hoog";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { getAuthenticatedUser } from "@/lib/auth/session";
|
||||
import { deriveBudgetSnapshot } from "@/lib/check-in/budget";
|
||||
import { ensureProfileBundleForCurrentUser } from "@/lib/profile/service";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import type {
|
||||
EnergyLevel,
|
||||
MorningCheckInRecord,
|
||||
MorningCheckInStatus,
|
||||
MorningCheckInSubmission,
|
||||
|
|
@ -16,6 +18,9 @@ type MorningCheckInRow = {
|
|||
check_in_date: string;
|
||||
energy_score: number;
|
||||
sleep_quality: SleepQuality;
|
||||
energy_level: EnergyLevel;
|
||||
daily_budget: number;
|
||||
budget_formula_version: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
|
@ -25,10 +30,13 @@ type MorningCheckInInsert = {
|
|||
check_in_date: string;
|
||||
energy_score: number;
|
||||
sleep_quality: SleepQuality;
|
||||
energy_level: EnergyLevel;
|
||||
daily_budget: number;
|
||||
budget_formula_version: number;
|
||||
};
|
||||
|
||||
const MORNING_CHECK_IN_COLUMNS =
|
||||
"id, user_id, check_in_date, energy_score, sleep_quality, created_at, updated_at";
|
||||
"id, user_id, check_in_date, energy_score, sleep_quality, energy_level, daily_budget, budget_formula_version, created_at, updated_at";
|
||||
|
||||
function mapMorningCheckInRow(row: MorningCheckInRow): MorningCheckInRecord {
|
||||
return {
|
||||
|
|
@ -37,6 +45,9 @@ function mapMorningCheckInRow(row: MorningCheckInRow): MorningCheckInRecord {
|
|||
checkInDate: row.check_in_date,
|
||||
energyScore: row.energy_score,
|
||||
sleepQuality: row.sleep_quality,
|
||||
energyLevel: row.energy_level,
|
||||
dailyBudget: row.daily_budget,
|
||||
budgetFormulaVersion: row.budget_formula_version,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
|
|
@ -122,11 +133,15 @@ export async function upsertTodayCheckInForCurrentUser(
|
|||
}
|
||||
|
||||
const checkInDate = getLocalDateForTimezone(profileBundle.profile.timezone);
|
||||
const budgetSnapshot = deriveBudgetSnapshot(submission.energyScore);
|
||||
const payload: MorningCheckInInsert = {
|
||||
user_id: user.id,
|
||||
check_in_date: checkInDate,
|
||||
energy_score: submission.energyScore,
|
||||
sleep_quality: submission.sleepQuality,
|
||||
energy_level: budgetSnapshot.energyLevel,
|
||||
daily_budget: budgetSnapshot.dailyBudget,
|
||||
budget_formula_version: budgetSnapshot.budgetFormulaVersion,
|
||||
};
|
||||
|
||||
const supabase = await createClient();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export type SleepQuality = "goed" | "matig" | "slecht";
|
||||
export type EnergyLevel = "zeer_laag" | "laag" | "midden" | "redelijk" | "hoog";
|
||||
|
||||
export type MorningCheckInRecord = {
|
||||
id: string;
|
||||
|
|
@ -6,6 +7,9 @@ export type MorningCheckInRecord = {
|
|||
checkInDate: string;
|
||||
energyScore: number;
|
||||
sleepQuality: SleepQuality;
|
||||
energyLevel: EnergyLevel;
|
||||
dailyBudget: number;
|
||||
budgetFormulaVersion: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue