small-shop-ledger / ARCHITECTURE.md
keshan's picture
Submit Small Shop Ledger to Build Small Hackathon
3e02e4b verified
|
Raw
History Blame Contribute Delete
8.04 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

Architecture

Small Shop Ledger is a small-model Gradio app that turns messy shop notes into structured ledger rows, insights, and follow-up actions.

System Overview

User text/audio/document
    |
    v
Gradio UI (`shop_ledger/ui.py`)
    |
    +--> local mode: `LedgerProcessor`
    |
    +--> Modal mode: `LedgerAgent().process.remote(...)`
              |
              v
       `LedgerProcessor`
              |
              +--> llama.cpp backend (`LlamaLedgerBackend`)
              |
              +--> heuristic fallback
              |
              v
       `LedgerResult`
              |
              v
Dashboard, ledger table, automation queue, CSV export

Code Map

File Responsibility
app.py Local Gradio entrypoint.
modal_app.py Modal image, volume, GPU worker, ASGI app, model download, smoke tests.
shop_ledger/ui.py Gradio Blocks UI, input selection, callbacks, CSV export.
shop_ledger/schema.py Pydantic models for ledger entries and model results.
shop_ledger/llama_backend.py llama.cpp prompt, model loading, JSON parsing.
shop_ledger/processor.py Runtime mode selection and fallback handling.
shop_ledger/heuristics.py Deterministic parser for mock/dev/fallback mode.
shop_ledger/insights.py Dashboard metrics, risk flags, follow-up queue, breakdown tables.
tests/ Unit tests for extraction, processor fallback, input-choice behavior, and insights.

Data Flow

  1. The user enters a written note, records/uploads a voice note, or uploads a receipt/bill image or PDF.
  2. If multiple inputs are present and Auto is selected, the UI asks the user to choose which input to analyze.
  3. Audio input is transcribed locally with faster-whisper when available.
  4. Documents are prepared locally: PDFs are rendered into page images with PyMuPDF, uploaded images are resized with Pillow, and both become base64 data URLs.
  5. The chosen note text is sent to LedgerProcessor.
  6. In Modal production, LedgerProcessor uses LlamaLedgerBackend.
  7. LlamaLedgerBackend asks Gemma through llama.cpp to return strict JSON, using multimodal image_url message parts when document images are present.
  8. The result is validated by LedgerResult and LedgerEntry, then tagged with the readable LLAMA_MODEL_LABEL when llama.cpp was used.
  9. Rows are appended to Gradio state.
  10. The app recomputes:
  • ledger table
  • dashboard metrics
  • field intelligence
  • dynamic insight graph plan
  • daily shop-pulse brief
  • local ledger question answer
  • shop pulse timeline
  • counterparty memory
  • anomaly scan
  • closing checklist
  • Plotly insight figures
  • automation queue
  • review queue
  • category and party tables
  • CSV export
  1. The analyzed input is cleared so the next note starts cleanly.

Model Contract

The model must return JSON shaped like:

{
  "entries": [
    {
      "date": "YYYY-MM-DD or empty",
      "direction": "expense|income|transfer|unknown",
      "counterparty": "person or business",
      "item": "what changed hands",
      "quantity": "quantity if known",
      "amount": 0,
      "currency": "LKR",
      "category": "inventory|utilities|rent|wages|transport|maintenance|sales|general expense|uncategorized",
      "payment_status": "paid|due|partial|unknown",
      "due_date": "",
      "reminder": "short follow-up reminder or empty",
      "confidence": 0.0,
      "original_note": "source fragment"
    }
  ],
  "reminders": ["short reminders"],
  "questions": ["only ask if an amount, person, or due date is unclear"],
  "cleaned_note": "normalized note"
}

The schema intentionally tolerates null for text fields by converting it to an empty string. This prevents valid model intent from failing because of minor JSON style differences.

Fallback Design

The app keeps a deterministic heuristic parser for three reasons:

  1. Local UI development should work without downloading a 12B model.
  2. The live demo should never go completely blank if model loading fails.
  3. Tests can verify app behavior quickly.

Fallback is visible. If llama.cpp fails, model_used becomes something like:

heuristic fallback (ValidationError)

The exception details are added to questions so the UI and smoke tests expose the reason.

Insights Engine

shop_ledger/insights.py is pure Python and deterministic. It computes:

  • net cash
  • paid income
  • paid expenses
  • due income
  • due expenses
  • open follow-ups
  • average extraction confidence
  • top categories
  • top parties
  • high-value due risk flags
  • low-confidence risk flags
  • chart plan selection
  • daily brief generation with Gemma or local fallback
  • Ask My Ledger answers from structured rows with Gemma or local fallback
  • voice questions for Ask My Ledger
  • command palette actions
  • counterparty memory cards
  • anomaly detection
  • daily closing checklist
  • timeline events and pulse chart
  • Plotly figures for due radar, spend pressure, cashflow, confidence review, category mix, and party exposure
  • follow-up queue with cadence and scripts
  • reply studio variants for polite, friendly, and firm reminders
  • review queue for low-confidence or incomplete rows
  • daily field note

The chart planner is deterministic first. It asks what matters most right now: unpaid money, expense pressure, cashflow timeline, review risk, or overall category mix. Keeping insights separate from the Gradio UI makes the dashboard testable and leaves room for a later local-LLM chart selector.

UI Structure

The UI is a dark, custom-styled Gradio Blocks app organized as a small-shop operating cockpit.

Top status strip:

  • model status
  • row count

Shop OS Cockpit:

  • Capture rail: written note, voice note, document upload, input selector, currency, add/clear actions, conflict notice, and examples.
  • Shop Pulse center: live KPIs, chart composer, chart director, themed Plotly graph wall, pulse timeline, and field intelligence.
  • Ledger Assistant rail: running totals, reminders, Gemma daily brief, full Ask My Ledger chat, voice questions, prompt suggestions, command palette, and daily closing ritual.

Action Inbox:

  • follow-up automation cards
  • review desk cards
  • anomaly lantern cards
  • reply/review/anomaly tables inside a secondary accordion

Workbenches:

  • People: counterparty memory, trust pulse, party totals, and next-message suggestions.
  • Ledger Archive: raw ledger rows, CSV export, category heatmap, closing checklist table, and timeline event table.

See UI_DESIGN.md for the layout rationale, CSS hooks, and demo flow.

Modal Production Path

The live Modal path is:

fastapi_app
  -> build_demo(process_fn=process_remote)
  -> LedgerAgent().process.remote(note, currency)
  -> LedgerProcessor.from_env()
  -> LlamaLedgerBackend(...)
  -> llama_cpp.Llama.create_chat_completion(...)

The model worker is configured with:

gpu=A10
cpu=8
memory=32768
timeout=1800
LLAMA_N_GPU_LAYERS=-1
LLAMA_N_CTX=2048
LLAMA_MODEL_LABEL=unsloth/gemma-4-12b-it-GGUF / gemma-4-12b-it-UD-Q4_K_XL.gguf / llama.cpp

Testing Strategy

Current tests cover:

  • heuristic parsing
  • processor fallback behavior
  • text/audio input-choice rules
  • field-clearing callback behavior
  • dashboard metrics
  • chart-plan selection
  • Plotly figure generation
  • follow-up queue priority
  • reply studio message variants
  • review queue generation
  • risk flags

Run:

python3 -m unittest discover -s tests
python3 -m compileall shop_ledger app.py modal_app.py tests

Known Constraints

  • Gradio state is session-local. This is enough for the hackathon demo but not a multi-user accounting product.
  • CSV export is generated per session.
  • Voice transcription uses local faster-whisper only when available.
  • The app is not a replacement for accounting, tax, legal, or financial advice.
  • The app should not store sensitive customer data without adding auth and persistence controls.