Public Access
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>
113 lines
5.4 KiB
Python
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 |