https://github.com/drake69/spendif-ai
π¦ Personal finance ledger β aggregates bank statements (CSV/XLSX) into a single ledger with hybrid deterministic + LLM categorization. Offline-first, privacy-safe.
https://github.com/drake69/spendif-ai
bank-statements budgeting finance llm ollama personal-finance python self-hosted sqlite streamlit
Last synced: 7 days ago
JSON representation
π¦ Personal finance ledger β aggregates bank statements (CSV/XLSX) into a single ledger with hybrid deterministic + LLM categorization. Offline-first, privacy-safe.
- Host: GitHub
- URL: https://github.com/drake69/spendif-ai
- Owner: drake69
- License: other
- Created: 2026-01-22T11:08:02.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-05-30T17:33:21.000Z (23 days ago)
- Last Synced: 2026-05-30T19:10:37.531Z (22 days ago)
- Topics: bank-statements, budgeting, finance, llm, ollama, personal-finance, python, self-hosted, sqlite, streamlit
- Language: Python
- Homepage: https://drake69.github.io/spendify
- Size: 4.01 MB
- Stars: 3
- Watchers: 1
- Forks: 1
- Open Issues: 24
-
Metadata Files:
- Readme: README.it.md
- Changelog: CHANGELOG.it.md
- Contributing: CONTRIBUTING.en.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.it.md
- Support: support/core_logic.py
Awesome Lists containing this project
README
# Spendif.ai v3.0
[](https://github.com/drake69/spendif-ai/actions/workflows/ci.yml)
[](https://codecov.io/gh/drake69/spendif-ai)
[](https://www.python.org/downloads/)
[](LICENSE)
[](https://github.com/astral-sh/uv)
[](https://streamlit.io)
[](https://github.com/drake69/spendif-ai/issues)
[](https://github.com/drake69/spendif-ai/commits/main)
[](https://patreon.com/drake69)
> π¬π§ [Read in English](README.md)
Registro finanziario personale unificato con pipeline ibrida deterministica + LLM. Aggrega esportazioni bancarie eterogenee (conti correnti, carte di credito/debito/prepagate, conti deposito) in un unico ledger cronologico, eliminando i doppi conteggi causati da addebiti carta ricorrenti e da giroconti interni. Offline-first; backend LLM remoti opt-in, con sanitizzazione PII obbligatoria.
---
> π **Utente finale β vuoi installare Spendif.ai?**
> Vai alla [pagina Primo avvio](https://drake69.github.io/spendif-ai/getting-started.html) per la guida illustrata all'installazione e al primo avvio. Questo README Γ¨ dedicato a sviluppatori e contributor.
---
## Cos'Γ¨ (tecnicamente)
- **Python 3.13 Β· Streamlit Β· SQLAlchemy Β· Pydantic Β· Pandas**
- **Pipeline ibrida**: normalizer deterministico + classifier LLM + categorizer a cascata
- **LLM multi-backend** con circuit breaker: llama.cpp (default per desktop), Ollama, OpenAI, Claude β interazione diretta tramite SDK, senza LangChain
- **Launcher desktop nativo**: pywebview + PyInstaller β DMG / MSIX / .deb / .rpm
- **UI Streamlit articolata in una quindicina di pagine**, i18n IT+EN completo (760+ chiavi di traduzione)
## Cosa Γ¨ implementato
- **Pipeline ibrida (deterministica + LLM)** β `core/normalizer.py` analizza qualunque esportazione bancaria tabellare; `core/classifier.py` deduce `DocumentSchema` tramite LLM; `core/categorizer.py` esegue una cascata in 4 fasi (regole utente β regex β LLM β fallback)
- **LLM multi-backend con circuit breaker** β factory in `core/llm_backends.py`: llama.cpp, Ollama, OpenAI, Claude. Fallback automatico e quarantena in caso di fallimento
- **Sanitizzazione PII (RF-10)** β redazione di IBAN / PAN / codice fiscale / nomi titolari in `core/sanitizer.py`, obbligatoria prima di qualsiasi chiamata remota (`assert_sanitized()` Γ¨ una precondizione, non un'opzione best-effort)
- **Tassonomia multilingua** β 2 livelli in DB, 5 lingue (it/en/fr/de/es), configurabile dall'UI Streamlit
- **Riconciliazione carta-conto (RF-03, beta)** β algoritmo in 3 fasi in `core/normalizer.py`: abbina gli addebiti carta con le spese sottostanti per eliminare i doppi conteggi *(casi limite ancora in fase di affinamento)*
- **Rilevazione giroconti interni (RF-04, beta)** β abbinamento per importo simbolico e finestra di Β±7 giorni, con permutazioni dei nomi del titolare per intercettare esportazioni del tipo Β«Cognome NomeΒ» *(casi limite ancora in fase di affinamento)*
## Cosa arriva dopo
Spendif.ai Γ¨ in fase di alpha test. Le funzioni che aggiungeremo le decidiamo insieme a chi usa l'app oggi: nessun lavoro speculativo, nessuna roadmap calata dall'alto.
Funzioni in valutazione in base ai riscontri degli alpha tester:
- **Registrazione del contante** β registrare manualmente le spese in contanti, senza estratto bancario
- **Andamento degli investimenti** β vedere a colpo d'occhio come si comportano gli strumenti in portafoglio
- **App mobile compagna** β registrare al volo le spese in contanti dal telefono e sincronizzarle con il desktop
Una di queste ti servirebbe? Segnalacelo su [GitHub Discussions](https://github.com/drake69/spendif-ai/discussions). Quando arrivano abbastanza richieste sulla stessa funzione, sale in cima alla coda.
## π©βπ» Sviluppo locale
```bash
git clone https://github.com/drake69/spendif-ai.git
cd spendify
uv sync --extra desktop
# LLM locale (scelta dello sviluppatore β l'installer desktop gestisce
# questo automaticamente):
# β se hai giΓ Ollama attivo: ollama pull gemma3:12b
# β altrimenti: `uv sync` installa llama-cpp-python e il launcher
# scarica automaticamente un modello GGUF al primo avvio
./start.sh # oppure: streamlit run app.py
```
Prerequisiti: **Python 3.13+**, **[uv](https://github.com/astral-sh/uv)** e, in alternativa, Ollama (oppure nulla: llama.cpp Γ¨ giΓ incluso). Configurazione completa β [CONTRIBUTING.md](CONTRIBUTING.md).
### Eseguire come app desktop nativa da sorgente
```bash
uv run python -m desktop.launcher
```
Apre una finestra pywebview, scarica un modello AI al primo avvio e avvia Streamlit all'interno della stessa finestra. L'esperienza Γ¨ identica a quella dei bundle DMG/MSIX.
## Eseguire i test
```bash
uv run pytest -v # suite completa (no mock LLM)
uv run pytest --cov=. --cov-report=term-missing # con coverage (target β₯ 90%)
uv run pytest -k "architecture" # gate separazione layer
uv run pytest -k "security" # forbidden pattern + SQL injection
```
I test architetturali e di sicurezza sono gate CI obbligatori e devono restare verdi sul branch `main`.
## Architettura
```
ui/
β
services/ ββ¬βββ core/ (pipeline: normalizer, classifier, categorizer,
β sanitizer, llm_backends, orchestrator)
β
ββββ db/ (models + repository CRUD) β SQLite
```
**Layer:**
- **`ui/`** β Pagine Streamlit (onboarding, upload, review, history, analysis,
report, budget, budget vs actual, bulk edit, chat, checklist, home, registry,
rules, settings, taxonomy, llm_models) piΓΉ `i18n/`, `widgets/`, `components/`
e `sidebar.py`. Sola presentazione: importa **esclusivamente** da `services/`.
- **`services/`** β Facade fra UI e logica interna. I servizi
(`transaction_service`, `import_service`, `review_service`, `llm_service`,
`budget_service`, `category_service`, `rule_service`, `settings_service`,
`nsi_taxonomy_service`) orchestrano la pipeline di `core/` e persistono lo
stato via `db.repository` (pattern Repository). Riesportano i tipi di dominio
(`DocumentSchema`, β¦) per evitare che l'UI tocchi i layer inferiori.
- **`core/`** β Pipeline di dominio, indipendente da Streamlit:
normalizzazione deterministica (`normalizer`, RF-02/03/04/06),
classificazione documenti via LLM (`classifier`, RF-01), categorizzazione
a cascata (`categorizer`, RF-05), sanitizzazione PII obbligatoria prima
di chiamate LLM remote (`sanitizer`, RF-10), factory dei backend LLM
(`llm_backends`), orchestratore Flow 1 / Flow 2 (`orchestrator`, RF-01βRF-07)
e motore di auto-learning storico (`history_engine`). Funzioni pure dove
possibile, unit-testabili senza mock di Streamlit.
- **`db/`** β Persistenza SQLAlchemy: `models.py` definisce le tabelle ORM
(Transaction, ImportBatch, DocumentSchemaModel, ReconciliationLink,
InternalTransferLink, CategoryRule, UserSettings, BudgetTarget,
LlmUsageLog, β¦, RF-07); `repository.py` espone CRUD idempotenti upsert-style
(RF-06/07); `taxonomy_defaults.py` contiene i template di tassonomia per
lingua.
**Regola verificata in CI dal coupling gate:** `ui/` non puΓ² importare da
`core/` nΓ© da `db/` β passa solo per `services/`. `tools/coupling_check.py
--strict` blocca le PR che la violano.
**Debito noto:** `db/repository.py` importa al top-level alcuni tipi da
`core/` (`DocumentSchema`, `CategoryRule`); `core/` a sua volta importa da
`db/` in alcuni punti con import inline dentro le funzioni per evitare cicli.
L'estrazione di un layer `schemas/` ignorante che spezzi il ciclo Γ¨ tracciata
in backlog come **AI-153**.
Diagramma completo e Flusso 1 vs Flusso 2 β [docs/architecture.it.md](docs/architecture.it.md).
## Struttura del repository
```
sw_artifacts/
βββ app.py # Entry point Streamlit (onboarding gate + ~15 pagine)
βββ core/ # Pipeline: orchestrator, normalizer, classifier, categorizer, sanitizer, llm_backends
βββ services/ # Facade layer per l'UI; async runner; settings; import
βββ ui/ # Pagine Streamlit + i18n + widgets
βββ db/ # SQLAlchemy ORM, repository pattern, schema con auto-hash migrations
βββ api/ # Endpoint REST FastAPI (opzionale)
βββ desktop/ # Launcher nativo (pywebview) + splash
βββ packaging/ # Build script: macos/, windows/, linux/, homebrew/, winget/
βββ docker/ # Containerizzazione
βββ prompts/ # Template prompt LLM (JSON versionato)
βββ reports/ # Export HTML + CSV + XLSX
βββ tests/ # Suite pytest (target coverage β₯ 90%)
βββ benchmark/ # Suite benchmark LLM (multi-provider)
βββ docs/ # Documentazione utente e sviluppatore
```
Dettagli β [docs/developer_guide.md](docs/developer_guide.md).
## π Documentazione
| Argomento | Lingue |
|---|---|
| Installazione e primo avvio | [EN](docs/installazione.en.md) Β· [IT](docs/installazione.md) |
| Guida utente (ogni pagina) | [EN](docs/guida_utente.en.md) Β· [IT](docs/guida_utente.md) |
| Reference guide (pipeline, tassonomia, RF-03/04) | [EN](docs/reference_guide.en.md) Β· [IT](docs/reference_guide.md) |
| Architettura | [EN](docs/architecture.md) Β· [IT](docs/architecture.it.md) |
| Design decisions | [EN](docs/design_decisions.md) Β· [IT](docs/design_decisions.it.md) |
| Configurazione | [EN](docs/configurazione.en.md) Β· [IT](docs/configurazione.md) |
| Developer guide | [EN](docs/developer_guide.en.md) Β· [IT](docs/developer_guide.md) |
| Guida alla categorizzazione | [EN](docs/guida_classificazione.en.md) Β· [IT](docs/guida_classificazione.md) |
| Schema database | [EN](docs/database.en.md) Β· [IT](docs/database.md) |
| Deployment | [EN](docs/deployment.en.md) Β· [IT](docs/deployment.md) |
| Processo di rilascio | [EN](docs/release_process.md) Β· [IT](docs/release_process.it.md) |
| Loop di build e test desktop | [EN](docs/desktop_build_and_test.md) Β· [IT](docs/desktop_build_and_test.it.md) |
| Contribuire | [EN](CONTRIBUTING.en.md) Β· [IT](CONTRIBUTING.md) |
| Politica di sicurezza | [EN](SECURITY.md) Β· [IT](SECURITY.it.md) |
| Changelog | [EN](CHANGELOG.md) Β· [IT](CHANGELOG.it.md) |
## Contribuire
Segnalazioni di bug, proposte di funzionalitΓ e PR sono benvenute. Vedi [CONTRIBUTING.md](CONTRIBUTING.md) per workflow, regole di branching, framework delle prioritΓ e gate CI.
## Licenza
**PolyForm Noncommercial 1.0.0** β vedi [LICENSE](LICENSE). Uso personale libero; l'uso commerciale richiede una licenza separata.
---
### Cosa lascia la macchina β trasparenza
Tutti i dati finanziari sono memorizzati localmente in `~/.spendifai/ledger.db`.
**Backend LLM locale (default β llama.cpp, Ollama)**: nulla lascia la macchina.
**Backend LLM remoto (opt-in β OpenAI, Claude)**: il payload contiene
descrizioni sanitizzate insieme a **importi**, **date** e **metadati
delle colonne**.
#### Esempio di redazione β categorizer (`core/categorizer.py:303`)
Riga grezza della transazione dal CSV:
```
date: 2026-03-15
description: "BONIFICO da MARIO ROSSI IT60X0542811101000000123456 CAU 12345 STIPENDIO MENSILE"
amount: 1500.00
```
Cosa l'LLM remoto riceve effettivamente:
```json
{
"amount": "1500.00",
"description": "BONIFICO da Carlo Brambilla STIPENDIO MENSILE"
}
```
Cosa Γ¨ cambiato:
- `MARIO ROSSI` (nome titolare configurato) β `Carlo Brambilla` (nome fittizio dal pool italiano, ripristinato dopo la risposta LLM)
- `IT60X...` (IBAN) β ``
- `CAU 12345` (codice transazione bancaria) β ``
- `amount` e metadati di data: **inviati in chiaro**
Il prompt del categorizer istruisce il modello a "basare la decisione su
descrizione, importo e contesto" (`prompts/categorizer.json`). L'effettivo
impatto dell'importo sull'accuratezza, in pratica, non Γ¨ stato misurato
contro una baseline senza importi: il comportamento predefinito,
conservativo, lo lascia nel payload finchΓ© la misurazione non sarΓ disponibile.
#### Roadmap
Le modalitΓ di redazione di importi e date per i backend remoti
(`none` / `buckets` / `strip`) sono in roadmap β elemento di backlog **AI-55**.