Spaces:
Running
A newer version of the Gradio SDK is available: 6.20.0
ShopStack Architecture
Last updated: 2026-06-15 Version: 0.1.0 Purpose: Comprehensive architectural reference for the ShopStack shopping intelligence platform.
1. System Overview
ShopStack is a local-first, off-the-grid shopping intelligence platform that helps households know what they have, what to use soon, what to buy, what to skip, and where to buy from β without sending data to the cloud.
1.1 Design Philosophy
| Principle | Application |
|---|---|
| Local-first | SQLite database (WAL mode), no cloud dependencies for core functionality |
| Off-the-grid | All mock providers by default; real models via optional local backends (MLX, llama.cpp) |
| Decision-first | Every workflow leads to a buy/skip/use-soon decision, not raw data |
| Traceable | Every tool execution creates an auditable trace with PII redaction |
| Composable modules | Six logical modules (ShopStock, ShopBasket, ShopCompare, etc.) share data through the same database |
1.2 Architecture Diagram
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Gradio Blocks (app.py) β
β 13 tabs, workflow-header, custom CSS theme β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.ui.screens/ β
β dashboard.py shopping.py market_lens.py inventory.py β
β ask.py traces.py other.py price.py model_stack.py β
β portability.py household.py field_notes.py _utils.py β
β (Each screen is a Gradio adapter: parse β call β render) β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.services/ β
β shopping.py β shopping list normalization, classification, β
β Swiggy enrichment, list completion β
β market_lens.py β barcode scan, object detection, OCR, STT β
β dashboard.py β today dashboard state assembly β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.tools/registry.py β
β 11 tools: add/update/consume/move/find inventory items, β
β create shopping list, compare to inventory, record price, β
β use-soon, buy suggestions, export trace β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.persistence/database.py β
β SQLite + WAL mode, 10 tables, 18 seeded locations, full CRUD β
β Tables: inventory_lots, purchase_events, shopping_lists, β
β shopping_list_items, household_locations, movement_events, β
β price_observations, stores, traces, app_config β
β Views: price_history, agent_traces (compat aliases) β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.providers/ β
β registry.py ββ factory wired from Settings β
β interfaces.py ββ 11 abstract ABCs β
β mock_providers.py ββ full mock implementations β
β local_provider.py ββ MLX + llama.cpp β
β openai_provider.py ββ cloud fallback β
β whisper_provider.py ββ cloud STT β
β local_whisper_provider.py ββ on-device STT β
β runtime.py ββ RuntimeReport dataclass β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.planner/ β
β engine.py ββ PlannerEngine orchestrates completeβparseβexec β
β prompts.py ββ system prompt builder for tool-calling β
β parser.py ββ robust JSON + tool_call extraction β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β shopstack.market/ β
β schema.py ββ NormalizedMarketRecord, MarketSnapshot β
β normalization.py ββ size parser, unit prices, combo detect β
β analytics.py ββ snapshot analytics, cheapest option finder β
β basket.py ββ basket builder, canonical matching β
β metadata.py ββ produce shelf-life, waste-risk, storage β
β sources/swiggy.py ββ Swiggy loader, normalizer β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ
β Supporting modules β
β decisions.py ββ 7-class item decision engine (BUY/SKIP/...) β
β portability.py ββ JSON/CSV export/import β
β scanner.py ββ barcode decoding (pyzbar + zbarimg) β
β traces/export.py ββ PII redaction, JSONL export β
β model_registry.py ββ 16 candidate model entries β
β module_registry.py ββ canonical module metadata β
β app_context.py ββ singleton wiring (db, tools, providers) β
β config.py ββ pydantic-settings, SHOPSTACK_ env prefix β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Module Architecture
2.1 Module Map
| Module | Slug | Tabs | Dependencies | Service Paths |
|---|---|---|---|---|
| ShopStock | stock |
purchase, inventory, usesoon, map, portability | β | screens.inventory, portability |
| ShopBasket | basket |
shopping | ShopStock | services.shopping, screens.shopping |
| ShopCompare | compare |
prices | Sources | market.analytics, market.normalization |
| ShopLens | lens |
market | ShopStock | services.market_lens, screens.market_lens, scanner |
| ShopMemory | memory |
prices, notes | β | ui.views, screens.other |
| ShopAgent | agent |
today, ask, trace | ShopStock, ShopBasket, ShopMemory | planner.*, decisions, traces.export |
| Sources | sources |
(none) | β | market.sources.swiggy |
| Runtime | runtime |
modelstack | β | screens.model_stack, model_registry, providers.runtime |
2.2 Module Registry (shopstack/module_registry.py)
All module metadata is defined in a single canonical registry. Every UI surface that needs module info (names, tab labels, dependencies, service paths) imports from this registry β never hardcodes strings.
Key data structures:
ModuleMetadatadataclass (frozen) with slug, name, label, description, tab_ids, tab_labels, order, service_modules, depends_on, is_sourceTAB_ORDERdict β explicit ordering for the Gradio tab barTAB_LABELSdict β canonical display names for every tab- Lookup helpers:
get_by_slug(),get_by_tab_id(),tab_label(),tab_order(),navigation(),module_dependencies(),summary_table()
3. Data Layer
3.1 Database (SQLite, WAL mode)
Connection: check_same_thread=False (safe for Gradio multi-threaded access)
Tables:
| Table | Purpose | Key Fields |
|---|---|---|
inventory_lots |
Home inventory items | lot_id, canonical_name, quantity, unit, storage_location_id, status, price_paid, expiry dates |
purchase_events |
Purchase records | event_id, canonical_name, quantity, total_price, store_name, source_type |
shopping_lists |
Active shopping lists | list_id, name, goal, is_active |
shopping_list_items |
Items within lists | item_id, list_id, canonical_name, priority, status, linked_lots |
household_locations |
18 seeded storage locations | location_id, name, parent_location_id, location_type |
movement_events |
Item location changes | movement_id, lot_id, from/to location, source, confidence |
price_observations |
Price history records | price_id, canonical_name, quantity, unit, price, store_name, observation_date |
stores |
Store metadata | store_id, name, location, store_type |
traces |
Workflow audit trail | trace_id, input_type, user_goal, perception, decision, proposed_tool_calls |
app_config |
Key-value config storage | key, value |
Views (backward-compat aliases):
price_historyβ SELECT fromprice_observationsagent_tracesβ SELECT fromtraces
Location Hierarchy (18 seeded):
Home
βββ Kitchen
β βββ Fridge
β β βββ Fridge Door
β β βββ Fridge Top Shelf
β β βββ Fridge Vegetable Drawer
β βββ Freezer
β βββ Pantry
β βββ Pantry Top Shelf
β βββ Pantry Middle Shelf
β βββ Spice Box
βββ Bathroom
β βββ Bathroom Cabinet
β βββ Under Bathroom Sink
βββ Bedroom
β βββ Medicine Drawer
βββ Balcony
βββ Balcony Cleaning Shelf
3.2 Pydantic Models (shopstack/schemas/models.py)
All domain models are Pydantic BaseModel classes in a single file:
InventoryLot,PurchaseEvent,DetectionEvent,OcrExtractionShoppingList,ShoppingListItemVoiceCommand,ToolCall,TraceStore,PriceObservation,HouseholdLocation,MovementEventTripWeatherContext,ItemCatalog
Key enums: Currency, ItemStatus, Priority, ListItemStatus, LocationType, SourceType, MovementSource, RuntimeMode
ID generation: uuid4().hex[:12] β 12-char hex IDs throughout.
3.3 Configuration (shopstack/config.py)
pydantic-settings with SHOPSTACK_ env prefix:
| Variable | Default | Purpose |
|---|---|---|
SHOPSTACK_DB_PATH |
data/shopstack.db |
Database file path |
SHOPSTACK_APP_PORT |
7860 |
Gradio server port |
SHOPSTACK_OFF_THE_GRID |
true |
Use mock providers (no cloud) |
SHOPSTACK_PLANNER_BACKEND |
mock |
Text generation/planning |
SHOPSTACK_STT_BACKEND |
mock |
Speech-to-text |
SHOPSTACK_TTS_BACKEND |
mock |
Text-to-speech |
SHOPSTACK_VISION_BACKEND |
mock |
Vision/object detection |
SHOPSTACK_OCR_BACKEND |
mock |
OCR |
SHOPSTACK_SEGMENTATION_BACKEND |
mock |
Segmentation |
SHOPSTACK_LOCAL_MODEL_REPO |
unsloth/Llama-3.2-3B-Instruct-GGUF |
Local model source |
SHOPSTACK_LOCAL_WHISPER_SIZE |
tiny |
Local whisper model size |
SHOPSTACK_OPENAI_API_KEY |
"" |
Cloud fallback key |
4. Provider System
4.1 Provider Interfaces (shopstack/providers/interfaces.py)
11 abstract provider ABCs, each defining a capability:
| Interface | Key Methods | Mock Behavior |
|---|---|---|
STTProvider |
transcribe(audio_path) |
Returns predefined Hindi/Hinglish phrases |
TTSProvider |
speak(text) |
Writes note about what would be spoken |
VisionProvider |
analyze(image_path, prompt) |
Random samples from 26 common kitchen items |
ObjectDetectionProvider |
detect(image_path) |
Plausible bounding boxes + confidences |
GroundingProvider |
ground(image_path, text) |
Returns grounded item references |
SegmentationProvider |
segment(image_path) |
Returns placeholder masks |
OCRProvider |
extract_text(image_path) |
Returns mock extracted text |
PlannerProvider |
plan(context) |
Returns structured multi-step plans |
ToolCallParserProvider |
parse(response_text) |
Parses intent β tool call candidates |
EmbeddingsProvider |
embed(texts) |
Returns random 384-d vectors |
ImageEditProvider |
edit(image_path, prompt) |
Returns a dummy edited image path |
4.2 Provider Registry (shopstack/providers/registry.py)
The ProviderRegistry is a factory wired from Settings:
- Reads
SHOPSTACK_*_BACKENDenv vars - Backend
"mock"β Mock*Provider (default) - Backend
"local"βLocalProvider(MLX or llama.cpp) - Backend
"openai"βOpenAIProvider(cloud) - Backend
"local_whisper"βLocalWhisperProvider - Falls back gracefully to mock if a real backend isn't available
4.3 Runtime Report (shopstack/providers/runtime.py)
RuntimeReport dataclass captures:
- Provider name, backend, loaded status, capability count
- Error state if provider failed to init
- Used by the Model Stack UI tab
5. Tool System
5.1 Tool Registry (shopstack/tools/registry.py)
11 tools, each registered with name, description, args schema, and handler:
| Tool | Args | Purpose |
|---|---|---|
add_inventory_item |
canonical_name, display_name, quantity, unit, storage_location, category, purchase_date, price_paid | Add new item to inventory |
update_inventory_item |
lot_id, updates dict | Update existing inventory item |
consume_inventory_item |
lot_id, quantity | Record consumption (partial or full) |
move_inventory_item |
lot_id, to_location, from_location | Move item between storage locations |
find_item |
name (prefix search) | Search inventory across names & locations |
create_or_update_shopping_list |
goal, items list | Create/update the active shopping list |
compare_visible_item_to_inventory |
item_name | Compare detected item against current stock |
record_price_observation |
canonical_name, price, quantity, unit, store_name | Record a price observation |
get_use_soon_items |
days (default 3) | Get items expiring or aging soon |
get_next_buy_suggestions |
β | Get suggestions for what to buy next |
export_anonymized_trace |
trace_id | Export an anonymized agent trace |
6. Decision Engine (shopstack/decisions.py)
Every household item is classified into one of 7 categories:
| Decision | Color | When |
|---|---|---|
BUY |
Green | Out of stock or running low, and needed |
SKIP |
Gray | Already have enough, recently bought, or high waste risk |
USE_SOON |
Amber | Existing stock is expiring or aging |
OPTIONAL |
Blue | Not urgent, good-to-have |
COMPARE |
Purple | Needs price/store/pack comparison |
CONFIRM |
Red | Uncertain data, needs human verification |
WATCH |
Light Gray | Not urgent, monitor |
Classification logic (_classify()):
- If item is
use_soonANDquantity > 0β USE_SOON - If
low_stockANDquantity <= 0β BUY - If
low_stockANDquantity > 0β BUY - If
on_listANDquantity > 0β SKIP (already have) - If
quantity > 0AND well stocked β SKIP - Default β WATCH
Data sources for classification:
- Active inventory (DB)
- Use-soon items (3-day threshold)
- Active shopping list items
- Recent purchase events (2-day window)
- Market snapshot prices (Swiggy)
- Produce metadata (shelf life, waste risk)
7. Market Intelligence System
7.1 Data Flow
Swiggy Instamart snapshot (CSV/JSON)
β market/sources/swiggy.py (load + normalize)
β market/schema.py (NormalizedMarketRecord, MarketSnapshot)
β domain/unit_price.py (size parser, unit prices, combo detection)
β market/analytics.py (price stats, cheapest finder)
β Services (shopping.py enrichment, dashboard.py)
β UI Screens (other.py swiggy views, shopping.py cards)
7.2 Normalization Pipeline (domain/unit_price.py)
Canonical business logic for size/unit normalization lives in shopstack/domain/unit_price.py.
The original market/normalization.py is now a thin re-export shim (motto_v3 Β§7).
Raw Swiggy records are normalized through:
- Size parsing (
parse_size) β extracts numeric quantity and unit from text like "500 g", "1 kg" - Unit price calculation (
compute_unit_prices) β per-kg for weight-based, per-L for volume, per-unit for piece items - Canonical name mapping (
resolve_canonical,CANONICAL_MAP) β e.g., "Fresh Tomatoes (Hybrid)" β "tomato" - Combo detection (
canonicalize_name) β identifies multi-pack/assorted items
7.3 Produce Metadata (market/metadata.py)
Lookup table for ~80 common produce items with:
- Shelf life in days
- Waste risk (high/medium/low)
- Storage recommendations
- Use-first priority ranking
7.4 Basket Builder (market/basket.py)
Matches user item requests against available market records using:
- Canonical name matching
- Quantity/unit estimation
- Cheapest option selection
- Summary with total estimate
8. Planner System
8.1 Planner Engine (shopstack/planner/engine.py)
PlannerEngine orchestrates:
- Build prompt β system prompt + tool definitions + user question
- Get completion β calls ProviderRegistry planner backend
- Parse response β extracts JSON tool calls from LLM output
- Execute tools β runs through ToolRegistry, collects results
- Format response β human-readable output
8.2 Parser (shopstack/planner/parser.py)
Robust JSON extraction from LLM output:
- Finds ````json` blocks
- Falls back to regex for
[{"tool":...,"args":{...}}]patterns - Returns empty list on failure (graceful degradation)
8.3 Prompts (shopstack/planner/prompts.py)
System prompt builder that generates tool-calling instructions including:
- Current inventory state summary
- Available tools with arg schemas
- Usage examples
- Output format constraints
9. UI Architecture
9.1 Gradio Tab Structure
13 tabs, wired in app.py using module_registry.tab_label() for canonical names:
| Tab ID | Display Label | Screen Module | Key Functions |
|---|---|---|---|
today |
Today | screens/dashboard.py |
today_dashboard() β 6-value return |
ask |
Ask ShopStack | screens/ask.py |
ask_shopstack(), voice add commands |
shopping |
Shopping List | screens/shopping.py |
Create/view/classify/complete lists |
market |
Market Lens | screens/market_lens.py |
Scan, compare, buy/skip decisions |
purchase |
Add Purchase | screens/inventory.py |
Form + batch purchase recording |
inventory |
Find Item at Home | screens/inventory.py |
Search, cards, consume |
usesoon |
Use Soon | screens/inventory.py |
Expiry alerts, consume batch |
prices |
Price Memory Check | screens/price.py |
Price history, intelligence |
map |
Map | screens/other.py |
Location view, move items |
modelstack |
Model Stack | screens/model_stack.py |
Budget, provider status |
trace |
Traces | screens/traces.py |
List, detail, export |
portability |
Data | screens/portability.py |
JSON/CSV export/import |
notes |
Field Notes | screens/field_notes.py |
Markdown editor |
9.2 UI Components (shopstack/ui/components/cards.py)
HTML rendering helpers:
badge_html()β colored status badgescard()β styled card with header/bodyempty_state()β empty state messagerender_rows()β HTML table rows from dictsrender_decision_card()β decision with color-coded badgerender_grouped_cards()β grouped decision cardsrender_metric()β metric display
9.3 UI Views (shopstack/ui/views.py)
Dataclass-returning view builders:
PriceMemoryViewβ price history data + chart infoFieldNotesViewβ field notes load/savebuild_price_memory_view()β assembles price history + chart DataFrame
9.4 Error Boundary (shopstack/ui/screens/_utils.py)
@safe_render decorator catches exceptions in UI render functions and returns a graceful error HTML message instead of crashing the tab.
10. Service Layer
10.1 Shopping Service (shopstack/services/shopping.py)
| Function | Purpose |
|---|---|
normalize_item_name(name) |
Normalize item names (lowercase, strip) |
classify_shopping_items(items, tools) |
Classify items via LLM (buy/skip/use-soon) |
enrich_items_with_swiggy(items) |
Add Swiggy price/availability data |
complete_shopping_list_service(list_id, tools) |
Complete list β add to inventory |
mark_items_purchased_service(item_ids_json, tools) |
Mark items purchased |
10.2 Market Lens Service (shopstack/services/market_lens.py)
| Function | Purpose |
|---|---|
analyze_market_lens(image_path, audio_path, providers, tools) |
Full pipeline: detect β compare β decide |
detect_barcodes(image_path) |
Decode barcodes from image |
analyze_visible_items(image_path, providers, tools) |
Object detection + inventory comparison |
enrich_market_prices(decisions) |
Add Swiggy price data to decisions |
transcribe_audio(audio_path, providers) |
Speech-to-text |
10.3 Dashboard Service (shopstack/services/dashboard.py)
| Function | Purpose |
|---|---|
build_dashboard_state(db, tools) |
Assemble full dashboard state: inventory stats, use-soon, market basket, low-stock, recent purchases |
11. Model Registry (shopstack/model_registry.py)
16 candidate model entries across 7 provider groups.
Parameter budget: β€32B total active params (enforced by validate_active_model_budget()).
Active Models:
| Model | Group | Params | Runtime | Backend Config |
|---|---|---|---|---|
llama-3.2-3b-instruct (MLX) |
Planner | 3.0B | mlx | PLANNER_BACKEND=local |
llama-3.2-3b-gguf |
Planner | 3.0B | gguf | (llama.cpp fallback) |
local-whisper-tiny (MLX) |
STT | 0.04B | mlx | STT_BACKEND=local_whisper |
Total active: β€6.04B params β well within 32B cap.
12. Trace System (shopstack/traces/export.py)
Every tool execution creates an agent trace stored in the database. Traces include:
- Perception snapshots
- Inventory context before/after
- Decision rationale
- Proposed tool calls
- Human confirmation status
PII Redaction: Phone numbers (10+ digits), emails, Aadhar (12-digit), PAN (5+4+1), addresses. Generic name fields are preserved.
Export: JSONL format via export_traces().
13. Portability (shopstack/portability.py)
- JSON export: Full inventory + price observations + purchase events + field notes
- CSV export: Inventory items only
- JSON import: Inventory + price observations + field notes (with dedup)
- CSV import: Inventory items only (with dedup)
- Schema version: 1.0
14. CI/CD
GitHub Actions workflow (.github/workflows/ci.yml):
- Runs on push/PR to main
- Sets up Python 3.13
- Installs dependencies in dev mode
- Runs full test suite
- Runs benchmark suite
Pre-commit hook runs tools/sync-readme-stats to keep README test counts current.
15. Key Design Decisions
| Decision | Rationale |
|---|---|
| Single schemas file | Models share enums; avoid circular imports |
| *Provider ABCs named Provider | Clear naming convention prevents confusion |
| PurchaseEvent with per-item fields | No separate join table for simple purchases |
| 12-char hex IDs | Short enough for prefix resolution, collision-resistant |
| WAL mode | Concurrent read/write safe for Gradio |
| 18 seeded locations | Hierarchical, covers typical Indian household |
| No auto-purchase/payment scraping | Design-level constraint; ShopStack advises, doesn't buy |
| PII redaction targeted | Only phone/email/Aadhar/PAN/address; generic names preserved |
| Mock providers as default | Full app works without any ML model loaded |
_env_file=None in tests |
Prevents .env from affecting test results |
| shopstack.ui package | All render logic consolidated; no orphan modules |
| service boundary extraction | Product logic lives in services/; screens are Gradio adapters |
| module_registry canonical names | No hardcoded tab labels anywhere in app.py |
16. Future Architecture Targets
| Area | Planned Approach | Status |
|---|---|---|
| Cloud inference fallback | HF Inference API provider | Built in providers/huggingface_provider.py (26 tests) |
| Modal cloud GPU | Modal provider for heavy models | Built in providers/modal_provider.py |
| Semantic search | BGE-M3 + Nomic embeddings | Both providers built in providers/embeddings_provider.py. Default is Nomic (config: embeddings_backend=nomic). Wired into services/search.py + services/find.py. |
| Receipt scanning | OCR pipeline β purchase creation | Pipeline built: services/ocr_pipeline.py (3-stage) + services/receipt.py (full pipeline). OCR provider, Tesseract provider, and dedicated receipt screen exist. |
| Multi-retailer sources | Blinkit, Zepto, DMart adapters | All 4 built: Swiggy _swiggy_adapter.py, Blinkit _blinkit_adapter.py, Zepto _zepto_adapter.py, DMart _dmart_adapter.py. Cross-source comparison in _comparison.py. |
| Correction review UI | Accept/reject corrections | Built: dedicated ui/screens/corrections.py (264 lines), wired into Memory tab, per-row inline accept/reject buttons, DB correction_events table. |
| Multi-user auth | user_id columns exist in DB | Not started |
| Production deployment | Docker + deployment config | Not started |