Public Access
feat(backend): persist plan scores on MealPlanItem
- Migration 0012 adds score (float) and components (jsonb) to meal_plan_item
- generate.py: populates score and components at create time
- schemas/MealPlanItemResponse: include score + components fields
- GET /api/meal-plans/{id}: returns persisted values instead of zeros
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
"""Persist score and components on MealPlanItem."""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
revision = "0012"
|
||||||
|
down_revision = "0011"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.add_column("meal_plan_item", sa.Column("score", sa.Float, nullable=True))
|
||||||
|
op.add_column(
|
||||||
|
"meal_plan_item",
|
||||||
|
sa.Column("components", sa.dialects.postgresql.JSONB, nullable=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_column("meal_plan_item", "components")
|
||||||
|
op.drop_column("meal_plan_item", "score")
|
||||||
@@ -125,8 +125,7 @@ def get_plan(plan_id: UUID, db: Session = Depends(get_db)) -> GenerationResponse
|
|||||||
.order_by(MealPlanItem.day_of_week)
|
.order_by(MealPlanItem.day_of_week)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
# Read-after-create: scores not stored on MealPlanItem, so we return zeros
|
# Read-after-create: scores now stored on MealPlanItem
|
||||||
# for score/components. The original generate response is authoritative.
|
|
||||||
return GenerationResponse(
|
return GenerationResponse(
|
||||||
meal_plan_id=plan.id,
|
meal_plan_id=plan.id,
|
||||||
week_start_date=plan.week_start_date,
|
week_start_date=plan.week_start_date,
|
||||||
@@ -135,8 +134,8 @@ def get_plan(plan_id: UUID, db: Session = Depends(get_db)) -> GenerationResponse
|
|||||||
recipe_id=it.recipe_id,
|
recipe_id=it.recipe_id,
|
||||||
day_of_week=it.day_of_week,
|
day_of_week=it.day_of_week,
|
||||||
estimated_cost=it.estimated_cost or 0,
|
estimated_cost=it.estimated_cost or 0,
|
||||||
score=0.0,
|
score=it.score or 0.0,
|
||||||
components={},
|
components={k: float(v) for k, v in (it.components.items() if it.components else [])},
|
||||||
)
|
)
|
||||||
for it in items
|
for it in items
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Column, String, Integer, Numeric, Text, Boolean, Date, DateTime,
|
Column, String, Integer, Numeric, Float, Text, Boolean, Date, DateTime,
|
||||||
ForeignKey, CheckConstraint, UniqueConstraint, Enum as SQLEnum
|
ForeignKey, CheckConstraint, UniqueConstraint, Enum as SQLEnum
|
||||||
)
|
)
|
||||||
from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY
|
from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY
|
||||||
@@ -228,6 +228,8 @@ class MealPlanItem(Base):
|
|||||||
denial_reason = Column(SQLEnum(DenialReason, name="denial_reason_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]))
|
denial_reason = Column(SQLEnum(DenialReason, name="denial_reason_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]))
|
||||||
denial_details = Column(Text)
|
denial_details = Column(Text)
|
||||||
estimated_cost = Column(Numeric(10, 2))
|
estimated_cost = Column(Numeric(10, 2))
|
||||||
|
score = Column(Float)
|
||||||
|
components = Column(JSONB)
|
||||||
used_pantry_items = Column(ARRAY(UUID(as_uuid=True)))
|
used_pantry_items = Column(ARRAY(UUID(as_uuid=True)))
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||||
|
|||||||
@@ -188,6 +188,8 @@ class MealPlanItemResponse(MealPlanItemBase):
|
|||||||
denial_reason: Optional[DenialReason] = None
|
denial_reason: Optional[DenialReason] = None
|
||||||
denial_details: Optional[str] = None
|
denial_details: Optional[str] = None
|
||||||
used_pantry_items: Optional[List[UUID]] = []
|
used_pantry_items: Optional[List[UUID]] = []
|
||||||
|
score: Optional[float] = None
|
||||||
|
components: Optional[Dict[str, float]] = None
|
||||||
created_at: Optional[datetime] = None
|
created_at: Optional[datetime] = None
|
||||||
updated_at: Optional[datetime] = None
|
updated_at: Optional[datetime] = None
|
||||||
recipe: Optional[RecipeResponse] = None
|
recipe: Optional[RecipeResponse] = None
|
||||||
|
|||||||
@@ -196,6 +196,8 @@ def generate_meal_plan(
|
|||||||
meal_type=meal_type,
|
meal_type=meal_type,
|
||||||
approval_status=MealPlanItemStatus.pending,
|
approval_status=MealPlanItemStatus.pending,
|
||||||
estimated_cost=scored_recipe.cost.total_cost,
|
estimated_cost=scored_recipe.cost.total_cost,
|
||||||
|
score=scored_recipe.score,
|
||||||
|
components=scored_recipe.components,
|
||||||
)
|
)
|
||||||
db.add(item)
|
db.add(item)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user