import { BASE_DAILY_TARGETS, LEVEL1_NUTRITION_KEYS, LEVEL2_NUTRITION_KEYS, LEVEL3_NUTRITION_KEYS, NUTRIENT_META, USA_GOV_DGA_KEYS, } from '@/common/constants/nutrition'; import { NutrientKey, Nutrition, NutritionDisplayMode, NutritionSortMode } from '@/types/nutrition'; export type NutrientsByLevel = { level1: NutrientKey[]; level2: NutrientKey[]; level3: NutrientKey[]; }; export function getNutrientsByMode(mode: NutritionDisplayMode): NutrientsByLevel { if (mode === 'usa_gov_dga') { return { level1: USA_GOV_DGA_KEYS, level2: [], level3: [] }; } return { level1: LEVEL1_NUTRITION_KEYS, level2: LEVEL2_NUTRITION_KEYS, level3: LEVEL3_NUTRITION_KEYS, }; } export function sortNutrients( keys: NutrientKey[], sortMode: NutritionSortMode, consumed?: Nutrition, target?: Nutrition ): NutrientKey[] { if (sortMode === 'default') { return [...keys].sort((a, b) => NUTRIENT_META[a].importance - NUTRIENT_META[b].importance); } if (!consumed || !target) { return [...keys].sort((a, b) => NUTRIENT_META[a].importance - NUTRIENT_META[b].importance); } return [...keys].sort((a, b) => { const fillA = (consumed[a] ?? 0) / (target[a] || 1); const fillB = (consumed[b] ?? 0) / (target[b] || 1); return fillA - fillB; }); } export function sumNutrition(items: Nutrition[]): Nutrition { const result: Nutrition = {}; for (const item of items) { for (const key of Object.keys(item) as NutrientKey[]) { const val = item[key]; if (val != null) { result[key] = (result[key] ?? 0) + val; } } } return result; } export function emptyNutrition(): Nutrition { return {}; } export function getDailyTargets(date: Date): Nutrition { const dayOfYear = Math.floor( (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000 ); const seed = dayOfYear * 7 + date.getFullYear(); const result: Nutrition = {}; for (const [key, base] of Object.entries(BASE_DAILY_TARGETS)) { const hash = ((seed * 31 + key.charCodeAt(0)) * 17) % 100; const variation = 0.9 + (hash / 100) * 0.2; result[key as NutrientKey] = Math.round(base * variation * 100) / 100; } return result; } const SCORE_NUTRIENTS: { key: NutrientKey; weight: number }[] = [ { key: 'protein_g', weight: 12 }, { key: 'fiber_g', weight: 10 }, { key: 'omega_3_mg', weight: 10 }, { key: 'vitamin_d_mcg', weight: 8 }, { key: 'b12_mcg', weight: 7 }, { key: 'iron_mg', weight: 7 }, { key: 'calcium_mg', weight: 7 }, { key: 'magnesium_mg', weight: 7 }, { key: 'choline_mg', weight: 6 }, { key: 'dha_mg', weight: 6 }, { key: 'epa_mg', weight: 6 }, { key: 'potassium_mg', weight: 5 }, { key: 'zinc_mg', weight: 5 }, { key: 'c_mg', weight: 5 }, { key: 'a_mcg', weight: 5 }, { key: 'b9_mcg', weight: 5 }, { key: 'selenium_mcg', weight: 5 }, { key: 'iodine_mcg', weight: 5 }, { key: 'b6_mg', weight: 4 }, { key: 'e_mg', weight: 4 }, { key: 'k_mcg', weight: 4 }, { key: 'copper_mg', weight: 4 }, { key: 'manganese_mg', weight: 4 }, { key: 'b1_mg', weight: 3 }, { key: 'b2_mg', weight: 3 }, { key: 'b3_mg', weight: 3 }, { key: 'b5_mg', weight: 3 }, { key: 'biotin_mcg', weight: 3 }, { key: 'phosphorus_mg', weight: 3 }, { key: 'chromium_mcg', weight: 3 }, { key: 'molybdenum_mcg', weight: 3 }, { key: 'ala_mg', weight: 3 }, { key: 'soluble_fiber_g', weight: 3 }, { key: 'insoluble_fiber_g', weight: 3 }, { key: 'retinol_mcg', weight: 2 }, { key: 'dpa_mg', weight: 2 }, { key: 'omega_6_mg', weight: 2 }, { key: 'monounsaturated_fat_g', weight: 2 }, { key: 'polyunsaturated_fat_g', weight: 2 }, { key: 'tryptophan_mg', weight: 2 }, { key: 'tyrosine_mg', weight: 2 }, { key: 'valine_g', weight: 2 }, { key: 'glutamine_g', weight: 2 }, { key: 'lycopene_mcg', weight: 2 }, { key: 'lutein_zeaxanthin_mcg', weight: 2 }, { key: 'beta_carotene_mcg', weight: 2 }, { key: 'alpha_carotene_mcg', weight: 1 }, { key: 'cryptoxanthin_mcg', weight: 1 }, { key: 'anthocyanins_mg', weight: 2 }, { key: 'phytosterols_mg', weight: 1 }, { key: 'betaine_mg', weight: 1 }, { key: 'probiotics_cfu', weight: 2 }, { key: 'collagen_g', weight: 1 }, { key: 'coq10_mg', weight: 1 }, { key: 'curcumin_mg', weight: 1 }, { key: 'creatine_g', weight: 1 }, { key: 'resveratrol_mg', weight: 1 }, { key: 'astaxanthin_mcg', weight: 1 }, { key: 'water_ml', weight: 2 }, { key: 'caffeine_mg', weight: 1 }, { key: 'theobromine_mg', weight: 1 }, { key: 'taurine_mg', weight: 1 }, ]; const SCORE_FOOD_GROUPS: { key: NutrientKey; weight: number }[] = [ { key: 'usa_gov_dga_vegetables_cup_eq', weight: 8 }, { key: 'usa_gov_dga_fruits_cup_eq', weight: 5 }, { key: 'usa_gov_dga_protein_g', weight: 5 }, { key: 'usa_gov_dga_whole_grains_oz_eq', weight: 4 }, { key: 'usa_gov_dga_dairy_cup_eq', weight: 3 }, { key: 'usa_gov_dga_healthy_fats_tsp_eq', weight: 3 }, { key: 'usa_gov_dga_dark_green_veg_cup_eq', weight: 4 }, { key: 'usa_gov_dga_red_orange_veg_cup_eq', weight: 3 }, { key: 'usa_gov_dga_beans_peas_lentils_veg_cup_eq', weight: 3 }, { key: 'usa_gov_dga_starchy_veg_cup_eq', weight: 2 }, { key: 'usa_gov_dga_other_veg_cup_eq', weight: 2 }, ]; function fillRatio(consumed: number, target: number): number { if (target <= 0) return 0; const ratio = consumed / target; return Math.min(ratio, 1.5) / 1.5; } export function calculateNutritionScore( consumed: Nutrition, targets: Nutrition, uniqueIngredientCount: number ): number { const nutrientTotal = SCORE_NUTRIENTS.reduce((sum, n) => sum + n.weight, 0); const foodGroupTotal = SCORE_FOOD_GROUPS.reduce((sum, n) => sum + n.weight, 0); let nutrientScore = 0; for (const { key, weight } of SCORE_NUTRIENTS) { const c = consumed[key] ?? 0; const t = targets[key] ?? BASE_DAILY_TARGETS[key] ?? 1; nutrientScore += fillRatio(c, t) * weight; } const nutrientPct = nutrientScore / nutrientTotal; let foodGroupScore = 0; for (const { key, weight } of SCORE_FOOD_GROUPS) { const c = consumed[key] ?? 0; const t = targets[key] ?? BASE_DAILY_TARGETS[key] ?? 1; foodGroupScore += fillRatio(c, t) * weight; } const foodGroupPct = foodGroupScore / foodGroupTotal; const diversityBonus = Math.min(uniqueIngredientCount / 15, 1); const rawScore = nutrientPct * 45 + foodGroupPct * 45 + diversityBonus * 10; return Math.min(Math.max(rawScore, 0), 100); }