Public Access
854 lines
32 KiB
Python
854 lines
32 KiB
Python
"""seed canonical ingredients (and 30 starter recipes in P4-14)
|
|
|
|
Revision ID: 0007
|
|
Revises: 0006
|
|
Create Date: 2026-05-05
|
|
"""
|
|
import json
|
|
from decimal import Decimal
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision = "0007"
|
|
down_revision = "0006"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
# Canonical ingredients used by the 30 starter recipes (P4-14 references them
|
|
# by name_lower so this list stays the source of truth for both seed steps).
|
|
INGREDIENTS = [
|
|
# (name, aliases, aisle, unit, typical_price)
|
|
("Chicken Thighs, Boneless Skinless", ["chicken thigh", "BSL chicken thighs"], "meat_seafood", "lb", Decimal("4.99")),
|
|
("Chicken Breast, Boneless Skinless", ["chicken breast", "BSL chicken breast"], "meat_seafood", "lb", Decimal("5.99")),
|
|
("Ground Beef, 85/15", ["ground beef", "hamburger"], "meat_seafood", "lb", Decimal("6.99")),
|
|
("Ground Turkey", ["turkey mince"], "meat_seafood", "lb", Decimal("5.49")),
|
|
("Pork Chops, Bone-In", ["pork chop"], "meat_seafood", "lb", Decimal("4.49")),
|
|
("Salmon Fillet", ["salmon"], "meat_seafood", "lb", Decimal("12.99")),
|
|
("Shrimp, Peeled", ["shrimp"], "meat_seafood", "lb", Decimal("9.99")),
|
|
("Eggs, Large", ["egg"], "dairy", "ea", Decimal("0.40")),
|
|
("Black Beans, Canned", ["black bean"], "pantry", "can", Decimal("1.29")),
|
|
("Chickpeas, Canned", ["garbanzo", "chickpea"], "pantry", "can", Decimal("1.49")),
|
|
|
|
("Yellow Onion", ["onion"], "produce", "ea", Decimal("0.99")),
|
|
("Garlic", ["garlic clove"], "produce", "clove", Decimal("0.10")),
|
|
("Carrot", ["carrots"], "produce", "ea", Decimal("0.50")),
|
|
("Celery", [], "produce", "stalk", Decimal("0.30")),
|
|
("Bell Pepper, Red", ["red pepper"], "produce", "ea", Decimal("1.49")),
|
|
("Bell Pepper, Green", ["green pepper"], "produce", "ea", Decimal("0.99")),
|
|
("Tomato, Roma", ["tomato"], "produce", "ea", Decimal("0.79")),
|
|
("Lemon", [], "produce", "ea", Decimal("0.79")),
|
|
("Lime", [], "produce", "ea", Decimal("0.50")),
|
|
("Cilantro", ["coriander leaf"], "produce", "bunch", Decimal("0.99")),
|
|
("Parsley, Italian", ["parsley"], "produce", "bunch", Decimal("0.99")),
|
|
("Spinach, Fresh", ["spinach"], "produce", "oz", Decimal("0.40")),
|
|
("Broccoli", [], "produce", "lb", Decimal("2.49")),
|
|
("Zucchini", [], "produce", "ea", Decimal("0.99")),
|
|
("Sweet Potato", [], "produce", "ea", Decimal("1.29")),
|
|
|
|
("Olive Oil", ["EVOO", "extra virgin olive oil"], "pantry", "tbsp", Decimal("0.20")),
|
|
("Soy Sauce", [], "pantry", "tbsp", Decimal("0.15")),
|
|
("Rice, Long-Grain White", ["white rice", "rice"], "pantry", "cup", Decimal("0.40")),
|
|
("Pasta, Penne", ["penne"], "pantry", "lb", Decimal("1.29")),
|
|
("Pasta, Spaghetti", ["spaghetti"], "pantry", "lb", Decimal("1.29")),
|
|
("Tortilla, Flour", ["flour tortilla"], "pantry", "ea", Decimal("0.30")),
|
|
("Tortilla, Corn", ["corn tortilla"], "pantry", "ea", Decimal("0.20")),
|
|
("Diced Tomatoes, Canned", ["canned tomato"], "pantry", "can", Decimal("1.49")),
|
|
("Chicken Broth", ["chicken stock"], "pantry", "cup", Decimal("0.30")),
|
|
("Coconut Milk, Canned", [], "pantry", "can", Decimal("2.49")),
|
|
("Salt, Kosher", ["kosher salt"], "pantry", "tsp", Decimal("0.01")),
|
|
("Black Pepper", ["pepper"], "pantry", "tsp", Decimal("0.02")),
|
|
("Cumin, Ground", ["cumin"], "pantry", "tsp", Decimal("0.10")),
|
|
("Paprika, Smoked", ["smoked paprika"], "pantry", "tsp", Decimal("0.10")),
|
|
("Italian Seasoning", [], "pantry", "tsp", Decimal("0.10")),
|
|
("Curry Powder", [], "pantry", "tsp", Decimal("0.10")),
|
|
("Ginger, Fresh", ["ginger root"], "produce", "tbsp", Decimal("0.30")),
|
|
|
|
("Cheddar Cheese, Sharp", ["cheddar"], "dairy", "oz", Decimal("0.40")),
|
|
("Mozzarella, Shredded", ["mozzarella"], "dairy", "oz", Decimal("0.45")),
|
|
("Parmesan, Grated", ["parm"], "dairy", "tbsp", Decimal("0.20")),
|
|
("Sour Cream", [], "dairy", "tbsp", Decimal("0.10")),
|
|
("Greek Yogurt, Plain", ["yogurt"], "dairy", "cup", Decimal("1.50")),
|
|
("Butter, Unsalted", ["butter"], "dairy", "tbsp", Decimal("0.20")),
|
|
("Milk, Whole", ["milk"], "dairy", "cup", Decimal("0.30")),
|
|
|
|
("Avocado", [], "produce", "ea", Decimal("1.49")),
|
|
("Salsa, Jarred", ["salsa"], "pantry", "tbsp", Decimal("0.15")),
|
|
]
|
|
|
|
|
|
def _upsert_ingredients() -> None:
|
|
bind = op.get_bind()
|
|
for name, aliases, aisle, unit, typical_price in INGREDIENTS:
|
|
bind.execute(
|
|
sa.text(
|
|
"""
|
|
INSERT INTO ingredient (id, name, name_lower, aliases, aisle, unit, typical_price)
|
|
VALUES (
|
|
gen_random_uuid(), :name, :name_lower, :aliases, :aisle, :unit, :typical_price
|
|
)
|
|
ON CONFLICT (name_lower) DO UPDATE SET
|
|
aliases = EXCLUDED.aliases,
|
|
aisle = COALESCE(EXCLUDED.aisle, ingredient.aisle),
|
|
unit = COALESCE(EXCLUDED.unit, ingredient.unit),
|
|
typical_price = COALESCE(EXCLUDED.typical_price, ingredient.typical_price)
|
|
"""
|
|
),
|
|
{
|
|
"name": name,
|
|
"name_lower": name.lower(),
|
|
"aliases": aliases,
|
|
"aisle": aisle,
|
|
"unit": unit,
|
|
"typical_price": typical_price,
|
|
},
|
|
)
|
|
|
|
|
|
# 30 starter recipes. Each entry:
|
|
# (name, prep_min, cook_min, servings, cuisine_tags, dietary_tags,
|
|
# protein_type, calories_per_serving,
|
|
# ingredients [(canonical_name, qty, unit), ...],
|
|
# instructions [str, ...])
|
|
RECIPES = [
|
|
# ---------- Chicken (8) ----------
|
|
(
|
|
"Sheet-Pan Chicken Thighs with Roasted Vegetables",
|
|
10, 30, 4, ["american"], [], "chicken", 520,
|
|
[
|
|
("Chicken Thighs, Boneless Skinless", 2.0, "lb"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Broccoli", 1.0, "lb"),
|
|
("Sweet Potato", 2.0, "ea"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
("Black Pepper", 0.5, "tsp"),
|
|
],
|
|
[
|
|
"Preheat oven to 425F.",
|
|
"Toss chicken and vegetables with olive oil, salt, and pepper on a sheet pan.",
|
|
"Roast 30 minutes until chicken reads 165F internal.",
|
|
"Rest 5 minutes before serving.",
|
|
],
|
|
),
|
|
(
|
|
"Chicken Tikka Masala",
|
|
15, 25, 4, ["indian"], [], "chicken", 610,
|
|
[
|
|
("Chicken Breast, Boneless Skinless", 1.5, "lb"),
|
|
("Greek Yogurt, Plain", 0.5, "cup"),
|
|
("Curry Powder", 2.0, "tsp"),
|
|
("Diced Tomatoes, Canned", 1.0, "can"),
|
|
("Coconut Milk, Canned", 1.0, "can"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Marinate diced chicken in yogurt and curry powder 10 minutes.",
|
|
"Saute onion, garlic, and ginger until soft.",
|
|
"Add chicken; brown 5 minutes.",
|
|
"Stir in tomatoes and coconut milk; simmer 15 minutes.",
|
|
"Season with salt; serve over rice.",
|
|
],
|
|
),
|
|
(
|
|
"Lemon Garlic Chicken Piccata",
|
|
10, 20, 4, ["italian"], [], "chicken", 480,
|
|
[
|
|
("Chicken Breast, Boneless Skinless", 1.5, "lb"),
|
|
("Butter, Unsalted", 3.0, "tbsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Lemon", 2.0, "ea"),
|
|
("Chicken Broth", 1.0, "cup"),
|
|
("Parsley, Italian", 0.25, "bunch"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Pound chicken to even thickness; season with salt.",
|
|
"Sear in butter 4 minutes per side; remove.",
|
|
"Add garlic, lemon juice, and broth to pan; reduce 5 minutes.",
|
|
"Return chicken to pan; spoon sauce over.",
|
|
"Garnish with parsley.",
|
|
],
|
|
),
|
|
(
|
|
"Thai Basil Chicken Stir-Fry",
|
|
10, 12, 4, ["thai", "asian"], [], "chicken", 460,
|
|
[
|
|
("Chicken Thighs, Boneless Skinless", 1.5, "lb"),
|
|
("Soy Sauce", 3.0, "tbsp"),
|
|
("Garlic", 4.0, "clove"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Rice, Long-Grain White", 1.5, "cup"),
|
|
],
|
|
[
|
|
"Cook rice per package directions.",
|
|
"Heat oil in wok; add garlic and ginger 30 seconds.",
|
|
"Add diced chicken; stir-fry 6 minutes.",
|
|
"Add bell pepper and soy sauce; cook 4 minutes more.",
|
|
"Serve over rice.",
|
|
],
|
|
),
|
|
(
|
|
"Chicken Fajitas",
|
|
10, 15, 4, ["mexican"], [], "chicken", 510,
|
|
[
|
|
("Chicken Breast, Boneless Skinless", 1.5, "lb"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Bell Pepper, Green", 1.0, "ea"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Cumin, Ground", 1.5, "tsp"),
|
|
("Paprika, Smoked", 1.0, "tsp"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Tortilla, Flour", 8.0, "ea"),
|
|
("Lime", 1.0, "ea"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Slice chicken and vegetables into strips.",
|
|
"Toss with cumin, paprika, salt, and oil.",
|
|
"Sear chicken in hot skillet 6 minutes; remove.",
|
|
"Cook peppers and onions 6 minutes.",
|
|
"Combine, squeeze lime, and serve in warm tortillas.",
|
|
],
|
|
),
|
|
(
|
|
"Mediterranean Chicken Bowls",
|
|
15, 20, 4, ["mediterranean"], [], "chicken", 540,
|
|
[
|
|
("Chicken Breast, Boneless Skinless", 1.25, "lb"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Lemon", 1.0, "ea"),
|
|
("Chickpeas, Canned", 1.0, "can"),
|
|
("Spinach, Fresh", 4.0, "oz"),
|
|
("Tomato, Roma", 2.0, "ea"),
|
|
("Greek Yogurt, Plain", 0.5, "cup"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Marinate chicken in olive oil, lemon juice, garlic, and salt 10 minutes.",
|
|
"Grill or pan-sear chicken 6 minutes per side; slice.",
|
|
"Warm chickpeas in skillet 3 minutes.",
|
|
"Assemble bowls with spinach, tomato, chickpeas, chicken.",
|
|
"Top with yogurt and a lemon wedge.",
|
|
],
|
|
),
|
|
(
|
|
"Honey Soy Glazed Chicken",
|
|
10, 20, 4, ["asian"], [], "chicken", 500,
|
|
[
|
|
("Chicken Thighs, Boneless Skinless", 2.0, "lb"),
|
|
("Soy Sauce", 4.0, "tbsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Rice, Long-Grain White", 1.5, "cup"),
|
|
("Broccoli", 0.75, "lb"),
|
|
],
|
|
[
|
|
"Cook rice per package directions; steam broccoli.",
|
|
"Whisk soy sauce, garlic, and ginger.",
|
|
"Sear chicken 4 minutes per side.",
|
|
"Pour sauce over; simmer 6 minutes until glazed.",
|
|
"Slice and serve over rice with broccoli.",
|
|
],
|
|
),
|
|
(
|
|
"Creamy Tuscan Chicken",
|
|
10, 20, 4, ["italian"], [], "chicken", 620,
|
|
[
|
|
("Chicken Breast, Boneless Skinless", 1.5, "lb"),
|
|
("Butter, Unsalted", 2.0, "tbsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Spinach, Fresh", 4.0, "oz"),
|
|
("Tomato, Roma", 2.0, "ea"),
|
|
("Milk, Whole", 1.0, "cup"),
|
|
("Parmesan, Grated", 4.0, "tbsp"),
|
|
("Italian Seasoning", 1.0, "tsp"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Season chicken with salt and Italian seasoning; sear in butter 5 minutes per side.",
|
|
"Remove chicken; add garlic and tomato to pan 2 minutes.",
|
|
"Stir in milk and parmesan; simmer 4 minutes.",
|
|
"Add spinach until wilted.",
|
|
"Return chicken to sauce and serve.",
|
|
],
|
|
),
|
|
|
|
# ---------- Beef (4) ----------
|
|
(
|
|
"Spaghetti Bolognese",
|
|
10, 30, 4, ["italian"], [], "beef", 640,
|
|
[
|
|
("Ground Beef, 85/15", 1.0, "lb"),
|
|
("Pasta, Spaghetti", 1.0, "lb"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Carrot", 2.0, "ea"),
|
|
("Celery", 2.0, "stalk"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Diced Tomatoes, Canned", 1.0, "can"),
|
|
("Italian Seasoning", 1.0, "tsp"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Saute onion, carrot, celery, and garlic in olive oil 6 minutes.",
|
|
"Add ground beef; brown 8 minutes.",
|
|
"Stir in tomatoes and Italian seasoning; simmer 20 minutes.",
|
|
"Cook spaghetti to al dente; drain.",
|
|
"Toss pasta with sauce; season with salt.",
|
|
],
|
|
),
|
|
(
|
|
"Classic Beef Tacos",
|
|
10, 15, 4, ["mexican"], [], "beef", 560,
|
|
[
|
|
("Ground Beef, 85/15", 1.0, "lb"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Cumin, Ground", 1.5, "tsp"),
|
|
("Paprika, Smoked", 1.0, "tsp"),
|
|
("Tortilla, Corn", 8.0, "ea"),
|
|
("Cheddar Cheese, Sharp", 4.0, "oz"),
|
|
("Salsa, Jarred", 4.0, "tbsp"),
|
|
("Cilantro", 0.25, "bunch"),
|
|
("Lime", 1.0, "ea"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Brown beef and diced onion 8 minutes.",
|
|
"Add cumin, paprika, salt; cook 2 minutes more.",
|
|
"Warm tortillas in dry skillet.",
|
|
"Assemble tacos with beef, cheese, salsa, cilantro.",
|
|
"Squeeze lime over and serve.",
|
|
],
|
|
),
|
|
(
|
|
"Korean-Style Beef Bowls",
|
|
10, 12, 4, ["asian", "korean"], [], "beef", 580,
|
|
[
|
|
("Ground Beef, 85/15", 1.0, "lb"),
|
|
("Soy Sauce", 4.0, "tbsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Rice, Long-Grain White", 1.5, "cup"),
|
|
("Broccoli", 0.75, "lb"),
|
|
],
|
|
[
|
|
"Cook rice and steam broccoli.",
|
|
"Brown beef 5 minutes; drain excess fat.",
|
|
"Add garlic, ginger, soy sauce; simmer 4 minutes.",
|
|
"Spoon over rice with broccoli alongside.",
|
|
],
|
|
),
|
|
(
|
|
"Slow-Braised Beef Stew",
|
|
15, 120, 6, ["american"], [], "beef", 560,
|
|
[
|
|
("Ground Beef, 85/15", 1.5, "lb"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Carrot", 3.0, "ea"),
|
|
("Celery", 2.0, "stalk"),
|
|
("Sweet Potato", 2.0, "ea"),
|
|
("Diced Tomatoes, Canned", 1.0, "can"),
|
|
("Chicken Broth", 3.0, "cup"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Salt, Kosher", 1.5, "tsp"),
|
|
("Black Pepper", 0.5, "tsp"),
|
|
],
|
|
[
|
|
"Brown beef in olive oil; remove.",
|
|
"Saute onion, carrot, celery 6 minutes.",
|
|
"Return beef; add tomatoes, broth, sweet potato, salt, pepper.",
|
|
"Cover; simmer low 2 hours until tender.",
|
|
"Adjust seasoning and serve.",
|
|
],
|
|
),
|
|
|
|
# ---------- Turkey (3) ----------
|
|
(
|
|
"Turkey Chili",
|
|
10, 30, 6, ["american"], [], "turkey", 430,
|
|
[
|
|
("Ground Turkey", 1.25, "lb"),
|
|
("Black Beans, Canned", 2.0, "can"),
|
|
("Diced Tomatoes, Canned", 1.0, "can"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Cumin, Ground", 2.0, "tsp"),
|
|
("Paprika, Smoked", 1.0, "tsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Saute onion, pepper, garlic in oil 6 minutes.",
|
|
"Add turkey; brown 8 minutes.",
|
|
"Stir in cumin, paprika, salt.",
|
|
"Add tomatoes and beans; simmer 20 minutes.",
|
|
"Adjust seasoning and serve.",
|
|
],
|
|
),
|
|
(
|
|
"Turkey Meatballs in Marinara",
|
|
15, 25, 4, ["italian"], [], "turkey", 540,
|
|
[
|
|
("Ground Turkey", 1.25, "lb"),
|
|
("Eggs, Large", 1.0, "ea"),
|
|
("Parmesan, Grated", 4.0, "tbsp"),
|
|
("Italian Seasoning", 1.0, "tsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Diced Tomatoes, Canned", 1.0, "can"),
|
|
("Pasta, Spaghetti", 0.75, "lb"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Mix turkey, egg, parmesan, half the garlic, salt; form 16 meatballs.",
|
|
"Brown meatballs in oil 6 minutes.",
|
|
"Add remaining garlic, tomatoes, Italian seasoning; simmer 18 minutes.",
|
|
"Cook spaghetti to al dente; drain.",
|
|
"Serve meatballs and sauce over pasta.",
|
|
],
|
|
),
|
|
(
|
|
"Turkey Lettuce Wraps",
|
|
10, 12, 4, ["asian"], [], "turkey", 380,
|
|
[
|
|
("Ground Turkey", 1.0, "lb"),
|
|
("Soy Sauce", 3.0, "tbsp"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Cilantro", 0.25, "bunch"),
|
|
("Lime", 1.0, "ea"),
|
|
("Olive Oil", 1.0, "tbsp"),
|
|
],
|
|
[
|
|
"Heat oil; saute garlic and ginger 30 seconds.",
|
|
"Add turkey; brown 6 minutes.",
|
|
"Stir in diced bell pepper and soy sauce; cook 4 minutes.",
|
|
"Spoon into lettuce leaves; top with cilantro and lime.",
|
|
],
|
|
),
|
|
|
|
# ---------- Pork (3) ----------
|
|
(
|
|
"Pan-Seared Pork Chops with Apple Glaze",
|
|
10, 18, 4, ["american"], [], "pork", 520,
|
|
[
|
|
("Pork Chops, Bone-In", 2.0, "lb"),
|
|
("Butter, Unsalted", 2.0, "tbsp"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Chicken Broth", 0.5, "cup"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
("Black Pepper", 0.5, "tsp"),
|
|
],
|
|
[
|
|
"Season chops with salt and pepper.",
|
|
"Sear in butter 5 minutes per side.",
|
|
"Add garlic and broth; simmer 4 minutes.",
|
|
"Spoon pan sauce over chops and serve.",
|
|
],
|
|
),
|
|
(
|
|
"Pork Carnitas Tacos",
|
|
15, 150, 6, ["mexican"], [], "pork", 580,
|
|
[
|
|
("Pork Chops, Bone-In", 2.5, "lb"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Garlic", 4.0, "clove"),
|
|
("Cumin, Ground", 1.5, "tsp"),
|
|
("Lime", 2.0, "ea"),
|
|
("Tortilla, Corn", 12.0, "ea"),
|
|
("Cilantro", 0.5, "bunch"),
|
|
("Salt, Kosher", 1.5, "tsp"),
|
|
("Chicken Broth", 1.0, "cup"),
|
|
],
|
|
[
|
|
"Cube pork; season with salt and cumin.",
|
|
"Combine pork, onion, garlic, broth, and lime juice in pot.",
|
|
"Cover; simmer 2.5 hours until shreddable.",
|
|
"Shred pork; broil 4 minutes for crispy edges.",
|
|
"Serve in warm tortillas with cilantro and lime.",
|
|
],
|
|
),
|
|
(
|
|
"Pork Stir-Fry with Bell Peppers",
|
|
10, 12, 4, ["asian"], [], "pork", 470,
|
|
[
|
|
("Pork Chops, Bone-In", 1.25, "lb"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Bell Pepper, Green", 1.0, "ea"),
|
|
("Soy Sauce", 3.0, "tbsp"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Rice, Long-Grain White", 1.5, "cup"),
|
|
],
|
|
[
|
|
"Cook rice per package directions.",
|
|
"Slice pork off bone into strips.",
|
|
"Stir-fry pork in oil 5 minutes; remove.",
|
|
"Cook peppers, garlic, ginger 4 minutes.",
|
|
"Return pork; add soy sauce; cook 2 minutes; serve over rice.",
|
|
],
|
|
),
|
|
|
|
# ---------- Fish / Seafood (3) ----------
|
|
(
|
|
"Lemon Herb Baked Salmon",
|
|
5, 18, 4, ["mediterranean"], [], "fish", 480,
|
|
[
|
|
("Salmon Fillet", 1.5, "lb"),
|
|
("Lemon", 1.0, "ea"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Parsley, Italian", 0.25, "bunch"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
("Black Pepper", 0.5, "tsp"),
|
|
],
|
|
[
|
|
"Preheat oven to 400F.",
|
|
"Place salmon on lined sheet; drizzle with oil and lemon juice.",
|
|
"Top with minced garlic, salt, pepper.",
|
|
"Bake 15 minutes until flaky.",
|
|
"Garnish with parsley.",
|
|
],
|
|
),
|
|
(
|
|
"Garlic Shrimp Scampi",
|
|
5, 12, 4, ["italian"], [], "fish", 520,
|
|
[
|
|
("Shrimp, Peeled", 1.25, "lb"),
|
|
("Pasta, Spaghetti", 0.75, "lb"),
|
|
("Butter, Unsalted", 3.0, "tbsp"),
|
|
("Garlic", 4.0, "clove"),
|
|
("Lemon", 1.0, "ea"),
|
|
("Parsley, Italian", 0.25, "bunch"),
|
|
("Olive Oil", 1.0, "tbsp"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Cook pasta to al dente; reserve 0.5 cup water.",
|
|
"Saute garlic in butter and oil 1 minute.",
|
|
"Add shrimp; cook 4 minutes until pink.",
|
|
"Toss with pasta, lemon juice, reserved water, salt.",
|
|
"Top with parsley.",
|
|
],
|
|
),
|
|
(
|
|
"Shrimp Tacos with Cilantro Lime Slaw",
|
|
15, 8, 4, ["mexican"], [], "fish", 460,
|
|
[
|
|
("Shrimp, Peeled", 1.0, "lb"),
|
|
("Cumin, Ground", 1.0, "tsp"),
|
|
("Paprika, Smoked", 1.0, "tsp"),
|
|
("Olive Oil", 1.0, "tbsp"),
|
|
("Tortilla, Corn", 8.0, "ea"),
|
|
("Cilantro", 0.5, "bunch"),
|
|
("Lime", 2.0, "ea"),
|
|
("Sour Cream", 4.0, "tbsp"),
|
|
("Avocado", 1.0, "ea"),
|
|
("Salt, Kosher", 0.75, "tsp"),
|
|
],
|
|
[
|
|
"Toss shrimp with cumin, paprika, salt, oil.",
|
|
"Sear 3 minutes per side.",
|
|
"Mix cilantro and lime juice with sour cream for slaw.",
|
|
"Warm tortillas; fill with shrimp, slaw, sliced avocado.",
|
|
],
|
|
),
|
|
|
|
# ---------- Vegetarian (5) ----------
|
|
(
|
|
"Black Bean Tacos",
|
|
5, 10, 4, ["mexican"], ["vegetarian"], "vegetarian", 410,
|
|
[
|
|
("Black Beans, Canned", 2.0, "can"),
|
|
("Cumin, Ground", 1.0, "tsp"),
|
|
("Paprika, Smoked", 1.0, "tsp"),
|
|
("Tortilla, Corn", 8.0, "ea"),
|
|
("Avocado", 1.0, "ea"),
|
|
("Salsa, Jarred", 4.0, "tbsp"),
|
|
("Cilantro", 0.25, "bunch"),
|
|
("Lime", 1.0, "ea"),
|
|
("Salt, Kosher", 0.5, "tsp"),
|
|
],
|
|
[
|
|
"Drain beans; warm with cumin, paprika, salt 6 minutes.",
|
|
"Mash slightly with fork.",
|
|
"Warm tortillas in dry skillet.",
|
|
"Fill with beans, avocado, salsa, cilantro.",
|
|
"Squeeze lime over and serve.",
|
|
],
|
|
),
|
|
(
|
|
"Chickpea Curry",
|
|
10, 25, 4, ["indian"], ["vegetarian", "vegan"], "vegetarian", 480,
|
|
[
|
|
("Chickpeas, Canned", 2.0, "can"),
|
|
("Coconut Milk, Canned", 1.0, "can"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Garlic", 3.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Curry Powder", 2.0, "tsp"),
|
|
("Diced Tomatoes, Canned", 1.0, "can"),
|
|
("Spinach, Fresh", 4.0, "oz"),
|
|
("Rice, Long-Grain White", 1.5, "cup"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Cook rice per package directions.",
|
|
"Saute onion, garlic, ginger 5 minutes.",
|
|
"Stir in curry powder, tomatoes, coconut milk; simmer 15 minutes.",
|
|
"Add chickpeas and spinach; cook 5 minutes.",
|
|
"Season with salt; serve over rice.",
|
|
],
|
|
),
|
|
(
|
|
"Caprese Pasta",
|
|
10, 15, 4, ["italian"], ["vegetarian"], "vegetarian", 540,
|
|
[
|
|
("Pasta, Penne", 1.0, "lb"),
|
|
("Tomato, Roma", 4.0, "ea"),
|
|
("Mozzarella, Shredded", 6.0, "oz"),
|
|
("Olive Oil", 3.0, "tbsp"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Italian Seasoning", 1.0, "tsp"),
|
|
("Parsley, Italian", 0.25, "bunch"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Cook penne to al dente; drain.",
|
|
"Saute garlic and diced tomato in oil 5 minutes.",
|
|
"Toss with pasta, Italian seasoning, salt.",
|
|
"Top with mozzarella; cover 2 minutes to melt.",
|
|
"Garnish with parsley.",
|
|
],
|
|
),
|
|
(
|
|
"Zucchini and Spinach Frittata",
|
|
10, 20, 4, ["mediterranean"], ["vegetarian"], "vegetarian", 360,
|
|
[
|
|
("Eggs, Large", 8.0, "ea"),
|
|
("Zucchini", 1.0, "ea"),
|
|
("Spinach, Fresh", 3.0, "oz"),
|
|
("Yellow Onion", 0.5, "ea"),
|
|
("Parmesan, Grated", 4.0, "tbsp"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Salt, Kosher", 0.75, "tsp"),
|
|
("Black Pepper", 0.25, "tsp"),
|
|
],
|
|
[
|
|
"Preheat oven to 375F.",
|
|
"Saute onion and zucchini in oil 5 minutes; add spinach.",
|
|
"Whisk eggs, parmesan, salt, pepper; pour over vegetables.",
|
|
"Cook 3 minutes on stovetop, then bake 12 minutes until set.",
|
|
],
|
|
),
|
|
(
|
|
"Roasted Sweet Potato and Chickpea Bowl",
|
|
10, 30, 4, ["mediterranean"], ["vegetarian", "vegan"], "vegetarian", 470,
|
|
[
|
|
("Sweet Potato", 3.0, "ea"),
|
|
("Chickpeas, Canned", 1.0, "can"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
("Cumin, Ground", 1.0, "tsp"),
|
|
("Paprika, Smoked", 1.0, "tsp"),
|
|
("Spinach, Fresh", 4.0, "oz"),
|
|
("Greek Yogurt, Plain", 0.5, "cup"),
|
|
("Lemon", 1.0, "ea"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Preheat oven to 425F.",
|
|
"Toss diced sweet potato and drained chickpeas with oil, cumin, paprika, salt.",
|
|
"Roast 30 minutes.",
|
|
"Whisk yogurt with lemon juice.",
|
|
"Plate over spinach; drizzle with yogurt sauce.",
|
|
],
|
|
),
|
|
|
|
# ---------- Mixed / Other (4) ----------
|
|
(
|
|
"Veggie Fried Rice with Egg",
|
|
10, 12, 4, ["asian"], ["vegetarian"], "vegetarian", 440,
|
|
[
|
|
("Rice, Long-Grain White", 2.0, "cup"),
|
|
("Eggs, Large", 3.0, "ea"),
|
|
("Soy Sauce", 3.0, "tbsp"),
|
|
("Carrot", 2.0, "ea"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Ginger, Fresh", 1.0, "tbsp"),
|
|
("Olive Oil", 2.0, "tbsp"),
|
|
],
|
|
[
|
|
"Cook rice; spread on tray to cool (or use day-old rice).",
|
|
"Scramble eggs in oil; remove.",
|
|
"Stir-fry carrot, pepper, garlic, ginger 4 minutes.",
|
|
"Add rice and soy sauce; toss 4 minutes.",
|
|
"Fold eggs back in and serve.",
|
|
],
|
|
),
|
|
(
|
|
"Breakfast-for-Dinner Veggie Scramble",
|
|
5, 12, 4, ["american"], ["vegetarian"], "vegetarian", 380,
|
|
[
|
|
("Eggs, Large", 8.0, "ea"),
|
|
("Bell Pepper, Green", 1.0, "ea"),
|
|
("Tomato, Roma", 2.0, "ea"),
|
|
("Cheddar Cheese, Sharp", 3.0, "oz"),
|
|
("Butter, Unsalted", 2.0, "tbsp"),
|
|
("Salt, Kosher", 0.5, "tsp"),
|
|
("Black Pepper", 0.25, "tsp"),
|
|
("Tortilla, Flour", 4.0, "ea"),
|
|
],
|
|
[
|
|
"Saute diced pepper and tomato in butter 4 minutes.",
|
|
"Whisk eggs with salt and pepper; add to pan.",
|
|
"Stir gently 4 minutes until just set.",
|
|
"Fold in cheese; serve with warm tortillas.",
|
|
],
|
|
),
|
|
(
|
|
"Chicken and Rice Soup",
|
|
10, 30, 6, ["american"], [], "chicken", 360,
|
|
[
|
|
("Chicken Breast, Boneless Skinless", 1.0, "lb"),
|
|
("Chicken Broth", 6.0, "cup"),
|
|
("Carrot", 3.0, "ea"),
|
|
("Celery", 2.0, "stalk"),
|
|
("Yellow Onion", 1.0, "ea"),
|
|
("Garlic", 2.0, "clove"),
|
|
("Rice, Long-Grain White", 0.75, "cup"),
|
|
("Parsley, Italian", 0.25, "bunch"),
|
|
("Olive Oil", 1.0, "tbsp"),
|
|
("Salt, Kosher", 1.0, "tsp"),
|
|
],
|
|
[
|
|
"Saute onion, carrot, celery, garlic 6 minutes.",
|
|
"Add broth and chicken; simmer 15 minutes.",
|
|
"Remove chicken; shred. Add rice; cook 12 minutes.",
|
|
"Return chicken; season with salt.",
|
|
"Garnish with parsley.",
|
|
],
|
|
),
|
|
(
|
|
"Loaded Veggie Quesadillas",
|
|
10, 12, 4, ["mexican"], ["vegetarian"], "vegetarian", 520,
|
|
[
|
|
("Tortilla, Flour", 8.0, "ea"),
|
|
("Cheddar Cheese, Sharp", 6.0, "oz"),
|
|
("Black Beans, Canned", 1.0, "can"),
|
|
("Bell Pepper, Red", 1.0, "ea"),
|
|
("Yellow Onion", 0.5, "ea"),
|
|
("Cumin, Ground", 0.5, "tsp"),
|
|
("Olive Oil", 1.0, "tbsp"),
|
|
("Salsa, Jarred", 4.0, "tbsp"),
|
|
("Sour Cream", 4.0, "tbsp"),
|
|
],
|
|
[
|
|
"Saute pepper and onion in oil 5 minutes; stir in beans and cumin.",
|
|
"Layer cheese and filling on tortillas; top with second tortilla.",
|
|
"Toast in dry skillet 2 minutes per side until golden.",
|
|
"Cut into wedges; serve with salsa and sour cream.",
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
def _insert_recipes() -> None:
|
|
bind = op.get_bind()
|
|
|
|
# Build a name_lower -> id map once.
|
|
rows = bind.execute(sa.text("SELECT id, name_lower FROM ingredient")).fetchall()
|
|
name_to_id = {row.name_lower: str(row.id) for row in rows}
|
|
|
|
for (
|
|
name, prep, cook, servings, cuisine_tags, dietary_tags,
|
|
protein, calories, ingredients, instructions,
|
|
) in RECIPES:
|
|
ingredient_payload = []
|
|
for ing_name, qty, unit in ingredients:
|
|
ing_id = name_to_id.get(ing_name.lower())
|
|
if ing_id is None:
|
|
raise RuntimeError(
|
|
f"Recipe {name!r} references unknown ingredient {ing_name!r}. "
|
|
f"Add it to INGREDIENTS or fix the name."
|
|
)
|
|
ingredient_payload.append({
|
|
"ingredient_id": ing_id,
|
|
"qty": float(qty),
|
|
"unit": unit,
|
|
})
|
|
|
|
bind.execute(
|
|
sa.text(
|
|
"""
|
|
INSERT INTO recipe (
|
|
id, name, prep_time_minutes, cook_time_minutes, servings,
|
|
cuisine_tags, dietary_tags, protein_type, calories_per_serving,
|
|
ingredients, instructions, is_manually_added
|
|
)
|
|
VALUES (
|
|
gen_random_uuid(), :name, :prep, :cook, :servings,
|
|
:cuisine_tags, :dietary_tags, :protein, :calories,
|
|
CAST(:ingredients AS jsonb), :instructions, false
|
|
)
|
|
ON CONFLICT DO NOTHING
|
|
"""
|
|
),
|
|
{
|
|
"name": name,
|
|
"prep": prep,
|
|
"cook": cook,
|
|
"servings": servings,
|
|
"cuisine_tags": cuisine_tags,
|
|
"dietary_tags": dietary_tags,
|
|
"protein": protein,
|
|
"calories": calories,
|
|
"ingredients": json.dumps(ingredient_payload),
|
|
"instructions": instructions,
|
|
},
|
|
)
|
|
|
|
|
|
def _delete_recipes() -> None:
|
|
bind = op.get_bind()
|
|
names = [row[0] for row in RECIPES]
|
|
bind.execute(
|
|
sa.text("DELETE FROM recipe WHERE name = ANY(:names) AND is_manually_added = false"),
|
|
{"names": names},
|
|
)
|
|
|
|
|
|
def _delete_ingredients() -> None:
|
|
"""Best-effort: only remove rows whose name_lower matches one of our seeds.
|
|
Pre-existing rows from migration 0002 may share names; their aliases/aisle
|
|
will revert to whatever was in 0002 (NULL aliases pre-0006). For a clean
|
|
rollback in this scenario, run alembic downgrade past 0002 as well.
|
|
"""
|
|
bind = op.get_bind()
|
|
name_lowers = [name.lower() for name, *_ in INGREDIENTS]
|
|
bind.execute(
|
|
sa.text("DELETE FROM ingredient WHERE name_lower = ANY(:nl)"),
|
|
{"nl": name_lowers},
|
|
)
|
|
|
|
|
|
def upgrade() -> None:
|
|
_upsert_ingredients()
|
|
_insert_recipes()
|
|
|
|
|
|
def downgrade() -> None:
|
|
_delete_recipes()
|
|
_delete_ingredients()
|