Skip to content

Meal Planning - Domain Model #1

@TheMagnificent11

Description

@TheMagnificent11

From MS Copilot...

Brilliant — since you’ve already got the architecture and you’re using your own DDD framework, we can go straight into the domain model, selection algorithm, and dual‑protein logic in a way that fits cleanly into aggregates, value objects, and domain services.

I’ll keep everything domain‑driven, explicit, and extensible so you can evolve it without painting yourself into a corner.


🧱 1. Domain Model

Below is a clean domain model broken into aggregates and value objects. This is designed to support:

  • Ingredient inventory
  • Dietary profiles
  • Recipe search
  • Weekly meal planning
  • Dual‑protein variations
  • Future extensibility (shopping lists, nutrition, expiry logic)

🍎 Ingredient Aggregate

Ingredient

Represents a single ingredient in your inventory.

Ingredient
- IngredientId
- Name
- Quantity (Quantity)
- Category (IngredientCategory)
- ExpiryDate (optional)

Value Objects

Quantity
- Amount (decimal)
- Unit (UnitOfMeasure)

UnitOfMeasure
- grams, ml, pieces, tbsp, tsp, etc.

IngredientCategory
- vegetable, protein, spice, pantry, dairy, etc.

🥗 Diet Profile Aggregate

DietProfile

Represents a set of dietary rules for a person or group.

DietProfile
- ProfileId
- Name (e.g., "Vegetarian Low-FODMAP", "Omnivore")
- AllowedIngredients (list of IngredientName)
- ExcludedIngredients (list of IngredientName)
- AllowedTags (e.g., vegetarian, low-fodmap)
- ExcludedTags (e.g., garlic, onion)

This lets you express:

  • Vegetarian
  • Low‑FODMAP
  • Omnivore
  • Allergies
  • Religious restrictions

🍽️ Recipe Aggregate

Recipe

Represents a recipe returned from Spoonacular or stored locally.

Recipe
- RecipeId
- Title
- Ingredients (list of RecipeIngredient)
- Instructions (list of InstructionStep)
- Tags (list of RecipeTag)
- Nutrition (optional)

RecipeIngredient

RecipeIngredient
- Name
- Quantity (Quantity)
- IsProtein (bool)

Marking IsProtein is crucial for dual‑protein logic.


🍱 MealPlan Aggregate

MealPlan

Represents a weekly plan.

MealPlan
- MealPlanId
- WeekStartDate
- Meals (list of PlannedMeal)

PlannedMeal

PlannedMeal
- DayOfWeek
- BaseRecipe (Recipe)
- Variations (list of MealVariation)

MealVariation

MealVariation
- DietProfileId
- ProteinSubstitution (optional)
- AdjustedIngredients (list of RecipeIngredient)

This structure allows:

  • One base recipe
  • Multiple variations (vegetarian, omnivore)
  • Shared sides
  • Different proteins

🧠 2. Selection Algorithm (Friday Meal Generation)

This is a domain service, not an aggregate.

MealPlanGeneratorService

Inputs:

  • IngredientInventory
  • DietProfiles
  • RecipeRepository (Spoonacular + cached recipes)
  • NumberOfMeals (5)

Output:

  • MealPlan

Algorithm Overview

Step 1 — Build Candidate Recipe Pool

For each diet profile:

  1. Query Spoonacular with:

    • Ingredients you have
    • Dietary tags
    • Meal type = dinner
  2. Filter out recipes that:

    • Contain excluded ingredients
    • Require ingredients you don’t have (unless small amounts)
    • Cannot be adapted to dual‑protein (optional rule)
  3. Score each recipe (see scoring below)


Step 2 — Score Recipes

A simple scoring model:

Score = 
  IngredientMatchWeight * IngredientMatchPercentage
+ PerishableUsageWeight * PerishableUsageScore
+ VarietyWeight * VarietyScore
+ PrepTimeWeight * PrepTimeScore

IngredientMatchPercentage
How many ingredients you already have.

PerishableUsageScore
Higher if it uses ingredients close to expiry.

VarietyScore
Penalise repeating cuisines or proteins.

PrepTimeScore
Optional: prefer < 45 minutes.


Step 3 — Select Top Recipes

Pick the top N recipes per diet profile.

Then merge them into a combined pool.


Step 4 — Dual‑Protein Adaptation

For each candidate recipe:

  1. Identify the primary protein

  2. Check if:

    • The base recipe is vegetarian OR
    • The protein is a single ingredient that can be swapped
  3. Generate variations:

    • Vegetarian version (tofu, tempeh, eggs)
    • Omnivore version (chicken, salmon, beef, etc.)
  4. Ensure both variations share:

    • Sides
    • Seasoning
    • Cooking method (if possible)

Step 5 — Build the Weekly Plan

Pick 5 meals that:

  • Maximise ingredient usage
  • Respect dietary profiles
  • Provide variety
  • Include at least 2–3 dual‑protein meals

Step 6 — Produce Shopping List

Subtract used ingredients from inventory.

Add missing ingredients.


🔀 3. Dual‑Protein Logic (Domain Service)

ProteinSubstitutionService

Inputs:

  • Recipe
  • DietProfile
  • ProteinMappingRules

Output:

  • MealVariation

Protein Mapping Rules

Define a mapping table:

ProteinMapping
- BaseProtein: "salmon"
- VegetarianAlternative: "tofu"

ProteinMapping
- BaseProtein: "chicken"
- VegetarianAlternative: "tempeh"

You can also define:

ProteinCategoryMapping
- fish → tofu
- poultry → tempeh
- beef → seitan (if not low-FODMAP)

Substitution Algorithm

  1. Identify the protein ingredient in the recipe
  2. Look up a vegetarian alternative
  3. Replace the protein ingredient
  4. Adjust cooking instructions:
    • Replace “pan‑fry salmon” with “pan‑fry tofu”
    • Adjust cooking time
  5. Keep all sides identical
  6. Produce two variations:
    • Vegetarian
    • Omnivore

🧩 Putting It All Together

Your DDD structure will look like:

Aggregates

  • Ingredient
  • DietProfile
  • Recipe
  • MealPlan

Domain Services

  • MealPlanGeneratorService
  • RecipeScoringService
  • ProteinSubstitutionService
  • IngredientMatchingService

Repositories

  • IngredientRepository
  • RecipeRepository (Spoonacular + cache)
  • MealPlanRepository

If you want, I can help you next with:

  • Concrete C# domain models
  • Domain service implementations
  • A full example of generating a weekly plan
  • A dual‑protein substitution engine in code
  • A sample JSON structure for Spoonacular responses mapped to your aggregates

Just tell me where you want to go from here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions