{"id":50874733,"url":"https://github.com/drake69/spendif-ai","last_synced_at":"2026-06-15T08:32:20.731Z","repository":{"id":344894964,"uuid":"1139746777","full_name":"drake69/spendif-ai","owner":"drake69","description":"🏦 Personal finance ledger — aggregates bank statements (CSV/XLSX) into a single ledger with hybrid deterministic + LLM categorization. Offline-first, privacy-safe.","archived":false,"fork":false,"pushed_at":"2026-05-30T17:33:21.000Z","size":4204,"stargazers_count":3,"open_issues_count":24,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-30T19:10:37.531Z","etag":null,"topics":["bank-statements","budgeting","finance","llm","ollama","personal-finance","python","self-hosted","sqlite","streamlit"],"latest_commit_sha":null,"homepage":"https://drake69.github.io/spendify","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/drake69.png","metadata":{"files":{"readme":"README.it.md","changelog":"CHANGELOG.it.md","contributing":"CONTRIBUTING.en.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.it.md","support":"support/core_logic.py","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-22T11:08:02.000Z","updated_at":"2026-05-30T17:33:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/drake69/spendif-ai","commit_stats":null,"previous_names":["drake69/spendify","drake69/spendif-ai"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/drake69/spendif-ai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drake69%2Fspendif-ai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drake69%2Fspendif-ai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drake69%2Fspendif-ai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drake69%2Fspendif-ai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drake69","download_url":"https://codeload.github.com/drake69/spendif-ai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drake69%2Fspendif-ai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34355157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bank-statements","budgeting","finance","llm","ollama","personal-finance","python","self-hosted","sqlite","streamlit"],"created_at":"2026-06-15T08:32:18.479Z","updated_at":"2026-06-15T08:32:20.709Z","avatar_url":"https://github.com/drake69.png","language":"Python","funding_links":["https://patreon.com/drake69"],"categories":[],"sub_categories":[],"readme":"# Spendif.ai v3.0\n\n[![CI](https://github.com/drake69/spendif-ai/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/drake69/spendif-ai/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/drake69/spendif-ai/graph/badge.svg)](https://codecov.io/gh/drake69/spendif-ai)\n[![Python 3.13](https://img.shields.io/badge/python-3.13-blue?logo=python\u0026logoColor=white)](https://www.python.org/downloads/)\n[![License: PolyForm NC](https://img.shields.io/badge/license-PolyForm%20Noncommercial-orange)](LICENSE)\n[![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)\n[![Streamlit](https://img.shields.io/badge/UI-Streamlit-ff4b4b?logo=streamlit\u0026logoColor=white)](https://streamlit.io)\n[![Issues](https://img.shields.io/github/issues/drake69/spendif-ai)](https://github.com/drake69/spendif-ai/issues)\n[![Last commit](https://img.shields.io/github/last-commit/drake69/spendif-ai)](https://github.com/drake69/spendif-ai/commits/main)\n[![Supporta su Patreon](https://img.shields.io/badge/Patreon-offrimi%20un%20caffè%20☕-F96854?logo=patreon\u0026logoColor=white)](https://patreon.com/drake69)\n\n\u003e 🇬🇧 [Read in English](README.md)\n\nRegistro 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.\n\n---\n\n\u003e 👋 **Utente finale — vuoi installare Spendif.ai?**\n\u003e 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.\n\n---\n\n## Cos'è (tecnicamente)\n\n- **Python 3.13 · Streamlit · SQLAlchemy · Pydantic · Pandas**\n- **Pipeline ibrida**: normalizer deterministico + classifier LLM + categorizer a cascata\n- **LLM multi-backend** con circuit breaker: llama.cpp (default per desktop), Ollama, OpenAI, Claude — interazione diretta tramite SDK, senza LangChain\n- **Launcher desktop nativo**: pywebview + PyInstaller → DMG / MSIX / .deb / .rpm\n- **UI Streamlit articolata in una quindicina di pagine**, i18n IT+EN completo (760+ chiavi di traduzione)\n\n## Cosa è implementato\n\n- **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)\n- **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\n- **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)\n- **Tassonomia multilingua** — 2 livelli in DB, 5 lingue (it/en/fr/de/es), configurabile dall'UI Streamlit\n- **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)*\n- **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)*\n\n## Cosa arriva dopo\n\nSpendif.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.\n\nFunzioni in valutazione in base ai riscontri degli alpha tester:\n\n- **Registrazione del contante** — registrare manualmente le spese in contanti, senza estratto bancario\n- **Andamento degli investimenti** — vedere a colpo d'occhio come si comportano gli strumenti in portafoglio\n- **App mobile compagna** — registrare al volo le spese in contanti dal telefono e sincronizzarle con il desktop\n\nUna 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.\n\n## 👩‍💻 Sviluppo locale\n\n```bash\ngit clone https://github.com/drake69/spendif-ai.git\ncd spendify\nuv sync --extra desktop\n\n# LLM locale (scelta dello sviluppatore — l'installer desktop gestisce\n# questo automaticamente):\n#   → se hai già Ollama attivo:   ollama pull gemma3:12b\n#   → altrimenti: `uv sync` installa llama-cpp-python e il launcher\n#     scarica automaticamente un modello GGUF al primo avvio\n\n./start.sh                    # oppure: streamlit run app.py\n```\n\nPrerequisiti: **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).\n\n### Eseguire come app desktop nativa da sorgente\n\n```bash\nuv run python -m desktop.launcher\n```\n\nApre 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.\n\n## Eseguire i test\n\n```bash\nuv run pytest -v                                  # suite completa (no mock LLM)\nuv run pytest --cov=. --cov-report=term-missing   # con coverage (target ≥ 90%)\nuv run pytest -k \"architecture\"                   # gate separazione layer\nuv run pytest -k \"security\"                       # forbidden pattern + SQL injection\n```\n\nI test architetturali e di sicurezza sono gate CI obbligatori e devono restare verdi sul branch `main`.\n\n## Architettura\n\n```\nui/\n ↓\nservices/  ─┬──→  core/  (pipeline: normalizer, classifier, categorizer,\n            │             sanitizer, llm_backends, orchestrator)\n            │\n            └──→  db/    (models + repository CRUD) → SQLite\n```\n\n**Layer:**\n\n- **`ui/`** — Pagine Streamlit (onboarding, upload, review, history, analysis,\n  report, budget, budget vs actual, bulk edit, chat, checklist, home, registry,\n  rules, settings, taxonomy, llm_models) più `i18n/`, `widgets/`, `components/`\n  e `sidebar.py`. Sola presentazione: importa **esclusivamente** da `services/`.\n\n- **`services/`** — Facade fra UI e logica interna. I servizi\n  (`transaction_service`, `import_service`, `review_service`, `llm_service`,\n  `budget_service`, `category_service`, `rule_service`, `settings_service`,\n  `nsi_taxonomy_service`) orchestrano la pipeline di `core/` e persistono lo\n  stato via `db.repository` (pattern Repository). Riesportano i tipi di dominio\n  (`DocumentSchema`, …) per evitare che l'UI tocchi i layer inferiori.\n\n- **`core/`** — Pipeline di dominio, indipendente da Streamlit:\n  normalizzazione deterministica (`normalizer`, RF-02/03/04/06),\n  classificazione documenti via LLM (`classifier`, RF-01), categorizzazione\n  a cascata (`categorizer`, RF-05), sanitizzazione PII obbligatoria prima\n  di chiamate LLM remote (`sanitizer`, RF-10), factory dei backend LLM\n  (`llm_backends`), orchestratore Flow 1 / Flow 2 (`orchestrator`, RF-01→RF-07)\n  e motore di auto-learning storico (`history_engine`). Funzioni pure dove\n  possibile, unit-testabili senza mock di Streamlit.\n\n- **`db/`** — Persistenza SQLAlchemy: `models.py` definisce le tabelle ORM\n  (Transaction, ImportBatch, DocumentSchemaModel, ReconciliationLink,\n  InternalTransferLink, CategoryRule, UserSettings, BudgetTarget,\n  LlmUsageLog, …, RF-07); `repository.py` espone CRUD idempotenti upsert-style\n  (RF-06/07); `taxonomy_defaults.py` contiene i template di tassonomia per\n  lingua.\n\n**Regola verificata in CI dal coupling gate:** `ui/` non può importare da\n`core/` né da `db/` — passa solo per `services/`. `tools/coupling_check.py\n--strict` blocca le PR che la violano.\n\n**Debito noto:** `db/repository.py` importa al top-level alcuni tipi da\n`core/` (`DocumentSchema`, `CategoryRule`); `core/` a sua volta importa da\n`db/` in alcuni punti con import inline dentro le funzioni per evitare cicli.\nL'estrazione di un layer `schemas/` ignorante che spezzi il ciclo è tracciata\nin backlog come **AI-153**.\n\nDiagramma completo e Flusso 1 vs Flusso 2 → [docs/architecture.it.md](docs/architecture.it.md).\n\n## Struttura del repository\n\n```\nsw_artifacts/\n├── app.py                  # Entry point Streamlit (onboarding gate + ~15 pagine)\n├── core/                   # Pipeline: orchestrator, normalizer, classifier, categorizer, sanitizer, llm_backends\n├── services/               # Facade layer per l'UI; async runner; settings; import\n├── ui/                     # Pagine Streamlit + i18n + widgets\n├── db/                     # SQLAlchemy ORM, repository pattern, schema con auto-hash migrations\n├── api/                    # Endpoint REST FastAPI (opzionale)\n├── desktop/                # Launcher nativo (pywebview) + splash\n├── packaging/              # Build script: macos/, windows/, linux/, homebrew/, winget/\n├── docker/                 # Containerizzazione\n├── prompts/                # Template prompt LLM (JSON versionato)\n├── reports/                # Export HTML + CSV + XLSX\n├── tests/                  # Suite pytest (target coverage ≥ 90%)\n├── benchmark/              # Suite benchmark LLM (multi-provider)\n└── docs/                   # Documentazione utente e sviluppatore\n```\n\nDettagli → [docs/developer_guide.md](docs/developer_guide.md).\n\n## 📚 Documentazione\n\n| Argomento | Lingue |\n|---|---|\n| Installazione e primo avvio | [EN](docs/installazione.en.md) · [IT](docs/installazione.md) |\n| Guida utente (ogni pagina) | [EN](docs/guida_utente.en.md) · [IT](docs/guida_utente.md) |\n| Reference guide (pipeline, tassonomia, RF-03/04) | [EN](docs/reference_guide.en.md) · [IT](docs/reference_guide.md) |\n| Architettura | [EN](docs/architecture.md) · [IT](docs/architecture.it.md) |\n| Design decisions | [EN](docs/design_decisions.md) · [IT](docs/design_decisions.it.md) |\n| Configurazione | [EN](docs/configurazione.en.md) · [IT](docs/configurazione.md) |\n| Developer guide | [EN](docs/developer_guide.en.md) · [IT](docs/developer_guide.md) |\n| Guida alla categorizzazione | [EN](docs/guida_classificazione.en.md) · [IT](docs/guida_classificazione.md) |\n| Schema database | [EN](docs/database.en.md) · [IT](docs/database.md) |\n| Deployment | [EN](docs/deployment.en.md) · [IT](docs/deployment.md) |\n| Processo di rilascio | [EN](docs/release_process.md) · [IT](docs/release_process.it.md) |\n| Loop di build e test desktop | [EN](docs/desktop_build_and_test.md) · [IT](docs/desktop_build_and_test.it.md) |\n| Contribuire | [EN](CONTRIBUTING.en.md) · [IT](CONTRIBUTING.md) |\n| Politica di sicurezza | [EN](SECURITY.md) · [IT](SECURITY.it.md) |\n| Changelog | [EN](CHANGELOG.md) · [IT](CHANGELOG.it.md) |\n\n## Contribuire\n\nSegnalazioni 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.\n\n## Licenza\n\n**PolyForm Noncommercial 1.0.0** — vedi [LICENSE](LICENSE). Uso personale libero; l'uso commerciale richiede una licenza separata.\n\n---\n\n### Cosa lascia la macchina — trasparenza\n\nTutti i dati finanziari sono memorizzati localmente in `~/.spendifai/ledger.db`.\n\n**Backend LLM locale (default — llama.cpp, Ollama)**: nulla lascia la macchina.\n\n**Backend LLM remoto (opt-in — OpenAI, Claude)**: il payload contiene\ndescrizioni sanitizzate insieme a **importi**, **date** e **metadati\ndelle colonne**.\n\n#### Esempio di redazione — categorizer (`core/categorizer.py:303`)\n\nRiga grezza della transazione dal CSV:\n\n```\ndate:        2026-03-15\ndescription: \"BONIFICO da MARIO ROSSI IT60X0542811101000000123456 CAU 12345 STIPENDIO MENSILE\"\namount:      1500.00\n```\n\nCosa l'LLM remoto riceve effettivamente:\n\n```json\n{\n  \"amount\": \"1500.00\",\n  \"description\": \"BONIFICO da Carlo Brambilla \u003cACCOUNT_ID\u003e \u003cTX_CODE\u003e STIPENDIO MENSILE\"\n}\n```\n\nCosa è cambiato:\n- `MARIO ROSSI` (nome titolare configurato) → `Carlo Brambilla` (nome fittizio dal pool italiano, ripristinato dopo la risposta LLM)\n- `IT60X...` (IBAN) → `\u003cACCOUNT_ID\u003e`\n- `CAU 12345` (codice transazione bancaria) → `\u003cTX_CODE\u003e`\n- `amount` e metadati di data: **inviati in chiaro**\n\nIl prompt del categorizer istruisce il modello a \"basare la decisione su\ndescrizione, importo e contesto\" (`prompts/categorizer.json`). L'effettivo\nimpatto dell'importo sull'accuratezza, in pratica, non è stato misurato\ncontro una baseline senza importi: il comportamento predefinito,\nconservativo, lo lascia nel payload finché la misurazione non sarà disponibile.\n\n#### Roadmap\n\nLe modalità di redazione di importi e date per i backend remoti\n(`none` / `buckets` / `strip`) sono in roadmap — elemento di backlog **AI-55**.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrake69%2Fspendif-ai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrake69%2Fspendif-ai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrake69%2Fspendif-ai/lists"}