Skip to content

Technical Design TD-001 — Prepared Foods & Leftovers

Field Value
ID TD-001
Title Prepared Foods & Leftovers
Status Draft
Owner @souperk
Audience contributors
Created 2026-02-07
Last Updated 2026-02-07
Reviewers TBD
Approvers TBD
Tracking TBD
Related v0.2 Roadmap, TD-002

1. Summary

  • Cooking a recipe currently consumes inventory but creates no record of the resulting meal
  • Users cannot track prepared food portions, storage locations, or expiry
  • This TD introduces a PreparedFood aggregate with activity tracking for portion consumption and discard
  • Enables the "what to eat soon" recommendation flow in v0.7

2. Context and Problem Statement

  • After cooking a recipe, the system records inventory consumption but nothing about the output
  • Users have no visibility into what prepared food exists, where it's stored, or when it expires
  • Leftovers cannot be tracked; users must mentally track portion counts and freshness

2.1 Goals

  • Track prepared foods with portions, storage location, and expiry window
  • Record portion consumption and discard events with timestamps
  • Optionally create a PreparedFood when cooking a recipe
  • Provide an expiry cleanup view combining inventory batches and prepared foods

2.2 Out of Scope

  • Quick meal templates (see TD-002)
  • Nutrition data for prepared foods (v0.5)
  • Automated expiry notifications (future)

2.3 Assumptions and Constraints

  • Prepared foods are always created manually or via the cook-recipe flow
  • Expiry is user-provided or derived from a configurable default window
  • Storage locations reuse the existing StorageLocation enum

3. Design Overview

  • New PreparedFood aggregate root with PreparedFoodActivity children
  • PreparedFoodService managing lifecycle (create, consume portion, discard, list)
  • Cook-recipe flow gains an optional flag to create a PreparedFood from the recipe output
  • Unified expiry endpoint returns both inventory batches and prepared foods sorted by expiry

3.1 Key Components

Component Purpose
PreparedFood Aggregate tracking a cooked meal with portions and expiry
PreparedFoodActivity Child entity recording portion consumption and discard events
PreparedFoodStatus Enum: ACTIVE, FINISHED, DISCARDED
PreparedFoodActivityType Enum: CONSUMED, DISCARDED
PreparedFoodRepository Data access for prepared foods
PreparedFoodService Domain service with @transactional methods
PreparedFoodRouter FastAPI router at /prepared-food

3.2 Architecture

graph TB
    Router[PreparedFoodRouter] --> Service[PreparedFoodService]
    Service --> Repo[PreparedFoodRepository]
    Service --> InvService[InventoryService]
    Repo --> DB[(PostgreSQL)]
    InvService --> InvRepo[InventoryBatchRepository]

3.3 Interface Impact (Before → After)

Area Before After
Cook recipe Consumes inventory, returns activity logs Optionally also creates PreparedFood
Expiring-soon Returns only inventory batches Returns batches + prepared foods
Inventory cleanup Not available New endpoint filtering by location

4. Implementation Details

4.1 Core Contracts and APIs

PreparedFood entity:

class PreparedFood(BaseAggregate):
    name: Mapped[str]
    recipe_id: Mapped[int | None]  # optional link to recipe
    user_id: Mapped[str]
    portions_total: Mapped[int]
    portions_remaining: Mapped[int]
    storage_location: Mapped[StorageLocation]
    prepared_at: Mapped[datetime]
    expires_at: Mapped[datetime | None]
    status: Mapped[PreparedFoodStatus]
    activities: Mapped[list[PreparedFoodActivity]]

PreparedFoodActivity entity:

class PreparedFoodActivity(BaseEntity, AuditMixin):
    prepared_food_id: Mapped[int]
    activity_type: Mapped[PreparedFoodActivityType]
    portions: Mapped[int]
    notes: Mapped[str | None]

API endpoints:

Method Path Description
POST /prepared-food Create prepared food
GET /prepared-food List prepared foods (filter by status, location)
GET /prepared-food/{id} Get single prepared food
POST /prepared-food/{id}/consume Consume portions
POST /prepared-food/{id}/discard Discard (remaining portions)
DELETE /prepared-food/{id} Delete prepared food
GET /inventory/cleanup Unified expiry view (batches + prepared foods)

4.2 Component Architecture

  • PreparedFood extends BaseAggregate with standard column helpers
  • PreparedFoodActivity extends BaseEntity + AuditMixin
  • Parent-child via relationship(cascade="all, delete-orphan", lazy="selectin")
  • Repository extends BaseRepository

4.3 Integration Points

  • Cook recipe flow: InventoryService.cook_recipe gains optional create_prepared_food: bool parameter
  • Expiry cleanup: New query joining InventoryBatch and PreparedFood filtered by expiry window and location

4.4 Error Handling and Edge Cases

Error When Response
PreparedFoodNotFoundError ID doesn't exist 404
InsufficientPortionsError Consuming more portions than remaining 400
PreparedFoodNotActiveError Consuming from finished/discarded food 400

5. Semantics

  • When portions_remaining reaches 0, status transitions to FINISHED
  • Discard sets status to DISCARDED regardless of remaining portions
  • expires_at is informational; expired foods remain ACTIVE until explicitly consumed or discarded
  • A prepared food created from cooking a recipe stores the recipe_id for reference but is independent of the recipe

6. Examples

6.1 Create Prepared Food via Cook Recipe

# POST /recipe/{id}/cook with create_prepared_food=true
{
    "user_id": "user-123",
    "create_prepared_food": true,
    "prepared_food_name": "Chicken Stir Fry",
    "portions": 4,
    "storage_location": "FRIDGE",
    "expires_at": "2026-02-10T00:00:00Z"
}

6.2 Consume Portions

# POST /prepared-food/{id}/consume
{
    "portions": 1,
    "notes": "Lunch"
}

7. Test Matrix

  • Unit: PreparedFood entity logic (portion tracking, status transitions)
  • Integration: Full CRUD via API, cook→prepared food flow, expiry cleanup query
  • Edge cases: Consume more than remaining, consume from finished/discarded, discard with 0 remaining

8. Acceptance Criteria

  • PreparedFood CRUD endpoints functional
  • Cook recipe optionally creates PreparedFood
  • Portion consumption updates remaining count and transitions status
  • Discard transitions status regardless of remaining portions
  • Unified expiry cleanup endpoint returns both batches and prepared foods
  • All error cases return appropriate HTTP status codes
  • Migration v07 applies cleanly

9. Rollout and Migration

9.1 Phased Plan

Phase 1: PreparedFood aggregate, repository, service, router, migration v07

Phase 2: Integration with cook-recipe flow

Phase 3: Unified expiry cleanup endpoint

9.2 Migration Strategy

  • New tables only (prepared_foods, prepared_food_activities)
  • New enums (prepared_food_status, prepared_food_activity_type)
  • No changes to existing tables

10. Risks and Mitigations

Risk Mitigation
Expiry cleanup query performance with large datasets Index on expires_at + status columns
Prepared food creation adds latency to cook flow Make it optional (default off), separate transaction if needed

11. Progress

  • Entity design
  • Repository implementation
  • Service implementation
  • API endpoints + schemas
  • Migration v07
  • Integration with cook-recipe flow
  • Expiry cleanup endpoint
  • Tests

12. Changelog

Date Change
2026-02-07 Initial draft