FastAPI backend (Python 3.11, MSSQL ODBC for SambaPOS, Azure DI OCR),
kitchen_db on central PG. React/TS/Vite frontend with navy sidebar layout.
Backend: auth.py (APP_SLUG=kitchen, SimpleNamespace — archive routes use
.kitchen_id/.is_admin without modification), main.py (51 migrations, scheduler,
internal router for KDS bookings feed), api/internal.py, full archive API
(31 routers: invoices, recipes, menus, sambapos, resos, newbook, disputes,
purchase_orders, etc.), models, migrations, OCR pipeline.
kitchen_id pinned to 1 (B1 — single hotel).
Frontend: AuthGate (app=kitchen, token shim for archive compat — B5b pending),
Layout (navy sidebar, 6 sections, Lucide icons, teal --app-primary),
App.tsx (Outlet pattern, UploadApp outside Layout), index.css (full :root block).
strict: false — archive components have type issues; build clean.
Note: 45 archive components call fetch('/api/...') without /kitchen/ prefix
(B5b). Runtime 404s; deferred until after initial testing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.3 KiB
LLM Integration Plan — Review & Enhancements
Supersedes the original 3-feature plan. Reviewed and expanded to cover 11 features across 6 phases, with master kill switch, removal manifest, cost guardrails, and detailed UX specification.
Context
The kitchen-invoice-flash system has several manual data entry bottlenecks where an LLM can reduce friction. Using Claude Haiku via the Anthropic Python SDK. Estimated cost: ~$0.50-1/month for a single kitchen. Mixed trigger approach: auto for cheap label parsing, user-triggered button for invoice analysis and ingredient matching.
Scope: Original 3 features + Tier 1 (A, B, C) + Tier 2 (D, E, F, G) + new Feature H (line item reconciliation). Instance model: Single instance (not multi-kitchen). Settings are global, not per-kitchen. API key storage: Plaintext in DB (matching existing pattern for Azure, NewBook, etc.).
1. What the Plan Gets Right
- Model choice: Claude Haiku is correct — structured extraction, not creative generation.
- API key in Settings: Follows the established
KitchenSettingspattern (same asazure_key,resos_api_key, etc.). - Structured output via
tool_use: Guarantees parseable JSON. Right approach over prompt-based JSON. - Graceful degradation: LLM features are additive, never blocking. Existing regex/trigram still works without a key.
- Trigger strategy: Auto for cheap label parsing, user-triggered buttons for invoice assist and ingredient matching.
2. Infrastructure Enhancements
2a. Client Instantiation
Single shared client instance, re-initialized if API key changes in settings.
2b. Response Caching
New llm_analysis_cache table:
- Columns:
id,feature,input_hash(SHA-256),result_json,model_used,prompt_version,created_at - Unique constraint:
(feature, input_hash, prompt_version) - Feature 1 (labels): cache by text hash, TTL 30 days. Feature 3 (matching): TTL 7 days. Feature 2 (invoices): skip caching.
2c. Usage Tracking
New llm_usage_log table:
- Columns:
id,feature,model,input_tokens,output_tokens,latency_ms,success,error_message,created_at GET /api/settings/llm-usageendpoint (aggregated last 30 days). Display on Settings page.
2d. Configurable Model
llm_model column in KitchenSettings (default "claude-haiku-4-5-latest"). Settings UI shows dropdown (Haiku / Sonnet).
2e. Prompt Versioning
Constants in llm_service.py. Cache lookup includes prompt_version — version bump auto-invalidates stale cache.
2f. Error UX
Three-state llm_status in API responses: "success", "unavailable" (no key), "error" (call failed + message).
2g. Rate Limiting
asyncio.Semaphore(5) caps concurrent LLM calls.
2h. Master Kill Switch
llm_enabled (Boolean, default False) in KitchenSettings. When disabled: zero AI footprint in frontend, no API calls, no logging. See LLM-MANIFEST.md for full details.
2i. Removal Manifest
LLM-MANIFEST.md in project root — updated each phase. All LLM code marked with breadcrumb comments: LLM FEATURE — see LLM-MANIFEST.md for removal instructions.
2j. Cost Guardrails (5 layers)
- Per-feature toggles —
llm_features_enabledJSONB column, individually disable features - Monthly token budget —
llm_monthly_token_limit(default 500,000 tokens ~$1.25/month) - Auto-trigger throttle — per-entity cooldown via cache check
- Single-call token cap —
max_tokenson every API call - Cost visibility — Usage stats card on Settings page
2k. Prompt Injection Mitigation
tool_use structured output mitigates this. System messages note input is "untrusted product/invoice text".
3. Features
Original Features
Feature 1: Product Label Allergen Parsing (Auto)
- Trigger: automatic when
product_ingredientstext is populated analyse_product_label(ingredients_text, flag_categories)→[{flag_id, status: "contains"|"may_contain"|"suitable_for", reason}]- ~550 tokens/call, ~$0.001
Feature 2: Invoice OCR Assist (User-triggered)
- "AI Assist" button on Review page
assist_invoice_ocr(ocr_data, supplier_list, line_items)→ supplier match, date correction, pack size extraction, OCR corrections- Batches line items in groups of 15-20 for 50+ line invoices
- ~1,900 tokens/call, ~$0.003
Feature 3: Smart Ingredient Matching (User-triggered)
- "AI Match" button when trigram results have low confidence
rank_ingredient_matches(description, candidates)→ re-ranked list with confidence scores- ~650 tokens/call, ~$0.001
Tier 1 — High Value
A. Recipe Text Allergen Scanning
Same analyse_product_label() function with recipe text as input. Catches contextual allergens regex misses.
B. Menu Description Generation
"Generate Description" button in PublishToMenuModal. Customer-facing descriptions with allergen callout.
C. Dispute Email Drafting
"Draft Email" button on DisputeDetailModal. Professional supplier email requesting credit.
Tier 2 — Medium Value
D. Smart Duplicate Detection
"AI Check" button when creating ingredients. Reuses rank_ingredient_matches().
E. OCR Field Extraction Fallback
Automatic when regex returns null. Mark LLM-extracted fields with source: "llm".
F. Supplier Alias Resolution
LLM fallback when identify_supplier() returns no match.
G. Ingredient Yield Estimation
Auto-hint on ingredient creation: "Typical yield: ~85%".
H. Invoice Line Item Reconciliation (Auto)
Matches unmatched line items against supplier's own historical naming from past 90 days.
4. Frontend UX
Core Principles
- Suggestions only, never auto-change data (except allergen "Contains" following existing Brakes pattern)
- Visible loading + notification for auto-triggered features
- Sparkle icon as consistent AI indicator
Auto-triggered Features
| Feature | Where | Visual |
|---|---|---|
| Label Parsing | IngredientModal | Spinner → toast → sparkle icon suggestions |
| Recipe Scanning | RecipeEditor | Spinner on flag matrix → sparkle suggestions |
| OCR Fallback | Invoice upload | Dashed amber border + "AI extracted" tooltip |
| Reconciliation | Review page | Amber "AI match" badge |
User-triggered Features
| Feature | Where | Trigger |
|---|---|---|
| AI Assist | Review top bar | Button → yellow-highlighted suggestions |
| AI Match | IngredientModal | Button → re-sorted dropdown |
| Menu Description | PublishToMenuModal | "Generate" button → pre-filled textarea |
| Dispute Email | DisputeDetailModal | "Draft Email" button → pre-filled text |
| Duplicate Detection | IngredientModal | "AI Check" button → warning panel |
| Supplier Alias | Review page | Auto suggestion banner |
| Yield Estimation | IngredientModal | Auto hint below field |
5. OCR Correction Enhancements (Feature 2)
LLM significantly enhances 7 existing OCR correction scenarios:
- Qty × Price ≠ Total — identifies which field Azure misread
- Line items vs invoice total mismatch — identifies delivery charges, subtotal rows
- Description content vs value — recommends which is the correct description
- SKU in description — distinguishes product codes from descriptions
- Weight-as-quantity — handles non-standard weight formats
- Subtotal/discount row detection — catches "Goods Total", "Delivery Surcharge", etc.
- Gross-to-net VAT — identifies VAT treatment from invoice context
6. Implementation Order
Phase 1: Infrastructure ✅
backend/services/llm_service.py— client, caching, logging, rate limitingbackend/requirements.txt—anthropic>=0.40.0backend/models/settings.py— LLM columnsbackend/models/llm.py— usage log + cache modelsbackend/migrations/add_llm_infrastructure.pybackend/api/settings.py— LLM fields, usage stats endpointLLM-MANIFEST.md— removal manifest
Phase 2: Label Parsing + Recipe Text (Features 1 + A)
analyse_product_label()with caching/analyse-labelendpoint- Frontend auto-trigger + sparkle suggestions
Phase 3: Invoice OCR Assist + Reconciliation (Features 2 + E + H)
assist_invoice_ocr()with batchingreconcile_line_items()for supplier history- "AI Assist" button + suggestion UI
- LLM fallback for regex field extraction
Phase 4: Ingredient Matching + Supplier (Features 3 + D + F)
rank_ingredient_matches(), supplier alias matching- "AI Match" button, duplicate detection
Phase 5: Text Generation (Features B + C)
- Menu description generation
- Dispute email drafting
Phase 6: Polish + Yield (Feature G)
- Yield estimation hints
- Usage dashboard
- Prompt tuning
7. Verification Checklist
- Add API key in Settings → saved, model dropdown works
llm_enabled = False(default) → zero AI footprint anywhere- Enable → full LLM settings section appears
- Ingredient with "Contains: wheat flour, milk" → Gluten, Dairy suggestions
- Upload invoice → "AI Assist" → corrections + suggestions
- Invalid API key → toast error, regex/trigram still works
- Budget exceeded → graceful degradation
- All AI suggestions visually distinct with sparkle icon