Public Access
feat: 21 meals per week (3/day) + approve/deny + generate single meal
Backend:
- Planner config: set_size=21 (was 3), top_k=30 (was 20)
- generate.py: distribute 21 recipes across 7 days × 3 meal types
- meals.py: add POST /items/{id}/approve, /items/{id}/deny
- meals.py: add POST /{plan_id}/generate-item for empty slots
Frontend:
- Dashboard: Approve/Deny buttons on pending meal cards
- Dashboard: Generate button in empty meal slots
- API client: approveItem, denyItem, generateItem methods
Build: TypeScript compiles clean, Python syntax verified.
This commit is contained in:
@@ -19,6 +19,9 @@ from app.services import approval as approval_service
|
||||
from uuid import UUID
|
||||
from typing import List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from uuid import UUID
|
||||
from typing import List, Optional
|
||||
import random
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -323,6 +326,77 @@ def swap_meal_item(item_id: UUID, new_recipe_id: UUID, db: Session = Depends(get
|
||||
return {"message": "Meal swapped", "item": item}
|
||||
|
||||
|
||||
@router.post("/items/{item_id}/approve")
|
||||
def approve_meal_item(item_id: UUID, db: Session = Depends(get_db)):
|
||||
"""Directly approve a meal plan item from the dashboard."""
|
||||
item = db.query(MealPlanItem).filter(MealPlanItem.id == item_id).first()
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Meal plan item not found")
|
||||
item.approval_status = MealPlanItemStatus.approved
|
||||
db.commit()
|
||||
return {"message": "Meal approved", "item": item}
|
||||
|
||||
|
||||
@router.post("/items/{item_id}/deny")
|
||||
def deny_meal_item(item_id: UUID, db: Session = Depends(get_db)):
|
||||
"""Directly deny a meal plan item from the dashboard."""
|
||||
item = db.query(MealPlanItem).filter(MealPlanItem.id == item_id).first()
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="Meal plan item not found")
|
||||
item.approval_status = MealPlanItemStatus.denied
|
||||
db.commit()
|
||||
return {"message": "Meal denied", "item": item}
|
||||
|
||||
|
||||
@router.post("/{meal_plan_id}/generate-item")
|
||||
def generate_single_item(
|
||||
meal_plan_id: UUID,
|
||||
day_of_week: int = Query(..., ge=1, le=7),
|
||||
meal_type: str = Query(...),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Generate a single meal for an empty slot."""
|
||||
plan = db.query(MealPlan).filter(MealPlan.id == meal_plan_id).first()
|
||||
if not plan:
|
||||
raise HTTPException(status_code=404, detail="Meal plan not found")
|
||||
|
||||
# Verify slot is empty
|
||||
existing = (
|
||||
db.query(MealPlanItem)
|
||||
.filter(
|
||||
MealPlanItem.meal_plan_id == meal_plan_id,
|
||||
MealPlanItem.day_of_week == day_of_week,
|
||||
MealPlanItem.meal_type == meal_type,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Slot already occupied")
|
||||
|
||||
# Get all recipes, score them, pick the best not already in this plan
|
||||
all_recipes = db.query(Recipe).all()
|
||||
used_ids = {i.recipe_id for i in plan.items if i.recipe_id is not None}
|
||||
|
||||
# Simple heuristic: pick a random un-used recipe (we can improve scoring later)
|
||||
available = [r for r in all_recipes if r.id not in used_ids]
|
||||
if not available:
|
||||
available = all_recipes # reuse if all recipes are in play
|
||||
|
||||
recipe = random.choice(available)
|
||||
|
||||
new_item = MealPlanItem(
|
||||
meal_plan_id=meal_plan_id,
|
||||
recipe_id=recipe.id,
|
||||
day_of_week=day_of_week,
|
||||
meal_type=MealType[meal_type.upper()],
|
||||
approval_status=MealPlanItemStatus.pending,
|
||||
)
|
||||
db.add(new_item)
|
||||
db.commit()
|
||||
db.refresh(new_item)
|
||||
return {"message": "Meal generated", "item": new_item}
|
||||
|
||||
|
||||
@router.put("/items/{item_id}/move")
|
||||
def move_meal_item(
|
||||
item_id: UUID,
|
||||
|
||||
Reference in New Issue
Block a user