Files
Cibello-app/packages/nutrition-engine/src/energy.ts
T
2026-08-05 19:21:11 +07:00

108 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ActivityLevel, DailyTargets, GoalType, Sex } from "@app/shared-types";
/**
* Energiberäkning enligt MifflinSt Jeor. Detta är vägledning baserad på
* officiella rekommendationer Appen är inte medicinsk rådgivning (spec §6, §21).
*/
export interface EnergyProfile {
sex: Sex;
age: number;
heightCm: number;
weightKg: number;
activityLevel: ActivityLevel;
primaryGoal?: GoalType | undefined;
}
const ACTIVITY_FACTORS: Record<ActivityLevel, number> = {
sedentary: 1.2,
light: 1.375,
moderate: 1.55,
active: 1.725,
very_active: 1.9,
};
/** Justering per mål (kcal/dag). Konservativa, väldokumenterade nivåer. */
const GOAL_ADJUSTMENTS: Partial<Record<GoalType, number>> = {
lose_weight: -500,
gain_weight: 400,
build_muscle: 250,
maintain_weight: 0,
};
/** Lägsta rekommenderade energiintag under detta kapas aldrig målet (säkerhetsgolv). */
const MIN_KCAL = 1200;
export function bmrMifflinStJeor(
p: Pick<EnergyProfile, "sex" | "age" | "heightCm" | "weightKg">,
): number {
const base = 10 * p.weightKg + 6.25 * p.heightCm - 5 * p.age;
if (p.sex === "male") return base + 5;
if (p.sex === "female") return base - 161;
// Ospecificerat: medelvärde av formlerna, transparent redovisat i UI.
return base - 78;
}
export function tdee(p: EnergyProfile): number {
return bmrMifflinStJeor(p) * ACTIVITY_FACTORS[p.activityLevel];
}
export interface DailyTargetsResult {
targets: DailyTargets;
basis: {
bmrKcal: number;
tdeeKcal: number;
goalAdjustmentKcal: number;
activityLevel: ActivityLevel;
};
}
/**
* Dagsmål: energi via TDEE + måljustering; protein per kg kroppsvikt;
* fett som andel av energi; kolhydrater = resten; fiber 3 g/MJ (nordisk rekommendation);
* salt max 6 g/dag.
*/
export function computeDailyTargets(p: EnergyProfile): DailyTargetsResult {
const bmr = bmrMifflinStJeor(p);
const maintenance = tdee(p);
const adjustment = GOAL_ADJUSTMENTS[p.primaryGoal ?? "maintain_weight"] ?? 0;
const kcal = Math.max(MIN_KCAL, Math.round(maintenance + adjustment));
const proteinPerKg =
p.primaryGoal === "build_muscle" || p.primaryGoal === "more_protein"
? 1.8
: p.primaryGoal === "lose_weight"
? 1.6
: 1.2;
const proteinG = Math.round(p.weightKg * proteinPerKg);
const fatShare = p.primaryGoal === "less_fat" ? 0.25 : 0.3;
const fatG = Math.round((kcal * fatShare) / 9);
const carbsKcal = Math.max(0, kcal - proteinG * 4 - fatG * 9);
const carbsG = Math.round(carbsKcal / 4);
// 3 g fiber per MJ (1 MJ ≈ 239 kcal)
const fiberG = Math.round((kcal / 239) * 3);
return {
targets: { kcal, proteinG, carbsG, fatG, fiberG, saltMaxG: 6 },
basis: {
bmrKcal: Math.round(bmr),
tdeeKcal: Math.round(maintenance),
goalAdjustmentKcal: adjustment,
activityLevel: p.activityLevel,
},
};
}
/** Standardmål när profil saknas (visas tydligt som schablon i appen). */
export const DEFAULT_TARGETS: DailyTargets = {
kcal: 2000,
proteinG: 80,
carbsG: 220,
fatG: 67,
fiberG: 25,
saltMaxG: 6,
};