PRAGMA foreign_keys = ON; CREATE TABLE units ( id TEXT PRIMARY KEY, name TEXT NOT NULL, symbol TEXT NOT NULL, dimension TEXT NOT NULL, system TEXT NOT NULL, base_unit_id TEXT REFERENCES units(id), factor REAL, offset REAL ); CREATE TABLE ingredients ( id TEXT PRIMARY KEY, schema_version INTEGER NOT NULL, name TEXT NOT NULL, status TEXT NOT NULL, categories_json TEXT NOT NULL DEFAULT '[]', tags_json TEXT NOT NULL DEFAULT '[]', description TEXT, source_json TEXT NOT NULL DEFAULT '{}', deleted_at TEXT ); CREATE TABLE ingredient_aliases ( ingredient_id TEXT NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE, name TEXT NOT NULL, kind TEXT, PRIMARY KEY (ingredient_id, name) ); CREATE TABLE equipment ( id TEXT PRIMARY KEY, name TEXT NOT NULL, category TEXT, notes TEXT ); CREATE TABLE prep_actions ( id TEXT PRIMARY KEY, name TEXT NOT NULL, action_type TEXT NOT NULL, default_yield_factor REAL CHECK(default_yield_factor > 0), notes TEXT ); CREATE TABLE recipes ( id TEXT PRIMARY KEY, schema_version INTEGER NOT NULL, save_version INTEGER NOT NULL DEFAULT 1, title TEXT NOT NULL, summary TEXT, categories_json TEXT NOT NULL DEFAULT '[]', tags_json TEXT NOT NULL DEFAULT '[]', yield_quantity REAL NOT NULL, yield_unit_id TEXT NOT NULL REFERENCES units(id), yield_servings REAL, yield_basis TEXT, scaling_mode TEXT, scaling_basis_id TEXT, scaling_basis_quantity REAL, scaling_basis_unit_id TEXT REFERENCES units(id), notes_json TEXT NOT NULL DEFAULT '[]', source_json TEXT NOT NULL DEFAULT '{}', auto_yield INTEGER NOT NULL DEFAULT 0, station TEXT, deleted_at TEXT, cover_media_url TEXT ); CREATE TABLE recipe_components ( recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, id TEXT NOT NULL, position INTEGER NOT NULL, name TEXT NOT NULL, notes_json TEXT NOT NULL DEFAULT '[]', PRIMARY KEY (recipe_id, id) ); CREATE TABLE recipe_items ( recipe_id TEXT NOT NULL, component_id TEXT NOT NULL, id TEXT NOT NULL, position INTEGER NOT NULL, ingredient_id TEXT REFERENCES ingredients(id), subrecipe_id TEXT REFERENCES recipes(id), quantity REAL NOT NULL, unit_id TEXT NOT NULL REFERENCES units(id), percentage REAL, basis_member INTEGER NOT NULL DEFAULT 0, optional INTEGER NOT NULL DEFAULT 0, notes TEXT, nutrition_retention_factor REAL NOT NULL DEFAULT 1 CHECK(nutrition_retention_factor >= 0 AND nutrition_retention_factor <= 1), PRIMARY KEY (recipe_id, id), FOREIGN KEY (recipe_id, component_id) REFERENCES recipe_components(recipe_id, id) ON DELETE CASCADE, CHECK ((ingredient_id IS NOT NULL) <> (subrecipe_id IS NOT NULL)) ); CREATE TABLE ingredient_prep_actions ( ingredient_id TEXT NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE, action_id TEXT NOT NULL REFERENCES prep_actions(id), yield_factor REAL NOT NULL CHECK(yield_factor > 0), notes TEXT, PRIMARY KEY (ingredient_id, action_id) ); CREATE TABLE item_prep_actions ( recipe_id TEXT NOT NULL, item_id TEXT NOT NULL, position INTEGER NOT NULL, action_id TEXT NOT NULL REFERENCES prep_actions(id), yield_factor REAL CHECK(yield_factor > 0), notes TEXT, PRIMARY KEY (recipe_id, item_id, position), FOREIGN KEY (recipe_id, item_id) REFERENCES recipe_items(recipe_id, id) ON DELETE CASCADE ); CREATE TABLE recipe_steps ( recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, id TEXT NOT NULL, position INTEGER NOT NULL, instruction TEXT NOT NULL, critical_control_point INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (recipe_id, id), UNIQUE (recipe_id, position) ); CREATE TABLE recipe_equipment ( recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, equipment_id TEXT NOT NULL REFERENCES equipment(id), PRIMARY KEY (recipe_id, equipment_id) ); CREATE TABLE step_equipment ( recipe_id TEXT NOT NULL, step_id TEXT NOT NULL, equipment_id TEXT NOT NULL REFERENCES equipment(id), PRIMARY KEY (recipe_id, step_id, equipment_id), FOREIGN KEY (recipe_id, step_id) REFERENCES recipe_steps(recipe_id, id) ON DELETE CASCADE ); CREATE TABLE ingredient_measure_conversions ( ingredient_id TEXT NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE, id TEXT NOT NULL, from_quantity REAL NOT NULL, from_unit_id TEXT NOT NULL REFERENCES units(id), to_quantity REAL NOT NULL, to_unit_id TEXT NOT NULL REFERENCES units(id), state TEXT, source_json TEXT NOT NULL, PRIMARY KEY (ingredient_id, id) ); CREATE TABLE ingredient_density_measurements ( ingredient_id TEXT NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE, id TEXT NOT NULL, mass_quantity REAL NOT NULL, mass_unit_id TEXT NOT NULL REFERENCES units(id), volume_quantity REAL NOT NULL, volume_unit_id TEXT NOT NULL REFERENCES units(id), temperature_c REAL, state TEXT, source_json TEXT NOT NULL, PRIMARY KEY (ingredient_id, id) ); CREATE TABLE recipe_measure_conversions ( recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, id TEXT NOT NULL, from_quantity REAL NOT NULL CHECK(from_quantity > 0), from_unit_id TEXT NOT NULL REFERENCES units(id), to_quantity REAL NOT NULL CHECK(to_quantity > 0), to_unit_id TEXT NOT NULL REFERENCES units(id), notes TEXT, source_json TEXT NOT NULL DEFAULT '{}', PRIMARY KEY (recipe_id, id) ); CREATE TABLE purchase_items ( id TEXT PRIMARY KEY, ingredient_id TEXT NOT NULL REFERENCES ingredients(id), name TEXT NOT NULL, brand TEXT, supplier_id TEXT, supplier_sku TEXT, status TEXT NOT NULL, package_quantity REAL NOT NULL, package_unit_id TEXT NOT NULL REFERENCES units(id), units_per_case INTEGER NOT NULL DEFAULT 1, usable_yield_factor REAL NOT NULL DEFAULT 1 ); CREATE TABLE price_observations ( purchase_item_id TEXT NOT NULL REFERENCES purchase_items(id) ON DELETE CASCADE, effective_at TEXT NOT NULL, currency TEXT NOT NULL, amount REAL NOT NULL, source_json TEXT NOT NULL, PRIMARY KEY (purchase_item_id, effective_at, currency) ); CREATE TABLE source_mappings ( id TEXT PRIMARY KEY, subject_type TEXT NOT NULL, subject_id TEXT NOT NULL, mapping_type TEXT NOT NULL, status TEXT NOT NULL, source_json TEXT NOT NULL, nutrition_json TEXT ); CREATE TABLE collections ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, source_json TEXT NOT NULL, deleted_at TEXT ); CREATE TABLE collection_recipes ( collection_id TEXT NOT NULL REFERENCES collections(id) ON DELETE CASCADE, recipe_id TEXT NOT NULL REFERENCES recipes(id), position INTEGER NOT NULL, PRIMARY KEY (collection_id, recipe_id) ); CREATE TABLE recipe_media ( recipe_id TEXT NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, id TEXT NOT NULL, step_id TEXT, media_type TEXT NOT NULL CHECK(media_type IN ('image', 'video')), url TEXT NOT NULL, caption TEXT, position INTEGER NOT NULL, PRIMARY KEY (recipe_id, id), FOREIGN KEY (recipe_id, step_id) REFERENCES recipe_steps(recipe_id, id) ON DELETE CASCADE ); CREATE INDEX recipe_items_ingredient_idx ON recipe_items(ingredient_id); CREATE INDEX recipe_items_subrecipe_idx ON recipe_items(subrecipe_id); CREATE INDEX purchase_items_ingredient_idx ON purchase_items(ingredient_id); CREATE INDEX source_mappings_subject_idx ON source_mappings(subject_type, subject_id); CREATE INDEX recipe_media_recipe_idx ON recipe_media(recipe_id, position);