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:
@@ -6,6 +6,8 @@ from app.schemas import (
|
||||
FamilyProfileResponse, FamilyProfileUpdate, FamilyProfileCreate,
|
||||
FamilyMemberResponse, FamilyMemberCreate
|
||||
)
|
||||
from app.schemas.planner_config import PlannerConfigOverride, PlannerConfigResponse, PlannerConfigUpdateRequest
|
||||
from app.services.planner.config import DEFAULT
|
||||
from app.security import require_session
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
@@ -78,4 +80,69 @@ def delete_member(member_id: UUID, db: Session = Depends(get_db)):
|
||||
|
||||
db.delete(member)
|
||||
db.commit()
|
||||
return {"message": "Member deleted"}
|
||||
return {"message": "Member deleted"}
|
||||
|
||||
|
||||
@router.get("/planner-config", response_model=PlannerConfigResponse)
|
||||
def get_planner_config(db: Session = Depends(get_db)):
|
||||
"""Return the effective planner configuration for the family (merged defaults + overrides)."""
|
||||
profile = db.query(FamilyProfile).first()
|
||||
if not profile:
|
||||
raise HTTPException(status_code=404, detail="Family profile not found")
|
||||
|
||||
effective = DEFAULT
|
||||
if profile.planner_config:
|
||||
try:
|
||||
effective = DEFAULT.merge(profile.planner_config)
|
||||
except ValueError:
|
||||
pass # fall back to default
|
||||
|
||||
data = effective.to_dict()
|
||||
data["source"] = "family_override" if profile.planner_config else "default"
|
||||
return data
|
||||
|
||||
|
||||
@router.put("/planner-config", response_model=PlannerConfigResponse, dependencies=[Depends(require_session)])
|
||||
def update_planner_config(payload: PlannerConfigUpdateRequest, db: Session = Depends(get_db)):
|
||||
"""Update the family planner_config. Partial overrides are merged into defaults."""
|
||||
profile = db.query(FamilyProfile).first()
|
||||
if not profile:
|
||||
raise HTTPException(status_code=404, detail="Family profile not found")
|
||||
|
||||
# Build a clean dict of overrides
|
||||
overrides = payload.planner_config.model_dump(exclude_unset=True, exclude_none=True)
|
||||
if not overrides:
|
||||
raise HTTPException(status_code=400, detail="No overrides provided")
|
||||
|
||||
# Merge with existing family config if present
|
||||
existing = dict(profile.planner_config) if profile.planner_config else {}
|
||||
merged = {**existing, **overrides}
|
||||
|
||||
# Validate via PlannerConfig
|
||||
try:
|
||||
DEFAULT.merge(merged)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid planner config: {exc}")
|
||||
|
||||
profile.planner_config = merged
|
||||
db.commit()
|
||||
db.refresh(profile)
|
||||
|
||||
effective = DEFAULT.merge(merged)
|
||||
data = effective.to_dict()
|
||||
data["source"] = "family_override"
|
||||
return data
|
||||
|
||||
|
||||
@router.delete("/planner-config", dependencies=[Depends(require_session)])
|
||||
def reset_planner_config(db: Session = Depends(get_db)):
|
||||
"""Clear family planner_config and restore system defaults."""
|
||||
profile = db.query(FamilyProfile).first()
|
||||
if not profile:
|
||||
raise HTTPException(status_code=404, detail="Family profile not found")
|
||||
|
||||
profile.planner_config = None
|
||||
db.commit()
|
||||
data = DEFAULT.to_dict()
|
||||
data["source"] = "default"
|
||||
return data
|
||||
|
||||
Reference in New Issue
Block a user