Metadata-Version: 2.4
Name: decaf-tax
Version: 0.3.5
Summary: De-CAF: Italian tax report generator for foreign investments. Modello Redditi PF — no commercialista needed.
Author-email: Marcello Barnaba <vjt@openssl.it>
License-Expression: MIT
Project-URL: Homepage, https://github.com/vjt/decaf
Project-URL: Repository, https://github.com/vjt/decaf
Project-URL: Issues, https://github.com/vjt/decaf/issues
Project-URL: Changelog, https://github.com/vjt/decaf/blob/master/CHANGELOG.md
Keywords: italian-tax,ivafe,quadro-rw,quadro-rt,quadro-rl,modello-redditi,ibkr,schwab,ecb,forex,fifo
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Natural Language :: Italian
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9
Requires-Dist: aiosqlite>=0.20
Requires-Dist: python-dotenv>=1.0
Requires-Dist: openpyxl>=3.1
Requires-Dist: fpdf2>=2.8
Requires-Dist: rich>=13.0
Requires-Dist: yfinance>=0.2
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: ibkr-flex-client>=0.1
Requires-Dist: ecb-fx-rates>=0.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: pyright>=1.1; extra == "dev"
Requires-Dist: reportlab>=4.0; extra == "dev"
Dynamic: license-file

<p align="center">
  <img src="https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/doc/img/logo.png" alt="decaf logo" width="180">
</p>

# decaf

**De-CAF** — Generatore di report fiscale per investimenti esteri. Niente commercialista.

<p align="center">
  <img src="https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/doc/img/cover.png" alt="Mascetti, Mosconi e Magnotta alle prese con la dichiarazione dei redditi">
</p>

Scarica i dati dai tuoi broker esteri e i tassi BCE, poi calcola tutto il necessario per il **Modello Redditi PF**:

- **Quadro RW** — Monitoraggio attività finanziarie estere + IVAFE
- **Quadro RT** — Plusvalenze di natura finanziaria (26%)
- **Quadro RL** — Redditi di capitale (interessi, dividendi, ritenute estere)
- **Soglia valutaria** — Analisi art. 67(1)(c-ter) TUIR

Output: tabelle colorate nel terminale, Excel (un foglio per quadro), PDF e YAML.

📝 **Articolo di presentazione**: [sindro.me — decaf: Modello Redditi PF su investimenti esteri](https://sindro.me/it/posts/2026-04-18-decaf-dichiarazione-redditi-investimenti-esteri/) ([🇬🇧 EN](https://sindro.me/posts/2026-04-18-decaf-dichiarazione-redditi-investimenti-esteri/)) — perché l'ho scritto, cosa fa in concreto, e il punto sulle plusvalenze valutarie che i broker non ti danno.

📖 **Manuale completo**: [doc/decaf_manual.pdf](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/doc/decaf_manual.pdf) — guida fiscale, normativa con riferimenti alla Gazzetta Ufficiale, architettura, internals per broker, setup Flex Query. Rigenerato ad ogni cambio in `doc/` via pre-commit hook.

🎬 **Guarda un esempio di output** — fixture sintetica `mascetti` (anno 2025, stress test con soglia forex superata, multi-broker, 4 ritenute estere):
[📄 PDF](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/examples/mascetti/decaf_2025.pdf) ·
[📊 Excel](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/examples/mascetti/decaf_2025.xlsx) ·
[📋 YAML](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/examples/mascetti/decaf_2025.yaml)

Altri output in [`examples/`](https://github.com/vjt/decaf/tree/master/examples).

> ⚠️ **Disclaimer.** Questo strumento automatizza i calcoli ma **non sostituisce un commercialista**. Le leggi fiscali cambiano, i tuoi dati e la tua situazione sono tuoi — verifica sempre i numeri prima di firmare il Modello Redditi. Gli autori non si assumono responsabilità per errori, omissioni, o interpretazioni della normativa. Usalo come punto di partenza, non come oracolo.

## Broker Supportati

| Broker | Sorgente dati | Note |
|--------|--------------|------|
| **Interactive Brokers** (Irlanda) | Flex Query API o file XML | Automatico |
| **Charles Schwab** (account EAC/RSU) | 3 file: PDF Year-End Summary + PDF Withholding + JSON transazioni | Manuale da schwab.com |

## Prerequisiti

**Linux (Debian/Ubuntu)**:
```bash
sudo apt install python3 python3-venv poppler-utils git
```

**macOS**:
```bash
brew install python poppler git
```

`poppler-utils` (`pdftotext`) serve al parsing dei PDF Schwab. Windows non testato.

## Installazione

### Opzione 1 — da PyPI (consigliata)

```bash
pip install --user decaf-tax    # pacchetto: decaf-tax · comando: decaf
mkdir ~/decaf
decaf --help
```

Installazione isolata con pipx (alternativa, un venv dedicato al tool):

```bash
pipx install decaf-tax
```

Il comando `decaf` sarà disponibile nel tuo PATH. Tutti i comandi di questo README (`decaf load`, `decaf report`, `decaf backtest`, `decaf manual`) funzionano identici.

### Opzione 2 — dal sorgente (per hackare o leggere il codice)

```bash
git clone https://github.com/vjt/decaf.git
cd decaf
mkdir private                   # qui metterai i tuoi file broker (gitignored)
./decaf.sh --help
```

`./decaf.sh` crea `.venv/` alla prima invocazione e aggiorna le dipendenze automaticamente quando `pyproject.toml` cambia (utile dopo un `git pull`). Le due librerie vendor (`ibkr-flex-client`, `ecb-fx-rates`) sono pubblicate su PyPI, quindi non serve `--recursive` per l'uso normale — vedi la sezione [Sviluppo](#sviluppo) se vuoi modificarle localmente.

## Primo utilizzo

Da qui in poi il comando `decaf` si riferisce sia al binario installato via pip/pipx sia a `./decaf.sh` dal sorgente — funzionano identici. Scegli tu dove tenere i file broker (`~/decaf/` se hai installato via PyPI, `./private/` dal sorgente — dir già gitignored).

### Quickstart solo Schwab (RSU) — 5 minuti

Se hai **solo un conto Schwab con RSU vestate dal datore di lavoro** (caso tipico di dipendenti italiani di Meta, Google, Apple, &co.) non devi toccare IBKR né Flex Query. Ti servono tre file che scarichi dal sito di Schwab:

1. **Transaction history JSON** — Accounts → History → *Export* → formato JSON. Copre tutte le transazioni del periodo di imposta (vest, vendite, bonifici, eventuali dividendi). Se ti servono gli anni precedenti, esporta un periodo più lungo; i caricamenti sono idempotenti.
2. **Year-End Summary PDF** — Statements → Tax Documents → *Year-End Summary* dell'anno di imposta. Contiene le plusvalenze per lotto (Quadro RT). Serve solo se nell'anno hai venduto azioni.
3. **Annual Withholding Statement PDF** — Equity Award Center → Documents → *Annual Withholding Statement* dell'anno. Contiene il FMV per data di vest per giurisdizione ITA/IRL (base per IVAFE, Quadro RW). Serve anche se non hai venduto niente — le RSU ricevute nell'anno vanno comunque dichiarate nel monitoraggio RW.

Mettili in una cartella a tuo piacimento (esempio: `~/decaf`) e lancia:

```bash
pip install decaf-tax   # oppure: pipx install decaf-tax
mkdir -p ~/decaf && cd ~/decaf
# copia qui i tre file scaricati da Schwab

decaf load --broker schwab \
  --file Individual_*_Transactions_*.json \
  --gains-pdfs "Year-End Summary*.PDF" \
  --vest-pdfs "Annual Withholding Statement*.PDF"

decaf report --year 2025 --output-dir ~/decaf
```

Output: `decaf_2025.{yaml,xlsx,pdf}` nella cartella corrente + tabelle colorate nel terminale con totali per quadro, etichette AdE (RW/RT/RL) e riferimenti normativi. Apri l'Excel e copia i numeri nei righi corrispondenti del Modello Redditi PF; il PDF serve come prova documentale ordinabile per eventuali controlli. La [Guida Fiscale](https://github.com/vjt/decaf/blob/master/doc/GUIDA_FISCALE.md) ti dice rigo per rigo dove va ciascun numero.

Nessuna Flex Query, nessuna API key, nessuna configurazione di credenziali. Gli unici segreti (file con il tuo nome, codice fiscale, importi) restano sul tuo disco in `~/decaf/` — decaf non chiama in rete niente oltre al cambio BCE ufficiale e (se hai titoli senza FMV Schwab) Yahoo Finance per il prezzo di fine anno.

### 1. Prepara i file broker

```
~/decaf/
├── flexquery.xml                              # IBKR — esportato da Flex Query
├── Individual_XXX_Transactions_*.json         # Schwab — Accounts → History → Export (JSON)
├── Year-End Summary*.PDF                      # Schwab — Statements → Tax Documents
└── Annual Withholding Statement*.PDF          # Schwab — Equity Award Center → Documents
```

**Prima volta con IBKR?** Devi configurare una Flex Query dal portale Interactive Brokers — serve sia per il download via API sia per esportare l'XML. Guida completa con screenshot: **[doc/QUERY_SETUP.md](https://github.com/vjt/decaf/blob/master/doc/QUERY_SETUP.md)**. Una volta configurata, puoi saltare il file e usare l'API mettendo `IBKR_TOKEN` + `IBKR_QUERY_ID` in `.env` nella directory corrente (gitignored).

Per Schwab i tre file contengono dati diversi e servono tutti:

| File | Cosa contiene |
|------|---------------|
| `Individual_*.json` | Dividendi, ritenute (RL), vendite, bonifici (forex LIFO) |
| `Year-End Summary*.PDF` | Plusvalenze per lotto (RT) |
| `Annual Withholding*.PDF` | FMV al vest per IVAFE (RW) |

### 2. Carica i dati nel DB locale

```bash
cd ~/decaf

# IBKR — da file
decaf load --file flexquery.xml

# IBKR — da API (richiede .env)
decaf load

# Schwab
decaf load --broker schwab \
  --file Individual_*_Transactions_*.json \
  --gains-pdfs "Year-End Summary*.PDF" \
  --vest-pdfs "Annual Withholding Statement*.PDF"
```

I caricamenti sono idempotenti — puoi rieseguirli senza duplicare. Il DB sta in `~/.cache/decaf/`.

### 3. Genera il report

```bash
decaf report --year 2025 --output-dir ~/decaf
```

Produce `decaf_2025.yaml` + `.xlsx` + `.pdf` in `~/decaf/`, e stampa tabelle colorate nel terminale con totali per quadro, etichette AdE, e riferimenti normativi.

## Esempi

[`examples/`](https://github.com/vjt/decaf/tree/master/examples) contiene gli output reali generati su tre fixture sintetiche:

| Fixture | Anni | Copre |
|---------|------|-------|
| [`magnotta/`](https://github.com/vjt/decaf/tree/master/examples/magnotta) | 2024 | IBKR-only, caso base |
| [`mosconi/`](https://github.com/vjt/decaf/tree/master/examples/mosconi) | 2023-2024 | IBKR + Schwab, RSU, stesso ticker a 2 broker |
| [`mascetti/`](https://github.com/vjt/decaf/tree/master/examples/mascetti) | 2024-2025 | Stress — soglia forex, LIFO multi-lotto USD, 4 ritenute diverse |

Ogni sotto-directory contiene `decaf_<year>.{yaml,xlsx,pdf}`. Input corrispondenti in [`tests/reference/`](https://github.com/vjt/decaf/tree/master/tests/reference).

## File di Output

| File                | Formato | Uso                                                         | Esempio |
|---------------------|:-------:|-------------------------------------------------------------|:-------:|
| `decaf_<year>.xlsx` | Excel   | Un foglio per quadro + riepilogo                            | [xlsx](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/examples/mascetti/decaf_2025.xlsx) |
| `decaf_<year>.pdf`  | PDF     | Prospetto con tabelle e totali                              | [pdf](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/examples/mascetti/decaf_2025.pdf) |
| `decaf_<year>.yaml` | YAML    | Dump completo del `TaxReport` — diffabile, stabile tra run  | [yaml](https://cdn.jsdelivr.net/gh/vjt/decaf@v0.3.5/examples/mascetti/decaf_2025.yaml) |

## Come Funziona

1. **Load** — Scarica dati dal broker (API o file) e tassi BCE. Salva tutto in SQLite.
2. **Report** — Carica da SQLite, converte USD→EUR al cambio BCE, calcola:
   - **Soglia valutaria**: ricostruisce il saldo giornaliero in valuta estera, verifica 7+ giorni lavorativi consecutivi sopra €51.645,69
   - **IVAFE**: 0.2% annuo sul valore di mercato dei titoli (pro-rata per giorni), €34.20 fisso per depositi
   - **Plusvalenze titoli**: converte il P/L del broker in EUR al tasso BCE alla data di regolamento
   - **Plusvalenze valutarie**: se soglia superata, calcola i guadagni forex con LIFO per singolo conto sui lotti USD (acquisti da vendite titoli, dividendi, interessi → cessioni tramite conversioni EUR.USD e bonifici)
   - **Redditi di capitale**: abbina interessi lordi con ritenute estere
3. **Output** — Genera i file e il report terminale

## Regole Fiscali Implementate

| Regola | Riferimento | Implementazione |
|--------|------------|-----------------|
| IVAFE titoli | D.L. 201/2011, art. 19 | 0.2% su valore di mercato, pro-rata giorni |
| IVAFE depositi | D.L. 201/2011 | €34.20 fisso annuo |
| Plusvalenze titoli | Art. 67(1)(c-bis) TUIR | 26% imposta sostitutiva |
| Plusvalenze valutarie | Art. 67(1)(c-ter) TUIR + 1-bis + risposta 204/2023 | LIFO per singolo conto su lotti USD, 26% se soglia superata |
| Soglia valutaria | Art. 67(1)(c-ter) TUIR | €51.645,69 per 7+ giorni lavorativi |
| Cambio | D.P.R. 917/1986 | Tassi BCE (cambio ufficiale AdE) |
| Quadro RW | Modello Redditi PF, Sez. II-A | Cod. 20 titoli, Cod. 1 depositi |
| Quadro RT | Modello Redditi PF, righi RT21+ | Sez. II-A, imposta sostitutiva 26% |
| Quadro RL | Modello Redditi PF, rigo RL2 | Sez. I, redditi di capitale esteri |

## Limitazioni note

Le scelte sotto si discostano dalla lettera della norma o lasciano fuori scope alcuni casi. Sono documentate per trasparenza — dettagli completi con citazioni in [doc/NORMATIVA.md](doc/NORMATIVA.md).

| Area | Cosa fa decaf oggi | Cosa richiederebbe la norma |
|------|-------------------|-----------------------------|
| **Obbligazioni e titoli non partecipativi** | Fuori scope. Decaf non e' back-testato su portafogli obbligazionari; applica la stessa logica delle partecipazioni. | Circ. 165/E/1998 §2.3.2 richiede LIFO esplicito sui titoli non partecipativi. Chi detiene obbligazioni estere deve rettificare manualmente. |
| **IVAFE 0,4% black-list** | Applica sempre 0,2%. Nessuna rilevazione automatica della giurisdizione. | L. 213/2023 art. 1 c. 91: 0,4% dal FY2024 per attivita' in Stati a fiscalita' privilegiata (D.M. 04/05/1999). Chi detiene posizioni presso intermediari black-list rettifica manualmente. |
| **RSU trasferite Schwab → IBKR** | Su IBKR decaf usa come costo il `Lot@cost` dell'XML, che eredita il basis fiscale USA (FMV del giorno di vest). IBKR non espone il Valore Normale italiano. | Art. 68 c. 6 + art. 9 c. 4 lett. a) TUIR: il costo fiscalmente riconosciuto e' il Valore Normale (media mensile pre-vest) tassato come reddito di lavoro. Su Schwab decaf lo sostituisce automaticamente via Annual Withholding Statement; su IBKR non c'e' una fonte equivalente — chi trasferisce RSU fuori da EAC deve rettificare manualmente. |

## Bring Your Own Data — Backtesting

Il comando `decaf backtest <dir>` riesegue l'intera pipeline su una directory di file broker e confronta l'output con oracoli YAML committati. Utile per:

- verificare che un cambio di codice non alteri output storici;
- congelare i risultati dell'anno N come regressione per l'anno N+1;
- condividere casi di test senza toccare dati sensibili.

Guida approfondita: [doc/BACKTEST.md](https://github.com/vjt/decaf/blob/master/doc/BACKTEST.md).

### Layout della directory

```
tests/reference/mascetti/
├── ibkr_flex_2024.xml                             # IBKR XML per anno
├── ibkr_flex_2025.xml
├── Individual_XXX066_Transactions_*.json          # Schwab JSON per anno
├── Year-End Summary*.PDF                          # Schwab YES PDF per anno
├── Annual Withholding*.PDF                        # Schwab AWH PDF per anno
├── prices.yaml                                    # opzionale — override prezzi
├── decaf_2024.yaml                                # oracolo per anno
└── decaf_2025.yaml
```

L'anno fiscale di ogni file si ricava dal nome: `ibkr_flex_<year>.xml` per l'XML, le date nei nomi Schwab per JSON/PDF. Gli oracoli sono obbligatori solo per gli anni che vuoi verificare.

### Comandi

```bash
# Rigenera oracoli (uso iniziale o dopo modifiche volute)
./decaf.sh backtest tests/reference/mascetti --update

# Verifica regressione (exit 0 = match, 1 = diff)
./decaf.sh backtest tests/reference/mascetti
```

Il comando:
1. crea un DB SQLite temporaneo in `/tmp/decaf_bt_<pid>.db`;
2. ingestisce tutti i file broker trovati nella directory;
3. calcola il report per ogni anno con oracolo;
4. confronta il dump YAML completo contro l'oracolo (`--update` lo sovrascrive invece).

Exit code: `0` = tutti gli anni matchano, `1` = almeno un anno diverge.

### Override di prezzo (`prices.yaml`)

Pinna i prezzi di fine anno per simboli che yfinance non risolve (ticker sintetici, delistati, esteri) o che vuoi controllare esplicitamente:

```yaml
2024:
  MSCT: 14.00
  SPKZ: 18.00
2025:
  ANTN: 6.00
```

Il dizionario è consultato **due volte** per ogni anno fiscale:
- blocco `<year>` → prezzo a fine anno (IVAFE al 31/12);
- blocco `<year-1>` → prezzo a fine anno precedente (usato come `initial_value` nel calcolo pro-rata IVAFE per titoli portati dall'anno precedente).

Senza override, entrambi i lookup passano a yfinance.

### Fixture sintetiche incluse

| Fixture | Anni | Copertura |
|---------|------|-----------|
| `magnotta/` | 2024 | IBKR singolo, caso base — IVAFE pro-rata, loss RT, dividendo con ritenuta |
| `mosconi/` | 2023-2024 | IBKR + Schwab, vendita parziale titoli multi-lotto (cost basis per lotto dal broker), RSU vest, multi-anno |
| `mascetti/` | 2024-2025 | Stress test — soglia forex superata 2 anni, LIFO multi-lotto USD (forex), RSU multi-anno, dividendi con 4 ritenute diverse (US 30%, UK 0%, DE 26.375%, IT 26%) |

Nomi dei personaggi:
- `mascetti/` — Il Conte Raffaello Mascetti, [personaggio immaginario del film *Amici Miei*](https://it.wikipedia.org/wiki/Amici_miei)
- `mosconi/` — [Germano Mosconi](https://it.wikipedia.org/wiki/Germano_Mosconi), leggendario giornalista veronese
- `magnotta/` — [Mario Magnotta](https://it.wikipedia.org/wiki/Mario_Magnotta), icona internet ante-litteram di L'Aquila

Account IDs contengono `666` per distinguerli visivamente da account reali.

## Sviluppo

```bash
source .venv/bin/activate
scripts/lint.sh     # ruff + pyright
scripts/test.sh     # pytest -x
```

Test suite: holidays, XML parsing, FX service, forex threshold, forex LIFO gains, statement store, Schwab PDF parsing, end-to-end regression su tre fixture sintetiche.

Richiede Python 3.12+. Le dipendenze sono gestite da `./decaf.sh` (primo avvio crea `.venv/` + installa, run successivi aggiornano solo se `pyproject.toml` è cambiato).

Per rigenerare il manuale PDF (`scripts/manual.sh`, lanciato anche dal pre-commit hook quando cambia `doc/`) serve pandoc + xelatex:

```bash
# Linux (Debian/Ubuntu)
sudo apt install pandoc texlive-xetex texlive-latex-recommended texlive-latex-extra

# macOS
brew install pandoc
brew install --cask mactex-no-gui
```

Se vuoi modificare le due librerie vendor (`ibkr-flex-client`, `ecb-fx-rates`), clona con i submodule:

```bash
git submodule update --init --recursive
```

`./decaf.sh` rileva automaticamente `vendor/<dep>/pyproject.toml` e installa quelle versioni in modalità editable, sovrascrivendo le pin PyPI. Fai le tue modifiche in `vendor/<dep>/`, i test di decaf le useranno subito.

I submodule sono via HTTPS. Se hai accesso push e preferisci SSH, scopi la riscrittura alle sole due repo dei submodule:

```bash
git config --global url."git@github.com:vjt/ibkr-flex-client.git".insteadOf "https://github.com/vjt/ibkr-flex-client.git"
git config --global url."git@github.com:vjt/ecb-fx-rates.git".insteadOf "https://github.com/vjt/ecb-fx-rates.git"
```

Nessun altro repo (nemmeno altri di `vjt/`) viene toccato.

### Rilasciare una nuova versione

```bash
# 1. Bump version, pin delle URL jsdelivr al nuovo tag, aggiorna CHANGELOG.
#    Le URL nel README devono puntare a @vX.Y.Z (non @master) così la
#    pagina PyPI serve asset coerenti con la release: jsdelivr cache-a
#    @master 7 giorni → un pin al tag elimina ogni staleness.
NEW=X.Y.Z
sed -i "s|^version = .*|version = \"$NEW\"|" pyproject.toml
sed -i "s|cdn.jsdelivr.net/gh/vjt/decaf@v[0-9.]\+|cdn.jsdelivr.net/gh/vjt/decaf@v$NEW|g" README.md
vim CHANGELOG.md  # sposta [Unreleased] in una sezione [X.Y.Z] — YYYY-MM-DD

# 2. Build wheel + sdist
source .venv/bin/activate
rm -rf dist && python -m build
twine check dist/*

# 3. Upload a PyPI (richiede PYPI_TOKEN in .env con scope account)
set -a && source .env && set +a
TWINE_USERNAME=__token__ TWINE_PASSWORD="$PYPI_TOKEN" twine upload dist/*

# 4. Commit + tag + push
git add pyproject.toml CHANGELOG.md README.md
git commit -m "Release $NEW: <riassunto>"
git tag v$NEW
git push origin master --tags
```

Il pre-commit hook rigenera automaticamente `doc/decaf_manual.pdf` se hai toccato `doc/`, quindi non c'è niente da fare a mano per il manuale.

## Licenza

MIT
