Files
adminandClaude Opus 4.7 8e89f793d5 feat: phase r1+r2 recovery + r3-0 swiftly api ingestion
R1 stabilization: pytest harness with transactional db fixture, smoke
+ alembic + auth + scrape + approval + swiftly tests, github actions
ci yaml. Bearer-token admin auth + signed-cookie session for family
ui mutations. Async POST /api/admin/scrape (BackgroundTasks, returns
202). Path canonicalization (no /list, /planned suffixes). DATABASE_URL
fail-fast on empty.

R2 deferred-risk spikes: live lucky california fetch (R2-A), full
email+per-voter approval click round trip with single-use enforcement
(R2-B, console email backend, sendgrid stub).

R3-0 phase 3 redesign: replaced playwright html scraper with requests
based swiftly json api client. 17 categories, ~10k products per scrape,
upsert by (source, external_id). 401 surfaces actionable token-refresh
message via ScrapeLog.error_message.

Pre-existing defects fixed: shopping_list.py syntax error blocking app
import, MealPlan.votes orphan relationship, JSONB(astext=True) invalid
kwarg, missing requests dep, calorie_target schema drift, every SQLEnum
needed values_callable, 0001 had empty downgrade(), seed had duplicate
ingredient rows.

Migrations added: 0003 grocery_item.description, 0004 family_profile.
calorie_target, 0005 grocery_item.external_id + source + composite index.

Verified: 31/31 pytest green, alembic upgrade->downgrade->upgrade clean,
frontend npm run build clean, live scrape 9,960 grocery_item rows in 36s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:08:19 -07:00

113 lines
5.4 KiB
Python

"""Seed initial data
Revision ID: 0002
Revises: 0001
Create Date: 2026-05-04
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '0002'
down_revision: Union[str, None] = '0001'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
family_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
adult1_id = 'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
adult2_id = 'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12'
op.execute(f"""
INSERT INTO family_profile (id, name, household_size, adult_count, child_count, dietary_notes, budget_per_meal)
VALUES ('{family_id}', 'Test Family', 4, 2, 2, '3 of 4 family members do not like mushrooms', 50.00)
""")
op.execute(f"""
INSERT INTO family_member (id, family_profile_id, name, email, role, likes_mushrooms)
VALUES
('{adult1_id}', '{family_id}', 'Adult 1', 'adult1@example.com', 'adult', false),
('{adult2_id}', '{family_id}', 'Adult 2', 'adult2@example.com', 'adult', true)
""")
ingredients = [
('Chicken Breast', 'chicken breast', 'lb', 'Meat', 8.99),
('Chicken Thighs', 'chicken thighs', 'lb', 'Meat', 6.99),
('Ground Beef', 'ground beef', 'lb', 'Meat', 7.99),
('Ground Turkey', 'ground turkey', 'lb', 'Meat', 7.49),
('Pork Chops', 'pork chops', 'lb', 'Meat', 8.49),
('Bacon', 'bacon', 'oz', 'Meat', 6.99),
('Shrimp', 'shrimp', 'lb', 'Seafood', 12.99),
('Salmon', 'salmon', 'lb', 'Seafood', 14.99),
('Tilapia', 'tilapia', 'lb', 'Seafood', 9.99),
('Rice', 'rice', 'lb', 'Grains', 2.49),
('Pasta', 'pasta', 'lb', 'Grains', 1.99),
('Bread', 'bread', 'loaf', 'Bakery', 3.49),
('Tortillas', 'tortillas', 'pack', 'Bakery', 4.49),
('Quinoa', 'quinoa', 'lb', 'Grains', 5.99),
('Black Beans', 'black beans', 'can', 'Canned Goods', 1.29),
('Chickpeas', 'chickpeas', 'can', 'Canned Goods', 1.49),
('Diced Tomatoes', 'diced tomatoes', 'can', 'Canned Goods', 1.99),
('Tomato Sauce', 'tomato sauce', 'can', 'Canned Goods', 1.79),
('Coconut Milk', 'coconut milk', 'can', 'Canned Goods', 2.49),
('Broccoli', 'broccoli', 'bunch', 'Produce', 3.99),
('Carrots', 'carrots', 'lb', 'Produce', 2.49),
('Spinach', 'spinach', 'bag', 'Produce', 4.99),
('Bell Peppers', 'bell peppers', 'each', 'Produce', 1.49),
('Onion', 'onion', 'lb', 'Produce', 1.29),
('Garlic', 'garlic', 'head', 'Produce', 0.79),
('Ginger', 'ginger', 'lb', 'Produce', 4.99),
('Potatoes', 'potatoes', 'lb', 'Produce', 1.99),
('Sweet Potatoes', 'sweet potatoes', 'lb', 'Produce', 2.29),
('Avocado', 'avocado', 'each', 'Produce', 1.99),
('Lime', 'lime', 'each', 'Produce', 0.50),
('Lemon', 'lemon', 'each', 'Produce', 0.60),
('Mushrooms', 'mushrooms', 'oz', 'Produce', 3.99),
('Zucchini', 'zucchini', 'lb', 'Produce', 2.49),
('Cucumber', 'cucumber', 'each', 'Produce', 1.29),
('Romaine Lettuce', 'romaine lettuce', 'head', 'Produce', 2.99),
('Cherry Tomatoes', 'cherry tomatoes', 'pint', 'Produce', 4.49),
('Cilantro', 'cilantro', 'bunch', 'Produce', 1.29),
('Parsley', 'parsley', 'bunch', 'Produce', 1.29),
('Milk', 'milk', 'gallon', 'Dairy', 4.49),
('Eggs', 'eggs', 'dozen', 'Dairy', 4.99),
('Cheese', 'cheese', 'oz', 'Dairy', 5.99),
('Butter', 'butter', 'lb', 'Dairy', 4.99),
('Greek Yogurt', 'greek yogurt', 'container', 'Dairy', 5.99),
('Parmesan', 'parmesan', 'oz', 'Dairy', 7.99),
('Mozzarella', 'mozzarella', 'oz', 'Dairy', 5.49),
('Soy Sauce', 'soy sauce', 'oz', 'Condiments', 2.99),
('Olive Oil', 'olive oil', 'oz', 'Condiments', 9.99),
('Vegetable Oil', 'vegetable oil', 'oz', 'Condiments', 4.99),
('Honey', 'honey', 'oz', 'Condiments', 7.99),
('Peanut Butter', 'peanut butter', 'jar', 'Condiments', 4.49),
('Salsa', 'salsa', 'jar', 'Condiments', 3.99),
('Chicken Broth', 'chicken broth', 'carton', 'Broths', 3.49),
('Vegetable Broth', 'vegetable broth', 'carton', 'Broths', 3.49),
('Corn Tortillas', 'corn tortillas', 'pack', 'Bakery', 3.99),
('Flour Tortillas', 'flour tortillas', 'pack', 'Bakery', 4.29),
('Tofu', 'tofu', 'oz', 'Protein', 4.99),
('Almonds', 'almonds', 'oz', 'Snacks', 8.99),
('Peanuts', 'peanuts', 'oz', 'Snacks', 5.99),
('Breadcrumbs', 'breadcrumbs', 'oz', 'Pantry', 2.99),
('Flour', 'flour', 'lb', 'Pantry', 1.99),
('Sugar', 'sugar', 'lb', 'Pantry', 2.49),
('Brown Rice', 'brown rice', 'lb', 'Grains', 3.49),
('Oats', 'oats', 'lb', 'Grains', 2.99),
('Kidney Beans', 'kidney beans', 'can', 'Canned Goods', 1.29),
('Corn', 'corn', 'can', 'Canned Goods', 1.49),
('Green Beans', 'green beans', 'can', 'Canned Goods', 1.49),
('Diced Green Chiles', 'diced green chiles', 'can', 'Canned Goods', 1.29),
]
for name, name_lower, unit, aisle, price in ingredients:
op.execute(f"""
INSERT INTO ingredient (id, name, name_lower, unit, aisle, typical_price)
VALUES (uuid_generate_v4(), '{name}', '{name_lower}', '{unit}', '{aisle}', {price})
ON CONFLICT (name_lower) DO NOTHING
""")
def downgrade() -> None:
pass