feat(meals): enforce three lower-calorie dinners with media
CI / backend (pytest + alembic) (push) Canceled after 0s
CI / frontend (build) (push) Canceled after 0s

This commit is contained in:
2026-09-01 10:11:36 -07:00
parent 7838c49721
commit 2695ba0232
85 changed files with 8103 additions and 795 deletions
+83 -1
View File
@@ -1,6 +1,6 @@
from sqlalchemy import (
Column, String, Integer, Numeric, Float, Text, Boolean, Date, DateTime,
ForeignKey, CheckConstraint, UniqueConstraint, Enum as SQLEnum
ForeignKey, CheckConstraint, UniqueConstraint, Index, Enum as SQLEnum
)
from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY
from sqlalchemy.orm import relationship
@@ -85,6 +85,18 @@ class IngredientMatchSource(enum.Enum):
MANUAL = "manual"
class RecipeImportStatus(str, enum.Enum):
PENDING = "pending"
QUARANTINED = "quarantined"
APPROVED = "approved"
REJECTED = "rejected"
class RecipeCatalogStatus(str, enum.Enum):
ACTIVE = "active"
QUARANTINED = "quarantined"
class FamilyProfile(Base):
__tablename__ = "family_profile"
@@ -186,6 +198,19 @@ class Recipe(Base):
external_source = Column(String(50))
external_id = Column(String(100))
discovery_reason = Column(Text)
catalog_status = Column(
SQLEnum(
RecipeCatalogStatus,
name="recipe_catalog_status_enum",
create_type=False,
values_callable=lambda obj: [e.value for e in obj],
),
nullable=False,
default=RecipeCatalogStatus.ACTIVE,
server_default="active",
)
catalog_findings = Column(JSONB, nullable=False, default=list, server_default="[]")
catalog_reviewed_at = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
@@ -197,6 +222,63 @@ class Recipe(Base):
return (self.prep_time_minutes or 0) + (self.cook_time_minutes or 0)
class RecipeImportStaging(Base):
"""Auditable, globally scoped recipe import candidate awaiting review."""
__tablename__ = "recipe_import_staging"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
source = Column(String(100), nullable=False)
external_id = Column(String(255), nullable=False)
source_url = Column(Text, nullable=False)
source_revision = Column(String(255), nullable=False)
license_name = Column(String(255), nullable=False)
license_url = Column(Text, nullable=False)
attribution = Column(Text, nullable=False)
raw_payload = Column(JSONB, nullable=False)
payload_sha256 = Column(String(64), nullable=False)
normalized_recipe = Column(JSONB, nullable=False)
validation_report = Column(JSONB, nullable=False)
status = Column(
SQLEnum(
RecipeImportStatus,
name="recipe_import_status_enum",
create_type=False,
values_callable=lambda obj: [e.value for e in obj],
),
nullable=False,
default=RecipeImportStatus.PENDING,
server_default="pending",
)
rejection_reason = Column(Text)
recipe_id = Column(UUID(as_uuid=True), ForeignKey("recipe.id", ondelete="SET NULL"))
acquired_at = Column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
reviewed_at = Column(DateTime(timezone=True))
reviewed_by = Column(String(300))
__table_args__ = (
UniqueConstraint(
"source",
"external_id",
"source_revision",
name="uq_recipe_import_staging_source_revision",
),
CheckConstraint(
"payload_sha256 ~ '^[0-9a-f]{64}$'",
name="ck_recipe_import_staging_payload_sha256",
),
CheckConstraint(
"(reviewed_at IS NULL) = (reviewed_by IS NULL)",
name="ck_recipe_import_staging_review_metadata",
),
Index("ix_recipe_import_staging_status_acquired_at", "status", "acquired_at"),
)
recipe = relationship("Recipe")
class MealPlan(Base):
__tablename__ = "meal_plan"