decaf-tax 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. decaf_tax-0.1.0/LICENSE +21 -0
  2. decaf_tax-0.1.0/PKG-INFO +309 -0
  3. decaf_tax-0.1.0/README.md +269 -0
  4. decaf_tax-0.1.0/pyproject.toml +93 -0
  5. decaf_tax-0.1.0/setup.cfg +4 -0
  6. decaf_tax-0.1.0/src/decaf/__init__.py +3 -0
  7. decaf_tax-0.1.0/src/decaf/__main__.py +5 -0
  8. decaf_tax-0.1.0/src/decaf/cli.py +715 -0
  9. decaf_tax-0.1.0/src/decaf/ecb_cache.py +197 -0
  10. decaf_tax-0.1.0/src/decaf/forex.py +247 -0
  11. decaf_tax-0.1.0/src/decaf/forex_gains.py +281 -0
  12. decaf_tax-0.1.0/src/decaf/fx.py +156 -0
  13. decaf_tax-0.1.0/src/decaf/holidays.py +80 -0
  14. decaf_tax-0.1.0/src/decaf/models.py +242 -0
  15. decaf_tax-0.1.0/src/decaf/output_cli.py +323 -0
  16. decaf_tax-0.1.0/src/decaf/output_pdf.py +311 -0
  17. decaf_tax-0.1.0/src/decaf/output_xls.py +248 -0
  18. decaf_tax-0.1.0/src/decaf/output_yaml.py +34 -0
  19. decaf_tax-0.1.0/src/decaf/parse.py +294 -0
  20. decaf_tax-0.1.0/src/decaf/prices.py +108 -0
  21. decaf_tax-0.1.0/src/decaf/quadro_rl.py +73 -0
  22. decaf_tax-0.1.0/src/decaf/quadro_rt.py +91 -0
  23. decaf_tax-0.1.0/src/decaf/quadro_rw.py +412 -0
  24. decaf_tax-0.1.0/src/decaf/schwab_auth.py +256 -0
  25. decaf_tax-0.1.0/src/decaf/schwab_client.py +106 -0
  26. decaf_tax-0.1.0/src/decaf/schwab_gains_pdf.py +143 -0
  27. decaf_tax-0.1.0/src/decaf/schwab_parse.py +507 -0
  28. decaf_tax-0.1.0/src/decaf/schwab_vest_pdf.py +142 -0
  29. decaf_tax-0.1.0/src/decaf/statement_store.py +505 -0
  30. decaf_tax-0.1.0/src/decaf_tax.egg-info/PKG-INFO +309 -0
  31. decaf_tax-0.1.0/src/decaf_tax.egg-info/SOURCES.txt +43 -0
  32. decaf_tax-0.1.0/src/decaf_tax.egg-info/dependency_links.txt +1 -0
  33. decaf_tax-0.1.0/src/decaf_tax.egg-info/entry_points.txt +2 -0
  34. decaf_tax-0.1.0/src/decaf_tax.egg-info/requires.txt +19 -0
  35. decaf_tax-0.1.0/src/decaf_tax.egg-info/top_level.txt +1 -0
  36. decaf_tax-0.1.0/tests/test_architecture.py +410 -0
  37. decaf_tax-0.1.0/tests/test_e2e.py +133 -0
  38. decaf_tax-0.1.0/tests/test_forex.py +215 -0
  39. decaf_tax-0.1.0/tests/test_forex_gains.py +421 -0
  40. decaf_tax-0.1.0/tests/test_fx.py +98 -0
  41. decaf_tax-0.1.0/tests/test_holidays.py +98 -0
  42. decaf_tax-0.1.0/tests/test_parse.py +297 -0
  43. decaf_tax-0.1.0/tests/test_prices.py +66 -0
  44. decaf_tax-0.1.0/tests/test_schwab_parse.py +72 -0
  45. decaf_tax-0.1.0/tests/test_statement_store.py +345 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcello Barnaba
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,309 @@
1
+ Metadata-Version: 2.4
2
+ Name: decaf-tax
3
+ Version: 0.1.0
4
+ Summary: De-CAF: Italian tax report generator for foreign investments. Modello Redditi PF — no commercialista needed.
5
+ Author-email: Marcello Barnaba <vjt@openssl.it>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/vjt/decaf
8
+ Project-URL: Repository, https://github.com/vjt/decaf
9
+ Project-URL: Issues, https://github.com/vjt/decaf/issues
10
+ Project-URL: Changelog, https://github.com/vjt/decaf/blob/master/CHANGELOG.md
11
+ Keywords: italian-tax,ivafe,quadro-rw,quadro-rt,quadro-rl,modello-redditi,ibkr,schwab,ecb,forex,fifo
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
16
+ Classifier: Topic :: Office/Business :: Financial :: Investment
17
+ Classifier: Natural Language :: Italian
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: aiohttp>=3.9
22
+ Requires-Dist: aiosqlite>=0.20
23
+ Requires-Dist: python-dotenv>=1.0
24
+ Requires-Dist: openpyxl>=3.1
25
+ Requires-Dist: fpdf2>=2.8
26
+ Requires-Dist: rich>=13.0
27
+ Requires-Dist: yfinance>=0.2
28
+ Requires-Dist: pydantic>=2.0
29
+ Requires-Dist: pyyaml>=6.0
30
+ Requires-Dist: ibkr-flex-client>=0.1
31
+ Requires-Dist: ecb-fx-rates>=0.1
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8.0; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
35
+ Requires-Dist: pytest-timeout>=2.3; extra == "dev"
36
+ Requires-Dist: ruff>=0.8; extra == "dev"
37
+ Requires-Dist: pyright>=1.1; extra == "dev"
38
+ Requires-Dist: reportlab>=4.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ <p align="center">
42
+ <img src="doc/img/logo.png" alt="decaf logo" width="180">
43
+ </p>
44
+
45
+ # decaf
46
+
47
+ **De-CAF** — Generatore di report fiscale per investimenti esteri. Niente commercialista.
48
+
49
+ <p align="center">
50
+ <img src="doc/img/cover.png" alt="Mascetti, Mosconi e Magnotta alle prese con la dichiarazione dei redditi">
51
+ </p>
52
+
53
+ Scarica i dati dai tuoi broker esteri e i tassi BCE, poi calcola tutto il necessario per il **Modello Redditi PF**:
54
+
55
+ - **Quadro RW** — Monitoraggio attività finanziarie estere + IVAFE
56
+ - **Quadro RT** — Plusvalenze di natura finanziaria (26%)
57
+ - **Quadro RL** — Redditi di capitale (interessi, dividendi, ritenute estere)
58
+ - **Soglia valutaria** — Analisi art. 67(1)(c-ter) TUIR
59
+
60
+ Output: tabelle colorate nel terminale, Excel (un foglio per quadro), PDF e YAML.
61
+
62
+ 📖 **Manuale completo**: [doc/decaf_manual.pdf](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.
63
+
64
+ > ⚠️ **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.
65
+
66
+ ## Broker Supportati
67
+
68
+ | Broker | Sorgente dati | Note |
69
+ |--------|--------------|------|
70
+ | **Interactive Brokers** (Irlanda) | Flex Query API o file XML | Automatico |
71
+ | **Charles Schwab** (account EAC/RSU) | 3 file: PDF Year-End Summary + PDF Withholding + JSON transazioni | Manuale da schwab.com |
72
+
73
+ ## Prerequisiti
74
+
75
+ **Linux (Debian/Ubuntu)**:
76
+ ```bash
77
+ sudo apt install python3 python3-venv poppler-utils git
78
+ ```
79
+
80
+ **macOS**:
81
+ ```bash
82
+ brew install python poppler git
83
+ ```
84
+
85
+ `poppler-utils` (`pdftotext`) serve al parsing dei PDF Schwab. Windows non testato.
86
+
87
+ ## Installazione
88
+
89
+ ```bash
90
+ git clone https://github.com/vjt/decaf.git
91
+ cd decaf
92
+ mkdir private # qui metterai i tuoi file broker (gitignored)
93
+ ```
94
+
95
+ Non serve creare il `venv` a mano: lo script `./decaf.sh` lo crea alla prima invocazione e aggiorna le dipendenze automaticamente quando cambia `pyproject.toml` (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.
96
+
97
+ ## Primo utilizzo
98
+
99
+ ### 1. Metti i file broker in `private/`
100
+
101
+ ```
102
+ private/
103
+ ├── flexquery.xml # IBKR — esportato da Flex Query
104
+ ├── Individual_XXX_Transactions_*.json # Schwab — Accounts → History → Export (JSON)
105
+ ├── Year-End Summary*.PDF # Schwab — Statements → Tax Documents
106
+ └── Annual Withholding Statement*.PDF # Schwab — Equity Award Center → Documents
107
+ ```
108
+
109
+ **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](doc/QUERY_SETUP.md)**. Una volta configurata, puoi saltare il file e usare l'API mettendo `IBKR_TOKEN` + `IBKR_QUERY_ID` in `.env` alla radice del repo (gitignored).
110
+
111
+ Per Schwab i tre file contengono dati diversi e servono tutti:
112
+
113
+ | File | Cosa contiene |
114
+ |------|---------------|
115
+ | `Individual_*.json` | Dividendi, ritenute (RL), vendite, bonifici (forex FIFO) |
116
+ | `Year-End Summary*.PDF` | Plusvalenze per lotto (RT) |
117
+ | `Annual Withholding*.PDF` | FMV al vest per IVAFE (RW) |
118
+
119
+ ### 2. Carica i dati nel DB locale
120
+
121
+ ```bash
122
+ # IBKR — da file
123
+ ./decaf.sh fetch --file private/flexquery.xml
124
+
125
+ # IBKR — da API (richiede .env)
126
+ ./decaf.sh fetch
127
+
128
+ # Schwab
129
+ ./decaf.sh fetch --broker schwab \
130
+ --file private/Individual_*_Transactions_*.json \
131
+ --gains-pdfs "private/Year-End Summary*.PDF" \
132
+ --vest-pdfs "private/Annual Withholding Statement*.PDF"
133
+ ```
134
+
135
+ I caricamenti sono idempotenti — puoi rieseguirli senza duplicare. Il DB sta in `~/.cache/decaf/`.
136
+
137
+ ### 3. Genera il report
138
+
139
+ ```bash
140
+ ./decaf.sh report --year 2025 --output-dir private/
141
+ ```
142
+
143
+ Produce `decaf_2025.yaml` + `.xlsx` + `.pdf` in `private/` (pure `private/` è gitignored), e stampa tabelle colorate nel terminale con totali per quadro, etichette AdE, e riferimenti normativi.
144
+
145
+ ## Esempi
146
+
147
+ [`examples/`](examples/) contiene gli output reali generati su tre fixture sintetiche:
148
+
149
+ | Fixture | Anni | Copre |
150
+ |---------|------|-------|
151
+ | [`magnotta/`](examples/magnotta/) | 2024 | IBKR-only, caso base |
152
+ | [`mosconi/`](examples/mosconi/) | 2023-2024 | IBKR + Schwab, RSU, stesso ticker a 2 broker |
153
+ | [`mascetti/`](examples/mascetti/) | 2024-2025 | Stress — soglia forex, FIFO multi-lotto, 4 ritenute diverse |
154
+
155
+ Ogni sotto-directory contiene `decaf_<year>.{yaml,xlsx,pdf}`. Input corrispondenti in [`tests/reference/`](tests/reference/).
156
+
157
+ ## File di Output
158
+
159
+ | File | Formato | Uso | Esempio |
160
+ |------|---------|-----|---------|
161
+ | `decaf_<year>.xlsx` | Excel | Un foglio per quadro + riepilogo | [mascetti/decaf_2025.xlsx](examples/mascetti/decaf_2025.xlsx) |
162
+ | `decaf_<year>.pdf` | PDF | Prospetto con tabelle e totali | [mascetti/decaf_2025.pdf](examples/mascetti/decaf_2025.pdf) |
163
+ | `decaf_<year>.yaml` | YAML | Dump completo del `TaxReport` — diffabile, stabile tra run | [mascetti/decaf_2025.yaml](examples/mascetti/decaf_2025.yaml) |
164
+
165
+ ## Come Funziona
166
+
167
+ 1. **Fetch** — Scarica dati dal broker (API o file) e tassi BCE. Salva tutto in SQLite.
168
+ 2. **Report** — Carica da SQLite, converte USD→EUR al cambio BCE, calcola:
169
+ - **Soglia valutaria**: ricostruisce il saldo giornaliero in valuta estera, verifica 7+ giorni lavorativi consecutivi sopra €51.645,69
170
+ - **IVAFE**: 0.2% annuo sul valore di mercato dei titoli (pro-rata per giorni), €34.20 fisso per depositi
171
+ - **Plusvalenze titoli**: converte il P/L del broker in EUR al tasso BCE alla data di regolamento
172
+ - **Plusvalenze valutarie**: se soglia superata, calcola i guadagni forex con FIFO sui lotti USD (acquisti da vendite titoli, dividendi, interessi → cessioni tramite conversioni EUR.USD e bonifici)
173
+ - **Redditi di capitale**: abbina interessi lordi con ritenute estere
174
+ 3. **Output** — Genera i file e il report terminale
175
+
176
+ ## Regole Fiscali Implementate
177
+
178
+ | Regola | Riferimento | Implementazione |
179
+ |--------|------------|-----------------|
180
+ | IVAFE titoli | D.L. 201/2011, art. 19 | 0.2% su valore di mercato, pro-rata giorni |
181
+ | IVAFE depositi | D.L. 201/2011 | €34.20 fisso annuo |
182
+ | Plusvalenze titoli | Art. 67(1)(c-bis) TUIR | 26% imposta sostitutiva |
183
+ | Plusvalenze valutarie | Art. 67(1)(c-ter) TUIR | FIFO su lotti USD, 26% se soglia superata |
184
+ | Soglia valutaria | Art. 67(1)(c-ter) TUIR | €51.645,69 per 7+ giorni lavorativi |
185
+ | Cambio | D.P.R. 917/1986 | Tassi BCE (cambio ufficiale AdE) |
186
+ | Quadro RW | Modello Redditi PF, Sez. II-A | Cod. 20 titoli, Cod. 1 depositi |
187
+ | Quadro RT | Modello Redditi PF, righi RT21+ | Sez. II-A, imposta sostitutiva 26% |
188
+ | Quadro RL | Modello Redditi PF, rigo RL2 | Sez. I, redditi di capitale esteri |
189
+
190
+ ## Bring Your Own Data — Backtesting
191
+
192
+ 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:
193
+
194
+ - verificare che un cambio di codice non alteri output storici;
195
+ - congelare i risultati dell'anno N come regressione per l'anno N+1;
196
+ - condividere casi di test senza toccare dati sensibili.
197
+
198
+ Guida approfondita: [doc/BACKTEST.md](doc/BACKTEST.md).
199
+
200
+ ### Layout della directory
201
+
202
+ ```
203
+ tests/reference/mascetti/
204
+ ├── ibkr_flex_2024.xml # IBKR XML per anno
205
+ ├── ibkr_flex_2025.xml
206
+ ├── Individual_XXX066_Transactions_*.json # Schwab JSON per anno
207
+ ├── Year-End Summary*.PDF # Schwab YES PDF per anno
208
+ ├── Annual Withholding*.PDF # Schwab AWH PDF per anno
209
+ ├── prices.yaml # opzionale — override prezzi
210
+ ├── decaf_2024.yaml # oracolo per anno
211
+ └── decaf_2025.yaml
212
+ ```
213
+
214
+ 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.
215
+
216
+ ### Comandi
217
+
218
+ ```bash
219
+ # Rigenera oracoli (uso iniziale o dopo modifiche volute)
220
+ ./decaf.sh backtest tests/reference/mascetti --update
221
+
222
+ # Verifica regressione (exit 0 = match, 1 = diff)
223
+ ./decaf.sh backtest tests/reference/mascetti
224
+ ```
225
+
226
+ Il comando:
227
+ 1. crea un DB SQLite temporaneo in `/tmp/decaf_bt_<pid>.db`;
228
+ 2. ingestisce tutti i file broker trovati nella directory;
229
+ 3. calcola il report per ogni anno con oracolo;
230
+ 4. confronta il dump YAML completo contro l'oracolo (`--update` lo sovrascrive invece).
231
+
232
+ Exit code: `0` = tutti gli anni matchano, `1` = almeno un anno diverge.
233
+
234
+ ### Override di prezzo (`prices.yaml`)
235
+
236
+ Pinna i prezzi di fine anno per simboli che yfinance non risolve (ticker sintetici, delistati, esteri) o che vuoi controllare esplicitamente:
237
+
238
+ ```yaml
239
+ 2024:
240
+ MSCT: 14.00
241
+ SPKZ: 18.00
242
+ 2025:
243
+ ANTN: 6.00
244
+ ```
245
+
246
+ Il dizionario è consultato **due volte** per ogni anno fiscale:
247
+ - blocco `<year>` → prezzo a fine anno (IVAFE al 31/12);
248
+ - blocco `<year-1>` → prezzo a fine anno precedente (usato come `initial_value` nel calcolo pro-rata IVAFE per titoli portati dall'anno precedente).
249
+
250
+ Senza override, entrambi i lookup passano a yfinance.
251
+
252
+ ### Fixture sintetiche incluse
253
+
254
+ | Fixture | Anni | Copertura |
255
+ |---------|------|-----------|
256
+ | `magnotta/` | 2024 | IBKR singolo, caso base — IVAFE pro-rata, loss RT, dividendo con ritenuta |
257
+ | `mosconi/` | 2023-2024 | IBKR + Schwab, FIFO su vendita parziale, RSU vest, multi-anno |
258
+ | `mascetti/` | 2024-2025 | Stress test — soglia forex superata 2 anni, FIFO multi-lotto, RSU multi-anno, dividendi con 4 ritenute diverse (US 30%, UK 0%, DE 26.375%, IT 26%) |
259
+
260
+ Nomi dei personaggi:
261
+ - `mascetti/` — Il Conte Raffaello Mascetti, [personaggio immaginario del film *Amici Miei*](https://it.wikipedia.org/wiki/Amici_miei)
262
+ - `mosconi/` — [Germano Mosconi](https://it.wikipedia.org/wiki/Germano_Mosconi), leggendario giornalista veronese
263
+ - `magnotta/` — [Mario Magnotta](https://it.wikipedia.org/wiki/Mario_Magnotta), icona internet ante-litteram di L'Aquila
264
+
265
+ Account IDs contengono `666` per distinguerli visivamente da account reali.
266
+
267
+ ## Sviluppo
268
+
269
+ ```bash
270
+ source .venv/bin/activate
271
+ scripts/lint.sh # ruff + pyright
272
+ scripts/test.sh # pytest -x
273
+ ```
274
+
275
+ 143 test: holidays, XML parsing, FX service, forex threshold, forex FIFO gains, statement store, Schwab PDF parsing, end-to-end regression su tre fixture sintetiche.
276
+
277
+ Richiede Python 3.12+. Le dipendenze sono gestite da `./decaf.sh` (primo avvio crea `.venv/` + installa, run successivi aggiornano solo se `pyproject.toml` è cambiato).
278
+
279
+ Per rigenerare il manuale PDF (`scripts/manual.sh`, lanciato anche dal pre-commit hook quando cambia `doc/`) serve pandoc + xelatex:
280
+
281
+ ```bash
282
+ # Linux (Debian/Ubuntu)
283
+ sudo apt install pandoc texlive-xetex texlive-latex-recommended texlive-latex-extra
284
+
285
+ # macOS
286
+ brew install pandoc
287
+ brew install --cask mactex-no-gui
288
+ ```
289
+
290
+ Se vuoi modificare le due librerie vendor (`ibkr-flex-client`, `ecb-fx-rates`), clona con i submodule:
291
+
292
+ ```bash
293
+ git submodule update --init --recursive
294
+ ```
295
+
296
+ `./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.
297
+
298
+ I submodule sono via HTTPS. Se hai accesso push e preferisci SSH, scopi la riscrittura alle sole due repo dei submodule:
299
+
300
+ ```bash
301
+ git config --global url."git@github.com:vjt/ibkr-flex-client.git".insteadOf "https://github.com/vjt/ibkr-flex-client.git"
302
+ git config --global url."git@github.com:vjt/ecb-fx-rates.git".insteadOf "https://github.com/vjt/ecb-fx-rates.git"
303
+ ```
304
+
305
+ Nessun altro repo (nemmeno altri di `vjt/`) viene toccato.
306
+
307
+ ## Licenza
308
+
309
+ MIT
@@ -0,0 +1,269 @@
1
+ <p align="center">
2
+ <img src="doc/img/logo.png" alt="decaf logo" width="180">
3
+ </p>
4
+
5
+ # decaf
6
+
7
+ **De-CAF** — Generatore di report fiscale per investimenti esteri. Niente commercialista.
8
+
9
+ <p align="center">
10
+ <img src="doc/img/cover.png" alt="Mascetti, Mosconi e Magnotta alle prese con la dichiarazione dei redditi">
11
+ </p>
12
+
13
+ Scarica i dati dai tuoi broker esteri e i tassi BCE, poi calcola tutto il necessario per il **Modello Redditi PF**:
14
+
15
+ - **Quadro RW** — Monitoraggio attività finanziarie estere + IVAFE
16
+ - **Quadro RT** — Plusvalenze di natura finanziaria (26%)
17
+ - **Quadro RL** — Redditi di capitale (interessi, dividendi, ritenute estere)
18
+ - **Soglia valutaria** — Analisi art. 67(1)(c-ter) TUIR
19
+
20
+ Output: tabelle colorate nel terminale, Excel (un foglio per quadro), PDF e YAML.
21
+
22
+ 📖 **Manuale completo**: [doc/decaf_manual.pdf](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.
23
+
24
+ > ⚠️ **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.
25
+
26
+ ## Broker Supportati
27
+
28
+ | Broker | Sorgente dati | Note |
29
+ |--------|--------------|------|
30
+ | **Interactive Brokers** (Irlanda) | Flex Query API o file XML | Automatico |
31
+ | **Charles Schwab** (account EAC/RSU) | 3 file: PDF Year-End Summary + PDF Withholding + JSON transazioni | Manuale da schwab.com |
32
+
33
+ ## Prerequisiti
34
+
35
+ **Linux (Debian/Ubuntu)**:
36
+ ```bash
37
+ sudo apt install python3 python3-venv poppler-utils git
38
+ ```
39
+
40
+ **macOS**:
41
+ ```bash
42
+ brew install python poppler git
43
+ ```
44
+
45
+ `poppler-utils` (`pdftotext`) serve al parsing dei PDF Schwab. Windows non testato.
46
+
47
+ ## Installazione
48
+
49
+ ```bash
50
+ git clone https://github.com/vjt/decaf.git
51
+ cd decaf
52
+ mkdir private # qui metterai i tuoi file broker (gitignored)
53
+ ```
54
+
55
+ Non serve creare il `venv` a mano: lo script `./decaf.sh` lo crea alla prima invocazione e aggiorna le dipendenze automaticamente quando cambia `pyproject.toml` (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.
56
+
57
+ ## Primo utilizzo
58
+
59
+ ### 1. Metti i file broker in `private/`
60
+
61
+ ```
62
+ private/
63
+ ├── flexquery.xml # IBKR — esportato da Flex Query
64
+ ├── Individual_XXX_Transactions_*.json # Schwab — Accounts → History → Export (JSON)
65
+ ├── Year-End Summary*.PDF # Schwab — Statements → Tax Documents
66
+ └── Annual Withholding Statement*.PDF # Schwab — Equity Award Center → Documents
67
+ ```
68
+
69
+ **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](doc/QUERY_SETUP.md)**. Una volta configurata, puoi saltare il file e usare l'API mettendo `IBKR_TOKEN` + `IBKR_QUERY_ID` in `.env` alla radice del repo (gitignored).
70
+
71
+ Per Schwab i tre file contengono dati diversi e servono tutti:
72
+
73
+ | File | Cosa contiene |
74
+ |------|---------------|
75
+ | `Individual_*.json` | Dividendi, ritenute (RL), vendite, bonifici (forex FIFO) |
76
+ | `Year-End Summary*.PDF` | Plusvalenze per lotto (RT) |
77
+ | `Annual Withholding*.PDF` | FMV al vest per IVAFE (RW) |
78
+
79
+ ### 2. Carica i dati nel DB locale
80
+
81
+ ```bash
82
+ # IBKR — da file
83
+ ./decaf.sh fetch --file private/flexquery.xml
84
+
85
+ # IBKR — da API (richiede .env)
86
+ ./decaf.sh fetch
87
+
88
+ # Schwab
89
+ ./decaf.sh fetch --broker schwab \
90
+ --file private/Individual_*_Transactions_*.json \
91
+ --gains-pdfs "private/Year-End Summary*.PDF" \
92
+ --vest-pdfs "private/Annual Withholding Statement*.PDF"
93
+ ```
94
+
95
+ I caricamenti sono idempotenti — puoi rieseguirli senza duplicare. Il DB sta in `~/.cache/decaf/`.
96
+
97
+ ### 3. Genera il report
98
+
99
+ ```bash
100
+ ./decaf.sh report --year 2025 --output-dir private/
101
+ ```
102
+
103
+ Produce `decaf_2025.yaml` + `.xlsx` + `.pdf` in `private/` (pure `private/` è gitignored), e stampa tabelle colorate nel terminale con totali per quadro, etichette AdE, e riferimenti normativi.
104
+
105
+ ## Esempi
106
+
107
+ [`examples/`](examples/) contiene gli output reali generati su tre fixture sintetiche:
108
+
109
+ | Fixture | Anni | Copre |
110
+ |---------|------|-------|
111
+ | [`magnotta/`](examples/magnotta/) | 2024 | IBKR-only, caso base |
112
+ | [`mosconi/`](examples/mosconi/) | 2023-2024 | IBKR + Schwab, RSU, stesso ticker a 2 broker |
113
+ | [`mascetti/`](examples/mascetti/) | 2024-2025 | Stress — soglia forex, FIFO multi-lotto, 4 ritenute diverse |
114
+
115
+ Ogni sotto-directory contiene `decaf_<year>.{yaml,xlsx,pdf}`. Input corrispondenti in [`tests/reference/`](tests/reference/).
116
+
117
+ ## File di Output
118
+
119
+ | File | Formato | Uso | Esempio |
120
+ |------|---------|-----|---------|
121
+ | `decaf_<year>.xlsx` | Excel | Un foglio per quadro + riepilogo | [mascetti/decaf_2025.xlsx](examples/mascetti/decaf_2025.xlsx) |
122
+ | `decaf_<year>.pdf` | PDF | Prospetto con tabelle e totali | [mascetti/decaf_2025.pdf](examples/mascetti/decaf_2025.pdf) |
123
+ | `decaf_<year>.yaml` | YAML | Dump completo del `TaxReport` — diffabile, stabile tra run | [mascetti/decaf_2025.yaml](examples/mascetti/decaf_2025.yaml) |
124
+
125
+ ## Come Funziona
126
+
127
+ 1. **Fetch** — Scarica dati dal broker (API o file) e tassi BCE. Salva tutto in SQLite.
128
+ 2. **Report** — Carica da SQLite, converte USD→EUR al cambio BCE, calcola:
129
+ - **Soglia valutaria**: ricostruisce il saldo giornaliero in valuta estera, verifica 7+ giorni lavorativi consecutivi sopra €51.645,69
130
+ - **IVAFE**: 0.2% annuo sul valore di mercato dei titoli (pro-rata per giorni), €34.20 fisso per depositi
131
+ - **Plusvalenze titoli**: converte il P/L del broker in EUR al tasso BCE alla data di regolamento
132
+ - **Plusvalenze valutarie**: se soglia superata, calcola i guadagni forex con FIFO sui lotti USD (acquisti da vendite titoli, dividendi, interessi → cessioni tramite conversioni EUR.USD e bonifici)
133
+ - **Redditi di capitale**: abbina interessi lordi con ritenute estere
134
+ 3. **Output** — Genera i file e il report terminale
135
+
136
+ ## Regole Fiscali Implementate
137
+
138
+ | Regola | Riferimento | Implementazione |
139
+ |--------|------------|-----------------|
140
+ | IVAFE titoli | D.L. 201/2011, art. 19 | 0.2% su valore di mercato, pro-rata giorni |
141
+ | IVAFE depositi | D.L. 201/2011 | €34.20 fisso annuo |
142
+ | Plusvalenze titoli | Art. 67(1)(c-bis) TUIR | 26% imposta sostitutiva |
143
+ | Plusvalenze valutarie | Art. 67(1)(c-ter) TUIR | FIFO su lotti USD, 26% se soglia superata |
144
+ | Soglia valutaria | Art. 67(1)(c-ter) TUIR | €51.645,69 per 7+ giorni lavorativi |
145
+ | Cambio | D.P.R. 917/1986 | Tassi BCE (cambio ufficiale AdE) |
146
+ | Quadro RW | Modello Redditi PF, Sez. II-A | Cod. 20 titoli, Cod. 1 depositi |
147
+ | Quadro RT | Modello Redditi PF, righi RT21+ | Sez. II-A, imposta sostitutiva 26% |
148
+ | Quadro RL | Modello Redditi PF, rigo RL2 | Sez. I, redditi di capitale esteri |
149
+
150
+ ## Bring Your Own Data — Backtesting
151
+
152
+ 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:
153
+
154
+ - verificare che un cambio di codice non alteri output storici;
155
+ - congelare i risultati dell'anno N come regressione per l'anno N+1;
156
+ - condividere casi di test senza toccare dati sensibili.
157
+
158
+ Guida approfondita: [doc/BACKTEST.md](doc/BACKTEST.md).
159
+
160
+ ### Layout della directory
161
+
162
+ ```
163
+ tests/reference/mascetti/
164
+ ├── ibkr_flex_2024.xml # IBKR XML per anno
165
+ ├── ibkr_flex_2025.xml
166
+ ├── Individual_XXX066_Transactions_*.json # Schwab JSON per anno
167
+ ├── Year-End Summary*.PDF # Schwab YES PDF per anno
168
+ ├── Annual Withholding*.PDF # Schwab AWH PDF per anno
169
+ ├── prices.yaml # opzionale — override prezzi
170
+ ├── decaf_2024.yaml # oracolo per anno
171
+ └── decaf_2025.yaml
172
+ ```
173
+
174
+ 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.
175
+
176
+ ### Comandi
177
+
178
+ ```bash
179
+ # Rigenera oracoli (uso iniziale o dopo modifiche volute)
180
+ ./decaf.sh backtest tests/reference/mascetti --update
181
+
182
+ # Verifica regressione (exit 0 = match, 1 = diff)
183
+ ./decaf.sh backtest tests/reference/mascetti
184
+ ```
185
+
186
+ Il comando:
187
+ 1. crea un DB SQLite temporaneo in `/tmp/decaf_bt_<pid>.db`;
188
+ 2. ingestisce tutti i file broker trovati nella directory;
189
+ 3. calcola il report per ogni anno con oracolo;
190
+ 4. confronta il dump YAML completo contro l'oracolo (`--update` lo sovrascrive invece).
191
+
192
+ Exit code: `0` = tutti gli anni matchano, `1` = almeno un anno diverge.
193
+
194
+ ### Override di prezzo (`prices.yaml`)
195
+
196
+ Pinna i prezzi di fine anno per simboli che yfinance non risolve (ticker sintetici, delistati, esteri) o che vuoi controllare esplicitamente:
197
+
198
+ ```yaml
199
+ 2024:
200
+ MSCT: 14.00
201
+ SPKZ: 18.00
202
+ 2025:
203
+ ANTN: 6.00
204
+ ```
205
+
206
+ Il dizionario è consultato **due volte** per ogni anno fiscale:
207
+ - blocco `<year>` → prezzo a fine anno (IVAFE al 31/12);
208
+ - blocco `<year-1>` → prezzo a fine anno precedente (usato come `initial_value` nel calcolo pro-rata IVAFE per titoli portati dall'anno precedente).
209
+
210
+ Senza override, entrambi i lookup passano a yfinance.
211
+
212
+ ### Fixture sintetiche incluse
213
+
214
+ | Fixture | Anni | Copertura |
215
+ |---------|------|-----------|
216
+ | `magnotta/` | 2024 | IBKR singolo, caso base — IVAFE pro-rata, loss RT, dividendo con ritenuta |
217
+ | `mosconi/` | 2023-2024 | IBKR + Schwab, FIFO su vendita parziale, RSU vest, multi-anno |
218
+ | `mascetti/` | 2024-2025 | Stress test — soglia forex superata 2 anni, FIFO multi-lotto, RSU multi-anno, dividendi con 4 ritenute diverse (US 30%, UK 0%, DE 26.375%, IT 26%) |
219
+
220
+ Nomi dei personaggi:
221
+ - `mascetti/` — Il Conte Raffaello Mascetti, [personaggio immaginario del film *Amici Miei*](https://it.wikipedia.org/wiki/Amici_miei)
222
+ - `mosconi/` — [Germano Mosconi](https://it.wikipedia.org/wiki/Germano_Mosconi), leggendario giornalista veronese
223
+ - `magnotta/` — [Mario Magnotta](https://it.wikipedia.org/wiki/Mario_Magnotta), icona internet ante-litteram di L'Aquila
224
+
225
+ Account IDs contengono `666` per distinguerli visivamente da account reali.
226
+
227
+ ## Sviluppo
228
+
229
+ ```bash
230
+ source .venv/bin/activate
231
+ scripts/lint.sh # ruff + pyright
232
+ scripts/test.sh # pytest -x
233
+ ```
234
+
235
+ 143 test: holidays, XML parsing, FX service, forex threshold, forex FIFO gains, statement store, Schwab PDF parsing, end-to-end regression su tre fixture sintetiche.
236
+
237
+ Richiede Python 3.12+. Le dipendenze sono gestite da `./decaf.sh` (primo avvio crea `.venv/` + installa, run successivi aggiornano solo se `pyproject.toml` è cambiato).
238
+
239
+ Per rigenerare il manuale PDF (`scripts/manual.sh`, lanciato anche dal pre-commit hook quando cambia `doc/`) serve pandoc + xelatex:
240
+
241
+ ```bash
242
+ # Linux (Debian/Ubuntu)
243
+ sudo apt install pandoc texlive-xetex texlive-latex-recommended texlive-latex-extra
244
+
245
+ # macOS
246
+ brew install pandoc
247
+ brew install --cask mactex-no-gui
248
+ ```
249
+
250
+ Se vuoi modificare le due librerie vendor (`ibkr-flex-client`, `ecb-fx-rates`), clona con i submodule:
251
+
252
+ ```bash
253
+ git submodule update --init --recursive
254
+ ```
255
+
256
+ `./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.
257
+
258
+ I submodule sono via HTTPS. Se hai accesso push e preferisci SSH, scopi la riscrittura alle sole due repo dei submodule:
259
+
260
+ ```bash
261
+ git config --global url."git@github.com:vjt/ibkr-flex-client.git".insteadOf "https://github.com/vjt/ibkr-flex-client.git"
262
+ git config --global url."git@github.com:vjt/ecb-fx-rates.git".insteadOf "https://github.com/vjt/ecb-fx-rates.git"
263
+ ```
264
+
265
+ Nessun altro repo (nemmeno altri di `vjt/`) viene toccato.
266
+
267
+ ## Licenza
268
+
269
+ MIT