Public Access
feat(backend): tunable planner weights via family profile config
- Add planner_config JSONB to family_profile model + migration - Add PlannerConfig.merge(overrides) + to_dict() for family-level override merging - generate_meal_plan merges family.planner_config into DEFAULT before filtering/scoring/selection - New endpoints on /api/profile: - GET /planner-config — returns merged effective config - PUT /planner-config — partial override validation + merge - DELETE /planner-config — reset to system defaults - Schemas: PlannerConfigOverride, PlannerConfigResponse, PlannerConfigUpdateRequest with weight-sum validation (0.999–1.001) - Export RecipeBase/Create/Read/Update from schemas/__init__ to resolve forward refs
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
|
||||
class PlannerConfigOverride(BaseModel):
|
||||
recency_weeks: Optional[int] = Field(default=None, ge=0)
|
||||
calorie_tolerance_pct: Optional[int] = Field(default=None, ge=0, le=100)
|
||||
max_total_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
max_meal_cost: Optional[float] = Field(default=None, ge=0)
|
||||
w_savings: Optional[float] = Field(default=None, ge=0, le=1)
|
||||
w_coverage: Optional[float] = Field(default=None, ge=0, le=1)
|
||||
w_pantry: Optional[float] = Field(default=None, ge=0, le=1)
|
||||
w_time: Optional[float] = Field(default=None, ge=0, le=1)
|
||||
w_recency: Optional[float] = Field(default=None, ge=0, le=1)
|
||||
time_ideal_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
time_full_minutes: Optional[int] = Field(default=None, ge=0)
|
||||
recency_full_weeks: Optional[int] = Field(default=None, ge=0)
|
||||
top_k: Optional[int] = Field(default=None, ge=1)
|
||||
set_size: Optional[int] = Field(default=None, ge=1)
|
||||
p_protein: Optional[float] = Field(default=None, ge=0)
|
||||
p_cuisine: Optional[float] = Field(default=None, ge=0)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _check_weights_sum(self):
|
||||
weights = [self.w_savings, self.w_coverage, self.w_pantry, self.w_time, self.w_recency]
|
||||
if all(w is not None for w in weights):
|
||||
total = sum(weights)
|
||||
if not 0.999 <= total <= 1.001:
|
||||
raise ValueError(f"weights must sum to 1.0, got {total:.4f}")
|
||||
return self
|
||||
|
||||
|
||||
class PlannerConfigResponse(BaseModel):
|
||||
recency_weeks: int
|
||||
calorie_tolerance_pct: int
|
||||
max_total_minutes: int
|
||||
max_meal_cost: float
|
||||
w_savings: float
|
||||
w_coverage: float
|
||||
w_pantry: float
|
||||
w_time: float
|
||||
w_recency: float
|
||||
time_ideal_minutes: int
|
||||
time_full_minutes: int
|
||||
recency_full_weeks: int
|
||||
top_k: int
|
||||
set_size: int
|
||||
p_protein: float
|
||||
p_cuisine: float
|
||||
source: str = "default"
|
||||
|
||||
|
||||
class PlannerConfigUpdateRequest(BaseModel):
|
||||
planner_config: PlannerConfigOverride
|
||||
Reference in New Issue
Block a user