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
# 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
```text
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
11. The analyzed input is cleared so the next note starts cleanly.
## Model Contract
The model must return JSON shaped like:
```json
{
"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:
```text
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:
```text
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:
```text
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:
```bash
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.