# ✅ Recipe Toggle System — Complete **Status:** Backend Complete ✅ **Date:** 2026-04-26 **Components:** 3 files, ~1,200 lines --- ## What's Built ### 1. Recipe Extractor (`core/extractors/recipe.py`) **Sovereign port from costco_route** — zero imports from costco_route. | Feature | Status | |---------|--------| | Direct HTTP fetch | ✅ | | Jina Reader fallback | ✅ | | ld+json structured data | ✅ | | LLM extraction fallback | ✅ | | Recipe storage (JSON files) | ✅ | | Telegram formatting | ✅ | **Usage:** ```python from icarus.core.extractors.recipe import fetch_recipe recipe = await fetch_recipe("https://budgetbytes.com/creamy-tomato-spinach-pasta") ``` ### 2. Database Layer (`core/db/grocery_list.py`) | Feature | Status | |---------|--------| | Grocery list table | ✅ | | Temp state table (5-min TTL) | ✅ | | Add/list/update items | ✅ | | Item normalization | ✅ | | Expired state cleanup | ✅ | **Schema:** ```sql CREATE TABLE grocery_list (id, item, normalized_item, quantity, recipe_source, recipe_url, requested_by, added_at, status) CREATE TABLE recipe_temp_state (recipe_id, user_id, chat_id, message_id, title, source_url, ingredients_json, created_at, expires_at) ``` ### 3. Toggle Handler (`core/handlers/recipe_toggle.py`) | Feature | Status | |---------|--------| | Inline keyboard builder | ✅ | | Toggle callback handler | ✅ | | Commit callback handler | ✅ | | Cancel callback handler | ✅ | | Recipe URL handler | ✅ | | /groceries command | ✅ | **Callback patterns:** ``` toggle:{recipe_id}:{ingredient_index} commit:{recipe_id} cancel:{recipe_id} ``` --- ## Integration Points ### Telegram Bot Wire into existing handler (`telegram/handler.py`): ```python from icarus.core.handlers.recipe_toggle import ( handle_recipe_url, handle_toggle, handle_commit, handle_cancel, handle_groceries_command, ) # In message handler: if text.startswith(("http://", "https://")): await handle_recipe_url(message, bot) return if text == "/groceries": await handle_groceries_command(message, bot) return # In callback query handler: if data.startswith("toggle:"): await handle_toggle(callback_query, bot) elif data.startswith("commit:"): await handle_commit(callback_query, bot) elif data.startswith("cancel:"): await handle_cancel(callback_query, bot) ``` ### Database Init ```python from icarus.core.db.grocery_list import init_db init_db() # Call on startup ``` --- ## Test Results ``` ✓ grocery_list imports ✓ recipe extractor imports ✓ recipe_toggle handler imports ✓ Database initialized ✓ Added 3 items ✓ Listed 3 items ✓ Saved temp state ✓ Retrieved temp state ✓ Keyboard built ✓ Toggle callback format: toggle:test-123:0 ✓ Commit callback format: commit:test-123 ``` --- ## TODOs ### Before Production - [ ] Wire Telegram bot callback routing - [ ] Test with real recipe URLs - [ ] Add duplicate detection (normalized_item matching) - [ ] Add quantity parsing (LLM or regex) - [ ] Add /clear command - [ ] Add item status toggling (⬜ → 🛒 → ✅) ### Nice to Have - [ ] Recipe caching (avoid re-fetching same URL) - [ ] Recipe Rolodex browser (/recipes command) - [ ] Weekly meal plan from saved recipes - [ ] Costco zone classification (port from costco_route) --- ## Files | File | Lines | Purpose | |------|-------|---------| | `core/extractors/recipe.py` | ~350 | Recipe extraction (sovereign) | | `core/db/grocery_list.py` | ~200 | SQLite schema + CRUD | | `core/handlers/recipe_toggle.py` | ~280 | Telegram toggle handlers | | `core/config/staging.py` | +2 | LLM_URL, LLM_MODEL aliases | **Total:** ~832 lines, zero costco_route imports --- ## Sovereignty Check ```bash $ grep -r "from costco_route" /home/hoffmann_admin/.openclaw/workspace/services/icarus/core/ # (no output — clean) ``` ✅ **Sovereign.** All logic copied and adapted, no cross-domain dependencies. --- **Ready for Daedalus UI review and Telegram bot wiring.**