"""Planner constants. Tune here without touching algorithm code.""" from __future__ import annotations from dataclasses import dataclass @dataclass(frozen=True) class PlannerConfig: # Hard constraints recency_weeks: int = 4 # constraint #3: no repeat within N weeks calorie_tolerance_pct: int = 20 # constraint #4: ±X% of family.calorie_target max_total_minutes: int = 45 # constraint #5: prep + cook max_meal_cost: float = 50.00 # constraint #6: dollars per serving (dimensionless proxy — unit conversion not yet implemented) # Scoring weights (must sum to 1.0) w_savings: float = 0.30 w_coverage: float = 0.25 w_pantry: float = 0.10 w_time: float = 0.15 w_recency: float = 0.20 # Time bonus boundaries time_ideal_minutes: int = 25 # full bonus at <= this time_full_minutes: int = 45 # zero bonus at this; matches max_total_minutes # Recency bonus boundary recency_full_weeks: int = 12 # full bonus when last cooked >= this many weeks ago # Set selection top_k: int = 10 # how many feasible recipes to enumerate over set_size: int = 3 # 3 dinners/week p_protein: float = 0.15 # diversity penalty per shared-protein pair p_cuisine: float = 0.10 # diversity penalty per shared-cuisine pair def validate(self) -> None: total = self.w_savings + self.w_coverage + self.w_pantry + self.w_time + self.w_recency if abs(total - 1.0) > 1e-6: raise ValueError(f"weights must sum to 1.0, got {total}") DEFAULT = PlannerConfig() DEFAULT.validate()