From 8ac27d843d32c82084cfb08ccd7a4d49f22569e8 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Tue, 5 May 2026 20:47:31 -0700 Subject: [PATCH] feat: add IngredientGroceryMatch model and Ingredient.aliases / Recipe.calories_per_serving --- backend/app/models/__init__.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 3e2d89a..ad17efb 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -79,6 +79,11 @@ class EmailStatus(enum.Enum): BOUNCED = "bounced" +class IngredientMatchSource(enum.Enum): + AUTO = "auto" + MANUAL = "manual" + + class FamilyProfile(Base): __tablename__ = "family_profile" @@ -139,11 +144,13 @@ class Ingredient(Base): typical_price = Column(Numeric(10, 2)) unit = Column(String(50)) season_months = Column(ARRAY(Integer)) + aliases = Column(ARRAY(Text), nullable=False, server_default="{}") created_at = Column(DateTime(timezone=True), server_default=func.now()) pantry_items = relationship("HomePantry", back_populates="ingredient") never_suggests = relationship("NeverSuggest", back_populates="ingredient") grocery_item_links = relationship("GroceryItem", back_populates="ingredient") + matches = relationship("IngredientGroceryMatch", back_populates="ingredient", cascade="all, delete-orphan") class Recipe(Base): @@ -163,6 +170,7 @@ class Recipe(Base): dietary_tags = Column(ARRAY(String(50))) protein_type = Column(String(50)) spice_level = Column(Integer) + calories_per_serving = Column(Integer) ingredients = Column(JSONB, nullable=False) instructions = Column(ARRAY(Text), nullable=False) source_url = Column(Text) @@ -379,3 +387,21 @@ class EmailLog(Base): meal_plan = relationship("MealPlan") meal_plan_item = relationship("MealPlanItem") + + +class IngredientGroceryMatch(Base): + __tablename__ = "ingredient_grocery_match" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + ingredient_id = Column(UUID(as_uuid=True), ForeignKey("ingredient.id", ondelete="CASCADE"), nullable=False) + grocery_item_id = Column(UUID(as_uuid=True), ForeignKey("grocery_item.id", ondelete="CASCADE"), nullable=False) + confidence = Column(Numeric(4, 3), nullable=False) + source = Column(SQLEnum(IngredientMatchSource, name="ingredient_match_source_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False) + updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) + + __table_args__ = ( + UniqueConstraint("ingredient_id", "grocery_item_id", name="uq_ingredient_grocery_match_pair"), + ) + + ingredient = relationship("Ingredient", back_populates="matches") + grocery_item = relationship("GroceryItem")