# Spendif.ai [![CI](https://github.com/spendifai/spendif-ai/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/spendifai/spendif-ai/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/spendifai/spendif-ai/graph/badge.svg)](https://codecov.io/gh/spendifai/spendif-ai) [![Python 3.13](https://img.shields.io/badge/python-3.13-blue?logo=python&logoColor=white)](https://www.python.org/downloads/) [![License: PolyForm NC](https://img.shields.io/badge/license-PolyForm%20Noncommercial-orange)](LICENSE) [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) [![Streamlit](https://img.shields.io/badge/UI-Streamlit-ff4b4b?logo=streamlit&logoColor=white)](https://streamlit.io) [![Issues](https://img.shields.io/github/issues/spendifai/spendif-ai)](https://github.com/spendifai/spendif-ai/issues) [![Last commit](https://img.shields.io/github/last-commit/spendifai/spendif-ai)](https://github.com/spendifai/spendif-ai/commits/main) [![Support on Patreon](https://img.shields.io/badge/Patreon-buy%20me%20a%20coffee%20โ˜•-F96854?logo=patreon&logoColor=white)](https://patreon.com/drake69) > ๐Ÿ‡ฎ๐Ÿ‡น [Leggi in italiano](README.it.md) Unified personal finance ledger with a hybrid deterministic + LLM pipeline. Aggregates heterogeneous bank exports (current accounts, credit/debit/prepaid cards, savings accounts) into a single chronological ledger, eliminating double-counting from periodic card settlements and internal transfers. Offline-first; remote LLM backends opt-in with mandatory PII sanitization. --- > ๐Ÿ‘‹ **End users โ€” looking to install Spendif.ai?** > Go to the [Getting Started page](https://spendif.ai/getting-started.html) for the illustrated install + first-launch guide. This README is for developers and contributors. > > On macOS you can also install and stay up to date through Homebrew: > ```bash > brew tap spendifai/spendifai > brew trust --cask spendifai/spendifai/spendifai # once per machine, Homebrew 6+ > brew install --cask spendifai > brew update && brew upgrade --cask spendifai # later releases > ``` > > The macOS build is **Apple Silicon only** (M1 or newer). On an Intel Mac? > [Tell us in GitHub Discussions](https://github.com/spendifai/spendif-ai/discussions) โ€” an Intel build is not > planned, but one person asking is enough to reconsider. > > **Uninstalling**: `brew uninstall --cask spendifai` on Homebrew, drag the app to the Trash for a DMG > install, `sudo apt remove spendifai` or `sudo dnf remove spendifai` on Linux, Settings > Apps on > Windows. Your database, models and settings stay in `~/.spendifai` (`%USERPROFILE%\.spendifai` on > Windows) and no uninstall command touches them: that is several GB. > > To remove the data too, export anything worth keeping from the Ledger page first, then either > `brew uninstall --zap --cask spendifai`, or run the uninstaller for your system > ([macOS](packaging/macos/uninstall.sh), [Linux](packaging/linux/uninstall.sh), > [Windows](packaging/windows/uninstall.ps1)), which asks separately about the database and the > models rather than deleting both at once. --- ## What it is (technical) - **Python 3.13 ยท Streamlit ยท SQLAlchemy ยท Pydantic ยท Pandas** - **Hybrid pipeline**: deterministic normalizer + LLM classifier + categorizer cascade - **Multi-backend LLM** with circuit breaker: llama.cpp (default for desktop), Ollama, OpenAI, Claude โ€” direct SDK use, no LangChain - **Native desktop launcher**: pywebview + PyInstaller โ†’ DMG / MSIX / .deb / .rpm - **17-page Streamlit UI**, full IT+EN i18n (760+ translation keys) ## What's implemented - **Hybrid pipeline (deterministic + LLM)** โ€” `core/normalizer.py` parses any tabular bank export; `core/classifier.py` infers `DocumentSchema` via LLM; `core/categorizer.py` runs a 4-step cascade (user rules โ†’ regex โ†’ LLM โ†’ fallback) - **Multi-backend LLM with circuit breaker** โ€” `core/llm_backends.py` factory: llama.cpp, Ollama, OpenAI, Claude. Automatic fallback + quarantine on failure - **PII sanitization (RF-10)** โ€” IBAN / PAN / fiscal code / owner-name redaction in `core/sanitizer.py`, mandatory before any remote call (`assert_sanitized()` is a precondition, not best-effort) - **Multi-language taxonomy** โ€” 2-level in DB, 5 languages (it/en/fr/de/es), configurable from the Streamlit UI - **Card-account reconciliation (RF-03, beta)** โ€” 3-phase algorithm in `core/normalizer.py`: pairs credit-card settlements with the underlying expenses to eliminate double-counting *(edge cases still being refined)* - **Internal transfer detection (RF-04, beta)** โ€” symbolic-amount + ยฑ7-day window matching, with owner-name permutations to catch "Cognome Nome" exports *(edge cases still being refined)* ## What's coming next Spendif.ai is in alpha. The features we add next are decided together with the people using the app today: no speculative work, no roadmap dictated from above. Features under evaluation based on alpha-tester feedback: - **Cash tracking** โ€” log cash purchases manually, with no bank statement attached - **Investment performance** โ€” see at a glance how the instruments in your portfolio are doing - **Companion mobile app** โ€” capture cash on the fly from your phone and sync with the desktop Would one of these help you? Tell us in [GitHub Discussions](https://github.com/spendifai/spendif-ai/discussions). When enough requests stack up on the same feature, it moves to the top of the queue. ## ๐Ÿ‘ฉโ€๐Ÿ’ป Develop locally ```bash git clone https://github.com/spendifai/spendif-ai.git cd spendif-ai uv sync --extra desktop # Local LLM (developer choice โ€” the desktop installer handles this automatically): # โ†’ if you already have Ollama running: ollama pull gemma3:12b # โ†’ otherwise: `uv sync` installs llama-cpp-python and the launcher # auto-downloads a GGUF model on first run ./start.sh # or: streamlit run app.py ``` Prerequisites: **Python 3.13+**, **[uv](https://github.com/astral-sh/uv)**, and either Ollama or nothing (llama.cpp is bundled). Full setup โ†’ [CONTRIBUTING.en.md](CONTRIBUTING.en.md). ### Run as a native desktop app from source ```bash uv run python -m desktop.launcher ``` Opens a pywebview window, downloads an AI model on first run, and starts Streamlit inside the same window. Identical to the bundled DMG/MSIX experience. ## Run tests ```bash uv run pytest -v # full suite (no LLM mocks) uv run pytest --cov=. --cov-report=term-missing # with coverage (target โ‰ฅ 90%) uv run pytest -k "architecture" # layer separation gate uv run pytest -k "security" # forbidden patterns + SQL injection ``` Architectural and security tests are mandatory CI gates and must stay green on `main`. ## Architecture ``` ui/ โ†’ services/ โ†’ core/ โ†’ db/ โ†’ SQLite โ†‘ โ†‘ async_runner llm_backends ยท sanitizer ยท normalizer ยท classifier ยท categorizer ``` UI may import only `services/`; `core/` may not import `db/`; `db/` may not import upward. The coupling gate (`tools/coupling_check.py --strict`) blocks PRs that violate this. Full diagram and Flow 1 vs Flow 2 โ†’ [docs/architecture.md](docs/architecture.md). ## Repository layout ``` sw_artifacts/ โ”œโ”€โ”€ app.py # Streamlit entry point (onboarding gate + 17 pages) โ”œโ”€โ”€ core/ # Pipeline: orchestrator, normalizer, classifier, categorizer, sanitizer, llm_backends โ”œโ”€โ”€ services/ # Facade layer for UI; async runner; settings; import โ”œโ”€โ”€ ui/ # Streamlit pages + i18n + widgets โ”œโ”€โ”€ db/ # SQLAlchemy ORM, repository pattern, schema with auto-hash migrations โ”œโ”€โ”€ api/ # FastAPI REST endpoints (optional) โ”œโ”€โ”€ desktop/ # Native launcher (pywebview) + splash โ”œโ”€โ”€ packaging/ # Build scripts: macos/, windows/, linux/, homebrew/, winget/ โ”œโ”€โ”€ docker/ # Containerisation โ”œโ”€โ”€ prompts/ # LLM prompt templates (versioned JSON) โ”œโ”€โ”€ reports/ # HTML + CSV + XLSX export โ”œโ”€โ”€ tests/ # pytest suite (โ‰ฅ 90% coverage target) โ”œโ”€โ”€ benchmark/ # LLM benchmark suite (multi-provider) โ””โ”€โ”€ docs/ # User & developer documentation ``` More detail โ†’ [docs/developer_guide.en.md](docs/developer_guide.en.md). ## ๐Ÿ“š Documentation | Topic | Languages | |---|---| | Install & first launch | [EN](docs/installazione.en.md) ยท [IT](docs/installazione.md) | | User guide (every page) | [EN](docs/guida_utente.en.md) ยท [IT](docs/guida_utente.md) | | Reference guide (pipeline, taxonomy, RF-03/04) | [EN](docs/reference_guide.en.md) ยท [IT](docs/reference_guide.md) | | Architecture | [EN](docs/architecture.md) ยท [IT](docs/architecture.it.md) | | Design decisions | [EN](docs/design_decisions.md) ยท [IT](docs/design_decisions.it.md) | | Configuration | [EN](docs/configurazione.en.md) ยท [IT](docs/configurazione.md) | | Developer guide | [EN](docs/developer_guide.en.md) ยท [IT](docs/developer_guide.md) | | Categorisation guide | [EN](docs/guida_classificazione.en.md) ยท [IT](docs/guida_classificazione.md) | | Database schema | [EN](docs/database.en.md) ยท [IT](docs/database.md) | | Deployment | [EN](docs/deployment.en.md) ยท [IT](docs/deployment.md) | | Release process | internal document, not published | | Desktop build & test loop | [EN](docs/desktop_build_and_test.md) ยท [IT](docs/desktop_build_and_test.it.md) | | Contributing | [EN](CONTRIBUTING.en.md) ยท [IT](CONTRIBUTING.md) | | Security policy | [EN](SECURITY.md) ยท [IT](SECURITY.it.md) | | Changelog | [EN](CHANGELOG.md) ยท [IT](CHANGELOG.it.md) | ## Contributing Bug reports, feature ideas and PRs welcome. See [CONTRIBUTING.en.md](CONTRIBUTING.en.md) for the workflow, branch policy, priority framework, and CI gates. ## License **PolyForm Noncommercial 1.0.0** โ€” see [LICENSE](LICENSE). Free for personal use; commercial use requires a separate licence. --- ### What leaves the machine โ€” be precise All financial data is stored locally in `~/.spendifai/ledger.db`. **Local LLM backend (default โ€” llama.cpp, Ollama)**: nothing leaves the machine. **Remote LLM backend (opt-in โ€” OpenAI, Claude)**: the payload contains sanitised descriptions **plus** transaction **amounts**, **dates**, and **column metadata**. #### Redaction example โ€” categorizer (`core/categorizer.py:303`) Raw transaction row from the CSV: ``` date: 2026-03-15 description: "BONIFICO da MARIO ROSSI IT60X0542811101000000123456 CAU 12345 STIPENDIO MENSILE" amount: 1500.00 ``` What the remote LLM actually receives: ```json { "amount": "1500.00", "description": "BONIFICO da Carlo Brambilla STIPENDIO MENSILE" } ``` What changed: - `MARIO ROSSI` (configured owner name) โ†’ `Carlo Brambilla` (fake from Italian pool, restored after the LLM responds) - `IT60X...` (IBAN) โ†’ `` - `CAU 12345` (bank transaction code) โ†’ `` - `amount` and date metadata: **sent in the clear** The categorizer prompt instructs the model to "base the decision on the description, amount, and context" (`prompts/categorizer.json`). Whether the amount actually changes accuracy in practice has not been benchmarked against an amount-stripped baseline โ€” the conservative default keeps it in the payload until measured. #### Roadmap Amount + date redaction modes for remote backends (`none` / `buckets` / `strip`) are on the roadmap โ€” backlog item **AI-55**.