factcircuit
FactCircuit: an auditable claim-and-evidence verification loop with immutable provenance, staged checks, bounded retrieval, and reproducible evaluation.
AI powered (free tier) receipt analysis, granular data extraction, price comparison, shopping prediction and nutrition data
Translation pending
DataPython · HTMLGitHub @smcgarrigleOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
A self-hosted, AI-powered receipt tracker that builds a personal price history database from your grocery receipts. Built with FastAPI, SQLite, HTMX, Tailscale and Google Gemini.
Just want a look? Try the live demo — the
whole app on fictional data, no install. See DEMO.md for how it's built.
To run it locally with the same data, use the demo seed script:
This populates the database with ~4months of ~52 fictional receipts across multiple stores enough to make all dashboards charts and trends pages populate.Read the full README ↗A self-hosted, AI-powered receipt tracker that builds a personal price history database from your grocery receipts. Built with FastAPI, SQLite, HTMX, Tailscale and Google Gemini.
Just want a look? Try the live demo — the whole app on fictional data, no install. See DEMO.md for how it's built.
To run it locally with the same data, use the demo seed script:
cd backend
uv run python scripts/seed_demo.py
This populates the database with ~4months of ~52 fictional receipts across multiple stores enough to make all dashboards charts and trends pages populate.
CORRECTION_PROMPT_LIMIT lessons (default 10) — pinned ones first, then the newest.uv run python scripts/backfill_store_names.py --apply (run from backend/; without --apply it only previews). When a review changes what a store's text means, the review page offers to move that store's earlier lines with the same text to the new item, after showing exactly which lines would change.scripts/ocr_eval.py measures extraction accuracy against your own reviewed receipts (--stored for a free baseline, --live to benchmark prompt or model changes).data/inbox and they process automatically through the full OCR + review pipeline. (FOLDER_WATCH=0 to disable.)[!NOTE] Edge Cases & Why Things Don't Always Add Up Every store's POS system has its own dialect. Some stores (like Target) distribute a basket discount proportionally across every item. Others put a "-$5.00" line at the bottom, leaving the AI to guess what it applies to. The Sandbox Review step exists to smooth out these idiosyncrasies — separate basket-level rewards into negative line items, or manually adjust prices before data hits your analytics. These corrections are remembered and improve future OCR.
extension/) that surfaces your personal purchase history while browsing Amazon Fresh, Whole Foods on Amazon, and Safeway (safeway.com).Good deal, Typical price, Above avg).static/css/tailwind.css — no Node required).This project is developed through Ensemble AI Programming — a coordinated group of AI harnesses and models, each with a distinct role:
Different models specialize in specific tasks — architectural planning, precise code execution, adversarial review, and OCR parsing — so no single model's weaknesses go unchecked.
curl -LsSf https://astral.sh/uv/install.sh | sh)[!TIP] Getting a free Gemini API key takes about a minute:
- Go to Google AI Studio and sign in with any Google account.
- Click Create API key and copy the key.
- Paste it into your
.envasGEMINI_API_KEY=...(see step 1 below).The free tier is plenty for personal receipt volume — the app minimizes API calls with a rule-based fast path for digital PDFs, OCR result caching, and token-light text-paste ingestion. No credit card required.
Clone & Configure:
git clone https://github.com/smcgarrigle/ihavethereceipts.git
cd ihavethereceipts
cp .env.example .env
# Edit .env and pick an OCR backend:
# OCR_BACKEND=gemini + GEMINI_API_KEY=your_key (cloud)
# or
# OCR_BACKEND=local + OCR_MODEL=granite3.3-vision:2b (LM Studio/Ollama)
# + OCR_BACKEND_URL=http://localhost:11434/v1
Install & Run:
cd backend
uv sync # installs runtime dependencies only
uv run uvicorn app.main:app --reload
[!TIP] Contributing? Install dev tools (linter, type checker, tests) with:
uv sync --extra dev uv run pre-commit install
Optional: Local AI models (LM Studio or Ollama):
# LM Studio: download from https://lmstudio.ai and load a qwen2.5-vl or Granite model
# Ollama:
ollama pull granite3.3-vision:2b # or qwen2.5-vl
Access App: Open http://127.0.0.1:8000
Optional: Run Tailscale or similar on your device to remotely and securely connect to your server
Option 4 in ./start_server.sh runs receipts through your own OpenRouter account, on a model you choose. For users who already have OpenRouter credits and provider preferences.
# root .env
OCR_BACKEND=openrouter
OPENROUTER_API_KEY=sk-or-v1-... # https://openrouter.ai/keys
OCR_MODEL=google/gemma-4-26b-a4b-it:free # any model accepting image input
Privacy Note. When using OpenRoutes receipts leave your machine. Every request carries provider.data_collection=deny, so OpenRouter refuses providers that retain or train on inputs, enforced server-side. Whether a given model still has an endpoint under that policy depends on its providers — some free models do, some don't, and a model with none returns a 404 explaining the options. You can remove that request paramenter from @/app/services/ocr.py return {"provider": {"data_collection": "deny"}}
https://openrouter.ai/models?zdr=true&max_price=0&input_modalities=image
Dependencies are declared in backend/pyproject.toml and pinned in backend/uv.lock.
| Audience | Install command | What you get |
|---|---|---|
| Users | uv sync | Runtime deps only (FastAPI, SQLAlchemy, Gemini, etc.) |
| Contributors | uv sync --extra dev | Runtime + dev tools (Ruff, Mypy, pytest, pre-commit) |
uv.lock pins Python packages only. The frontend stack is vendored and updated manually:
| Asset | Where | How to update |
|---|---|---|
| Tailwind CSS | static/css/tailwind.css (compiled) | Bump VERSION in scripts/build_css.sh, delete tools/tailwindcss, re-run the script, then visually check key pages. Major versions (v4+) change the config format — do not bump casually. |
| HTMX / Alpine.js / Chart.js | static/js/vendor/ | Download the new minified build over the old file, update SBOM.md, and exercise the UI (modals, drawers, charts). |
| Inter font | static/fonts/ | Re-vendor the woff2 subsets and regenerate inter.css. |
Tailwind precautions (precompiled build):
static/css/tailwind.css only contains classes that existed in templates when it was last built. After adding any new Tailwind class, re-run ./scripts/build_css.sh and commit the rebuilt CSS together with the template change — otherwise the class silently renders unstyled.templates/**/*.html, app/api/pages.py, and static/js/**/*.js. If you embed Tailwind classes elsewhere, add that path to content in tailwind.config.js.safelist.'unsafe-eval' from the CSP: the standard Alpine build evaluates inline expressions via AsyncFunction, and every modal/drawer/tab depends on it.The committed uv.lock keeps installs reproducible indefinitely. Refresh it deliberately (every few months, or after a CVE) rather than routinely, and always run the tests before committing the new lock.
cd backend
# Upgrade everything to latest compatible versions
uv lock --upgrade
uv sync --extra dev
uv run pytest && uv run ruff check . && uv run mypy . # verify
# Or upgrade a single package
uv lock --upgrade-package fastapi
Commit the updated uv.lock after a successful test run.
[!NOTE] For security auditing, run
uv run pip-auditperiodically. See SBOM.md for the full dependency inventory.
See ROADMAP.md for the full strategic backlog. Highlights:
Install system dependencies before running uv sync:
# PDF processing + file-type detection
sudo apt install -y poppler-utils
# Optional: local AI via Ollama
curl -fsSL https://ollama.com/install.sh | sh
ollama pull granite3.3-vision:2b # or qwen2.5-vl
Then follow the standard Setup steps. The Makefile in the project root wraps common commands:
make setup # uv sync + pre-commit install
make run # start the dev server
make test # run pytest suite
make lint # ruff check + format check
Common Linux issues:
| Issue | Resolution |
|---|---|
pdfplumber fails to open PDFs | Run sudo apt install poppler-utils |
| Port 8000 already in use | make run PORT=8001 |
| Ollama slow on first run | First inference downloads model weights — normal |
SQLite database is locked | WAL mode is on by default; this is rare under normal use |
[!NOTE] WSL2 users: The app runs fine under WSL2. Access it at
http://127.0.0.1:8000in your Windows browser. For best performance, clone the repo into the WSL2 filesystem (~/projects/) rather than a Windows-mounted path (/mnt/c/...) — cross-filesystem I/O adds latency to file watching and uploads.
The app runs natively on Apple Silicon. After cloning, install system dependencies first:
# Homebrew (if not installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install uv poppler # uv + PDF + file-type detection
# Optional: local AI via Ollama
brew install ollama
ollama pull granite3.3-vision:2b # or qwen2.5-vl
Then follow the standard Setup steps above. A Makefile in the project root also wraps common commands:
make setup # uv sync + pre-commit install
make run # start the dev server
make test # run pytest suite
make lint # ruff check + format check
Common Mac issues:
| Issue | Resolution |
|---|---|
pdfplumber fails to open PDFs | Run brew install poppler |
| Port 8000 already in use | make run PORT=8001 |
| LM Studio / Ollama slow on first run | First inference downloads model weights — normal |
SQLite database is locked | WAL mode is on by default; this is rare under normal use |
[!TIP] The app defaults to SQLite (zero config). No extra database setup needed for single-user use on Mac.