README.it.md
Rama main · réplica de lectura del repositorio original.
# Spendif.ai
[](https://github.com/spendifai/spendif-ai/actions/workflows/ci.yml)
[](https://codecov.io/gh/spendifai/spendif-ai)
[](https://www.python.org/downloads/)
[](LICENSE)
[](https://github.com/astral-sh/uv)
[](https://streamlit.io)
[](https://github.com/spendifai/spendif-ai/issues)
[](https://github.com/spendifai/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://spendif.ai/it/getting-started.html) per la guida illustrata all'installazione e al primo avvio. Questo README è dedicato a sviluppatori e contributor.
>
> Su macOS puoi installare e restare aggiornato via Homebrew:
> ```bash
> brew tap spendifai/spendifai
> brew trust --cask spendifai/spendifai/spendifai # una volta per macchina, Homebrew 6+
> brew install --cask spendifai
> brew update && brew upgrade --cask spendifai # release successive
> ```
>
> La build macOS è **solo Apple Silicon** (M1 o successivi). Hai un Mac Intel?
> [Scrivicelo su GitHub Discussions](https://github.com/spendifai/spendif-ai/discussions) — la build Intel non è
> prevista, ma basta una persona che la chieda per ripensarci.
>
> **Disinstallare**: `brew uninstall --cask spendifai` con Homebrew, trascinare l'app nel Cestino se
> hai installato da DMG, `sudo apt remove spendifai` o `sudo dnf remove spendifai` su Linux,
> Impostazioni > App su Windows. Database, modelli e impostazioni restano in `~/.spendifai`
> (`%USERPROFILE%\.spendifai` su Windows) e nessun comando di disinstallazione li tocca: sono
> diversi GB.
>
> Per rimuovere anche i dati, esporta prima quello che vuoi tenere dalla pagina Registro, poi usa
> `brew uninstall --zap --cask spendifai` oppure il disinstallatore del tuo sistema
> ([macOS](packaging/macos/uninstall.sh), [Linux](packaging/linux/uninstall.sh),
> [Windows](packaging/windows/uninstall.ps1)), che chiede conferma separatamente per il database e
> per i modelli invece di cancellare entrambi in un colpo solo.
---
## 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 17 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/spendifai/spendif-ai/discussions). Quando arrivano abbastanza richieste sulla stessa funzione, sale in cima alla coda.
## 👩💻 Sviluppo locale
```bash
git clone https://github.com/spendifai/spendif-ai.git
cd spendif-ai
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 + 17 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 | documento interno, non pubblicato |
| 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 <ACCOUNT_ID> <TX_CODE> STIPENDIO MENSILE"
}
```
Cosa è cambiato:
- `MARIO ROSSI` (nome titolare configurato) → `Carlo Brambilla` (nome fittizio dal pool italiano, ripristinato dopo la risposta LLM)
- `IT60X...` (IBAN) → `<ACCOUNT_ID>`
- `CAU 12345` (codice transazione bancaria) → `<TX_CODE>`
- `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**.