@gabrielerandelli/minus-tracker 0.5.3 → 0.5.5

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.
package/README.md CHANGED
@@ -2,44 +2,48 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
4
4
 
5
- > **Status:** v0.5.3 — published on npm as `@gabrielerandelli/minus-tracker`.
5
+ > **Status:** v0.5.5 — published on npm as `@gabrielerandelli/minus-tracker`.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Italiano
10
10
 
11
- Strumento open-source TypeScript (libreria + CLI) per il calcolo automatico di
11
+ minus-tracker è un progetto open-source sviluppato principalmente in TypeScript per il calcolo automatico di
12
12
  **plusvalenze e minusvalenze** nel _Regime Dichiarativo_ — abbinamento lotti LIFO/FIFO,
13
- normalizzazione multi-valuta con tassi BCE storici, parsing del CSV DEGIRO.
13
+ normalizzazione multi-valuta con tassi BCE storici.
14
+
15
+ Carica dati seguendo il formato CSV adottato da DEGIRO.
14
16
 
15
17
  ### Cosa fa
16
18
 
17
19
  - Calcolo plusvalenze/minusvalenze con abbinamento lotti **LIFO e FIFO** (configurabile)
18
- - **Parser CSV DEGIRO** (estensibile ad altri broker via PR della community)
20
+ - **Parser CSV DEGIRO**
19
21
  - Gestione **multi-valuta** con tassi BCE storici (EUR, USD, GBP, CHF)
20
- - Terminologia italiana: _plusvalenze_, _minusvalenze_, _Regime Dichiarativo_
21
- - Suite di test basata su **FAQ Agenzia Entrate** (baseline di correttezza fiscale)
22
- - Uscita in **italiano** (predefinita) o **inglese** (`--lang en`)
23
- - Libreria npm + CLI
22
+ - Suite di test basata su **FAQ Agenzia Entrate**
23
+ - L'ouput può essere restituito in **italiano** (predefinito) o **inglese** (`--lang en`)
24
+ - minus-tracker è un package NPM con supporto CLI
24
25
 
25
- ### Utilizzo rapido
26
+ ### Installazione CLI
26
27
 
27
- ```ts
28
- import { DEGIROParser, Calculator } from "@gabrielerandelli/minus-tracker";
28
+ **Installazione globale** (comando disponibile da qualsiasi directory):
29
29
 
30
- const transactions = new DEGIROParser().parse(csv);
31
- const report = new Calculator(transactions).calculateGains("LIFO"); // o "FIFO"
30
+ ```bash
31
+ npm install -g @gabrielerandelli/minus-tracker
32
32
  ```
33
33
 
34
- minus-tracker è **solo calcolo fiscale** nessuna UI, autenticazione, database o PDF.
34
+ **Senza installazione globale** (nessuna configurazione necessaria):
35
+
36
+ ```bash
37
+ npx @gabrielerandelli/minus-tracker calc trades.csv
38
+ ```
35
39
 
36
- ### CLI
40
+ ### Utilizzo CLI
37
41
 
38
42
  ```bash
39
- # Calcola plusvalenze/minusvalenze (uscita in italiano, predefinita)
43
+ # Calcola plusvalenze/minusvalenze (output in italiano)
40
44
  minus-tracker calc trades.csv
41
45
 
42
- # Uscita in inglese
46
+ # Output in inglese
43
47
  minus-tracker calc --lang en trades.csv
44
48
 
45
49
  # Imposta la lingua in modo permanente
@@ -53,14 +57,25 @@ minus-tracker rates --check
53
57
  minus-tracker rates --update
54
58
  ```
55
59
 
56
- ### Installazione
57
-
58
- **Prerequisiti:** Node.js ≥ 24 ([nodejs.org](https://nodejs.org))
60
+ ### Installazione libreria
59
61
 
60
62
  ```bash
61
63
  npm install @gabrielerandelli/minus-tracker
62
64
  ```
63
65
 
66
+ ### Utilizzo libreria
67
+
68
+ ```ts
69
+ import { DEGIROParser, Calculator } from "@gabrielerandelli/minus-tracker";
70
+
71
+ const transactions = new DEGIROParser().parse(csv);
72
+ const report = new Calculator(transactions).calculateGains("LIFO"); // o "FIFO"
73
+ ```
74
+
75
+ minus-tracker è **solo calcolo fiscale** — nessuna UI, autenticazione, database o PDF.
76
+
77
+ **Prerequisiti:** Node.js ≥ 24 ([nodejs.org](https://nodejs.org))
78
+
64
79
  ### Avvertenza
65
80
 
66
81
  minus-tracker è un **ausilio al calcolo, non consulenza fiscale**. I risultati sono
@@ -70,31 +85,36 @@ destinati alla revisione e alla consegna a un commercialista qualificato.
70
85
 
71
86
  ## English
72
87
 
73
- Open-source TypeScript library + CLI that automates **Italian capital-gains/loss
74
- calculation** (_Regime Dichiarativo_) — LIFO/FIFO lot matching, multi-currency
75
- normalisation (historical ECB rates), and DEGIRO CSV parsing.
88
+ minus-tracker is an open-source project developed primarily in TypeScript for the automatic
89
+ calculation of **capital gains and losses** in the _Regime Dichiarativo_ — LIFO/FIFO lot
90
+ matching, multi-currency normalisation with historical ECB rates.
91
+
92
+ It loads data following the CSV format used by DEGIRO.
76
93
 
77
94
  ### What it does
78
95
 
79
96
  - Capital-gains/loss calculation with configurable **LIFO and FIFO** lot matching
80
- - **DEGIRO CSV parser**, extensible to other brokers via community PRs
97
+ - **DEGIRO CSV parser**
81
98
  - **Multi-currency** handling with historical ECB rates (EUR, USD, GBP, CHF)
82
- - Test suite based on **Agenzia Entrate FAQ** as the correctness baseline
99
+ - Test suite based on **Agenzia Entrate FAQ**
83
100
  - Output in **Italian** (default) or **English** (`--lang en`)
84
- - npm library + CLI
101
+ - minus-tracker is an NPM package with CLI support
85
102
 
86
- ### Quick start
103
+ ### CLI Installation
87
104
 
88
- ```ts
89
- import { DEGIROParser, Calculator } from "@gabrielerandelli/minus-tracker";
105
+ **Global install** (command available system-wide):
90
106
 
91
- const transactions = new DEGIROParser().parse(csv);
92
- const report = new Calculator(transactions).calculateGains("LIFO"); // or "FIFO"
107
+ ```bash
108
+ npm install -g @gabrielerandelli/minus-tracker
93
109
  ```
94
110
 
95
- minus-tracker is **pure tax math** no UI, auth, database, or PDF.
111
+ **Without global install** (no setup required):
112
+
113
+ ```bash
114
+ npx @gabrielerandelli/minus-tracker calc trades.csv
115
+ ```
96
116
 
97
- ### CLI
117
+ ### CLI Usage
98
118
 
99
119
  ```bash
100
120
  # Calculate gains/losses (Italian output, default)
@@ -114,18 +134,29 @@ minus-tracker rates --check
114
134
  minus-tracker rates --update
115
135
  ```
116
136
 
117
- ### Installation
118
-
119
- **Prerequisites:** Node.js ≥ 24 ([nodejs.org](https://nodejs.org))
137
+ ### Library Installation
120
138
 
121
139
  ```bash
122
140
  npm install @gabrielerandelli/minus-tracker
123
141
  ```
124
142
 
143
+ ### Library Usage
144
+
145
+ ```ts
146
+ import { DEGIROParser, Calculator } from "@gabrielerandelli/minus-tracker";
147
+
148
+ const transactions = new DEGIROParser().parse(csv);
149
+ const report = new Calculator(transactions).calculateGains("LIFO"); // or "FIFO"
150
+ ```
151
+
152
+ minus-tracker is **pure tax math** — no UI, auth, database, or PDF.
153
+
154
+ **Prerequisites:** Node.js ≥ 24 ([nodejs.org](https://nodejs.org))
155
+
125
156
  ### Disclaimer
126
157
 
127
- A **calculation aid, not tax advice**. Outputs are intended for review and handoff to a
128
- qualified commercialista (_Italian tax professional_).
158
+ minus-tracker is a **calculation aid, not tax advice**. Outputs are intended for review
159
+ and handoff to a qualified commercialista.
129
160
 
130
161
  ---
131
162
 
package/dist/cli/index.js CHANGED
@@ -169,12 +169,18 @@ import * as fs3 from "fs";
169
169
  import { fileURLToPath } from "url";
170
170
  import * as path2 from "path";
171
171
  import * as fs2 from "fs";
172
- var _bundled = null;
172
+ var _bundled = void 0;
173
173
  function getBundledSnapshot() {
174
- if (_bundled) return _bundled;
174
+ if (_bundled !== void 0) return _bundled;
175
175
  const __dirname = path2.dirname(fileURLToPath(import.meta.url));
176
176
  const bundledPath = path2.join(__dirname, "../data/ecb-rates.json");
177
- _bundled = JSON.parse(fs2.readFileSync(bundledPath, "utf8"));
177
+ try {
178
+ _bundled = JSON.parse(
179
+ fs2.readFileSync(bundledPath, "utf8")
180
+ );
181
+ } catch {
182
+ _bundled = null;
183
+ }
178
184
  return _bundled;
179
185
  }
180
186
  function getUserSnapshot() {
@@ -195,6 +201,12 @@ function getUserSnapshot() {
195
201
  function getActiveSnapshot() {
196
202
  const bundled = getBundledSnapshot();
197
203
  const user = getUserSnapshot();
204
+ if (!bundled && !user) {
205
+ throw new Error(
206
+ "No ECB rates snapshot available. Run `minus-tracker rates --update` to fetch rates."
207
+ );
208
+ }
209
+ if (!bundled) return user;
198
210
  if (!user) return bundled;
199
211
  const merged = {};
200
212
  for (const ccy of /* @__PURE__ */ new Set([...Object.keys(bundled), ...Object.keys(user)])) {
@@ -709,11 +721,12 @@ async function fetchEcbData(currency) {
709
721
  const rates = {};
710
722
  const lines = data.split("\n");
711
723
  for (const line of lines) {
712
- const dateMatch = line.match(
713
- /^(\d{4}-\d{2}-\d{2}),.*?,.*?,.*?,.*?,([\d.]+)/
714
- );
715
- if (dateMatch) {
716
- rates[dateMatch[1]] = parseFloat(dateMatch[2]);
724
+ const parts = line.split(",");
725
+ if (parts.length < 8) continue;
726
+ const date = parts[6].trim();
727
+ const rate = parseFloat(parts[7].trim());
728
+ if (/^\d{4}-\d{2}-\d{2}$/.test(date) && !isNaN(rate) && rate > 0) {
729
+ rates[date] = rate;
717
730
  }
718
731
  }
719
732
  resolve(rates);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts","../../src/i18n/it.ts","../../src/i18n/en.ts","../../src/i18n/settings.ts","../../src/i18n/index.ts","../../src/errors.ts","../../src/cli/commands/calc.ts","../../src/rates/index.ts","../../src/parser/warnings.ts","../../src/parser/index.ts","../../src/calculator/index.ts","../../src/cli/renderer.ts","../../src/cli/commands/validate.ts","../../src/cli/commands/rates.ts","../../src/cli/commands/config.ts"],"sourcesContent":["import { parseArgs } from \"node:util\";\nimport { resolveLocale, getStrings } from \"../i18n/index.js\";\nimport { ParseError, CalculationError } from \"../errors.js\";\nimport { runCalc } from \"./commands/calc.js\";\nimport { runValidate } from \"./commands/validate.js\";\nimport { runRates } from \"./commands/rates.js\";\nimport { runConfig } from \"./commands/config.js\";\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n args: process.argv.slice(2),\n options: {\n lang: { type: \"string\" },\n method: { type: \"string\" },\n year: { type: \"string\" },\n json: { type: \"boolean\", default: false },\n check: { type: \"boolean\", default: false },\n update: { type: \"boolean\", default: false },\n show: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n strict: false,\n });\n\n // Resolve locale once — exits 2 on invalid value\n const locale = resolveLocale(values.lang as string | undefined);\n const s = getStrings(locale);\n\n const command = positionals[0];\n const restPositionals = positionals.slice(1);\n const flags = values as Record<string, string | boolean>;\n\n const stdout = process.stdout;\n const stderr = process.stderr;\n\n let exitCode = 0;\n\n try {\n switch (command) {\n case \"calc\":\n exitCode = await runCalc(restPositionals, flags, s, stdout, stderr);\n break;\n case \"validate\":\n exitCode = await runValidate(restPositionals, flags, s, stdout, stderr);\n break;\n case \"rates\":\n exitCode = await runRates(restPositionals, flags, s, stdout, stderr);\n break;\n case \"config\":\n exitCode = await runConfig(restPositionals, flags, s, stdout, stderr);\n break;\n default:\n stderr.write(\n \"Usage: minus-tracker <calc|validate|rates|config> [options] [file]\\n\",\n );\n exitCode = 2;\n }\n } catch (err) {\n if (err instanceof ParseError) {\n if (err.code === \"INVALID_CSV\") {\n stderr.write(s.errorInvalidCsv + \"\\n\");\n } else {\n stderr.write(s.errorMissingColumn(err.columnName!) + \"\\n\");\n }\n exitCode = 1;\n } else if (err instanceof CalculationError) {\n stderr.write(s.errorNoOpenLots(err.isin, err.date) + \"\\n\");\n exitCode = 1;\n } else {\n throw err;\n }\n }\n\n process.exit(exitCode);\n}\n\nmain().catch((err) => {\n process.stderr.write(`Unhandled error: ${String(err)}\\n`);\n process.exit(1);\n});\n","import type { LocaleStrings } from \"./types.js\";\n\nexport const it: LocaleStrings = {\n numberLocale: \"it-IT\",\n\n errorInvalidCsv: \"CSV non valido: impossibile analizzare il file\",\n errorMissingColumn: (col) => `Colonna obbligatoria mancante: ${col}`,\n errorNoOpenLots: (isin, date) =>\n `Nessun lotto aperto per ISIN ${isin} in data ${date}`,\n\n warnMissingIsin: (row) => `Riga ${row}: ISIN mancante — riga ignorata`,\n warnUnsupportedCurrency: (row, currency) =>\n `Riga ${row}: valuta non supportata ${currency} — riga ignorata`,\n warnNoEcbRate: (row, currency, date) =>\n `Riga ${row}: nessun tasso BCE per ${currency} in data ${date} — riga ignorata`,\n warnQuantityZero: (row) => `Riga ${row}: quantità pari a 0 — riga ignorata`,\n\n warnMultipleYears:\n \"Il CSV contiene transazioni di più anni — filtra per un singolo anno per un calcolo accurato.\",\n\n headerMethod: \"METODO\",\n headerTaxYear: \"ANNO FISCALE\",\n headerIsin: \"ISIN\",\n headerProduct: \"TITOLO\",\n headerQty: \"QTÀ\",\n headerBuyDate: \"DATA ACQUISTO\",\n headerSellDate: \"DATA VENDITA\",\n headerBuyEur: \"ACQUISTO EUR\",\n headerSellEur: \"VENDITA EUR\",\n headerGainLoss: \"GUADAGNO/PERDITA\",\n\n summaryPlusvalenze: \"PLUSVALENZE\",\n summaryMinusvalenze: \"MINUSVALENZE\",\n summaryNetResult: \"RISULTATO NETTO\",\n summaryWarnings: \"AVVERTENZE\",\n summaryGenerated: \"Generato\",\n\n validateOk: (count, errors) =>\n `OK: ${count} transazioni analizzate, ${errors} errori gravi`,\n validateWarn: (count, reason) =>\n `AVVISO: ${count} righe ignorate (${reason})`,\n\n ratesCoverage: (start, end, currencies) =>\n `Copertura: ${start} → ${end} | Valute: ${currencies}`,\n ratesGapsNone: \"Lacune: nessuna\",\n ratesGaps: (list) => `Lacune: ${list}`,\n ratesUpdateFetching: \"Recupero dati BCE SDMX in corso...\",\n ratesUpdateDone: (n) => `Completato. Aggiunte ${n} nuove date.`,\n ratesSnapshotWritten: (path) => `Snapshot scritto in ${path}`,\n\n configLangSet: (lang) => `Lingua impostata su: ${lang}`,\n configCurrentLang: (lang) => `Lingua corrente: ${lang}`,\n\n disclaimer: \"minus-tracker è un ausilio al calcolo, non consulenza fiscale.\",\n};\n","import type { LocaleStrings } from \"./types.js\";\n\nexport const en: LocaleStrings = {\n numberLocale: \"en-US\",\n\n errorInvalidCsv: \"Invalid CSV: unable to parse\",\n errorMissingColumn: (col) => `Missing required column: ${col}`,\n errorNoOpenLots: (isin, date) => `No open lots for ISIN ${isin} on ${date}`,\n\n warnMissingIsin: (row) => `Row ${row}: missing ISIN — skipped`,\n warnUnsupportedCurrency: (row, currency) =>\n `Row ${row}: unsupported currency ${currency} — skipped`,\n warnNoEcbRate: (row, currency, date) =>\n `Row ${row}: no ECB rate for ${currency} on ${date} — skipped`,\n warnQuantityZero: (row) => `Row ${row}: quantity is 0 — skipped`,\n\n warnMultipleYears:\n \"CSV contains transactions from multiple years — filter to a single year for accurate reporting.\",\n\n headerMethod: \"METHOD\",\n headerTaxYear: \"TAX YEAR\",\n headerIsin: \"ISIN\",\n headerProduct: \"PRODUCT\",\n headerQty: \"QTY\",\n headerBuyDate: \"BUY DATE\",\n headerSellDate: \"SELL DATE\",\n headerBuyEur: \"BUY EUR\",\n headerSellEur: \"SELL EUR\",\n headerGainLoss: \"GAIN/LOSS\",\n\n summaryPlusvalenze: \"PLUSVALENZE\",\n summaryMinusvalenze: \"MINUSVALENZE\",\n summaryNetResult: \"NET RESULT\",\n summaryWarnings: \"WARNINGS\",\n summaryGenerated: \"Generated\",\n\n validateOk: (count, errors) =>\n `OK: ${count} transactions parsed, ${errors} hard errors`,\n validateWarn: (count, reason) => `WARN: ${count} rows skipped (${reason})`,\n\n ratesCoverage: (start, end, currencies) =>\n `Coverage: ${start} → ${end} | Currencies: ${currencies}`,\n ratesGapsNone: \"Gaps: none\",\n ratesGaps: (list) => `Gaps: ${list}`,\n ratesUpdateFetching: \"Fetching ECB SDMX...\",\n ratesUpdateDone: (n) => `Done. Added ${n} new dates.`,\n ratesSnapshotWritten: (path) => `Snapshot written to ${path}`,\n\n configLangSet: (lang) => `Language set to: ${lang}`,\n configCurrentLang: (lang) => `Current language: ${lang}`,\n\n disclaimer: \"minus-tracker è un ausilio al calcolo, non consulenza fiscale.\",\n};\n","import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { MinusTrackerConfig, SupportedLocale } from \"./types.js\";\n\nconst SUPPORTED_LOCALES: SupportedLocale[] = [\"it\", \"en\"];\n\nexport function getConfigPath(): string {\n if (process.platform === \"win32\") {\n const appData =\n process.env.APPDATA ?? path.join(os.homedir(), \"AppData\", \"Roaming\");\n return path.join(appData, \"minus-tracker\", \"config.json\");\n }\n const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), \".config\");\n return path.join(xdg, \"minus-tracker\", \"config.json\");\n}\n\nexport function readConfig(): MinusTrackerConfig {\n try {\n const raw = fs.readFileSync(getConfigPath(), \"utf8\");\n return JSON.parse(raw) as MinusTrackerConfig;\n } catch {\n return {};\n }\n}\n\nexport function saveLocale(lang: SupportedLocale): void {\n const configPath = getConfigPath();\n fs.mkdirSync(path.dirname(configPath), { recursive: true });\n const existing = readConfig();\n const updated: MinusTrackerConfig = { ...existing, locale: lang };\n fs.writeFileSync(configPath, JSON.stringify(updated, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport function resolveLocale(cliLang?: string): SupportedLocale {\n // 1. --lang flag\n if (cliLang !== undefined) {\n if (!SUPPORTED_LOCALES.includes(cliLang as SupportedLocale)) {\n process.stderr.write(`Unsupported locale \"${cliLang}\". Use: it, en\\n`);\n process.exit(2);\n }\n return cliLang as SupportedLocale;\n }\n // 2. env var\n const envLang = process.env.MINUS_TRACKER_LANG;\n if (envLang !== undefined) {\n if (!SUPPORTED_LOCALES.includes(envLang as SupportedLocale)) {\n process.stderr.write(`Unsupported locale \"${envLang}\". Use: it, en\\n`);\n process.exit(2);\n }\n return envLang as SupportedLocale;\n }\n // 3. config file\n const config = readConfig();\n if (config.locale && SUPPORTED_LOCALES.includes(config.locale)) {\n return config.locale;\n }\n // 4. default\n return \"it\";\n}\n","import { it } from \"./it.js\";\nimport { en } from \"./en.js\";\nimport type { LocaleStrings, SupportedLocale } from \"./types.js\";\n\nexport { it, en };\nexport type { LocaleStrings, SupportedLocale };\nexport type { MinusTrackerConfig } from \"./types.js\";\nexport {\n resolveLocale,\n saveLocale,\n readConfig,\n getConfigPath,\n} from \"./settings.js\";\n\nexport function getStrings(locale: SupportedLocale): LocaleStrings {\n return locale === \"it\" ? it : en;\n}\n","export class ParseError extends Error {\n readonly code: \"INVALID_CSV\" | \"MISSING_COLUMN\";\n readonly columnName?: string;\n\n constructor(code: \"INVALID_CSV\");\n constructor(code: \"MISSING_COLUMN\", columnName: string);\n constructor(code: ParseError[\"code\"], columnName?: string) {\n const msg =\n code === \"INVALID_CSV\"\n ? \"Invalid CSV: unable to parse\"\n : `Missing required column: ${columnName}`;\n super(msg);\n this.name = \"ParseError\";\n this.code = code;\n this.columnName = columnName;\n }\n}\n\nexport class CalculationError extends Error {\n readonly isin: string;\n readonly date: string;\n\n constructor(isin: string, date: string) {\n super(`No open lots for ISIN ${isin} on ${date}`);\n this.name = \"CalculationError\";\n this.isin = isin;\n this.date = date;\n }\n}\n","import * as fs from \"node:fs\";\nimport { DEGIROParser } from \"../../parser/index.js\";\nimport { Calculator } from \"../../calculator/index.js\";\nimport { ParseError } from \"../../errors.js\";\nimport { renderReport } from \"../renderer.js\";\nimport type { LocaleStrings } from \"../../i18n/types.js\";\nimport type { LotMethod } from \"../../types.js\";\n\nexport async function runCalc(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n const filePath = positional[0];\n if (!filePath) {\n stderr.write(\n \"Usage: minus-tracker calc [--method LIFO|FIFO] [--json] <file.csv>\\n\",\n );\n return 2;\n }\n\n let csv: string;\n try {\n csv = fs.readFileSync(filePath, \"utf8\");\n } catch {\n stderr.write(`Cannot read file: ${filePath}\\n`);\n return 1;\n }\n\n const method = (flags[\"method\"] as LotMethod) ?? \"LIFO\";\n if (method !== \"LIFO\" && method !== \"FIFO\") {\n stderr.write(\"--method must be LIFO or FIFO\\n\");\n return 2;\n }\n\n const parser = new DEGIROParser();\n let transactions;\n try {\n transactions = parser.parse(csv);\n } catch (err) {\n if (err instanceof ParseError) {\n if (err.code === \"INVALID_CSV\") {\n stderr.write(s.errorInvalidCsv + \"\\n\");\n } else {\n stderr.write(s.errorMissingColumn(err.columnName!) + \"\\n\");\n }\n return 1;\n }\n throw err;\n }\n\n const calculator = new Calculator(transactions, parser.warnings);\n const report = calculator.calculateGains(method);\n\n if (flags[\"json\"]) {\n stdout.write(JSON.stringify(report, null, 2) + \"\\n\");\n } else {\n stdout.write(renderReport(report, s) + \"\\n\");\n }\n return 0;\n}\n","import { fileURLToPath } from \"node:url\";\nimport * as path from \"node:path\";\nimport * as fs from \"node:fs\";\n\nexport type RatesSnapshot = Record<string, Record<string, number>>;\n\n// Load the bundled snapshot lazily\nlet _bundled: RatesSnapshot | null = null;\n\nfunction getBundledSnapshot(): RatesSnapshot {\n if (_bundled) return _bundled;\n // Use import.meta.url-relative path to locate the bundled JSON\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const bundledPath = path.join(__dirname, \"../data/ecb-rates.json\");\n _bundled = JSON.parse(fs.readFileSync(bundledPath, \"utf8\")) as RatesSnapshot;\n return _bundled;\n}\n\nfunction getUserSnapshot(): RatesSnapshot | null {\n const platform = process.platform;\n let configDir: string;\n if (platform === \"win32\") {\n configDir =\n process.env.APPDATA ??\n path.join(process.env.USERPROFILE ?? \"~\", \"AppData\", \"Roaming\");\n } else {\n configDir =\n process.env.XDG_CONFIG_HOME ??\n path.join(process.env.HOME ?? \"~\", \".config\");\n }\n const userPath = path.join(configDir, \"minus-tracker\", \"ecb-rates.json\");\n try {\n return JSON.parse(fs.readFileSync(userPath, \"utf8\")) as RatesSnapshot;\n } catch {\n return null;\n }\n}\n\nexport function getActiveSnapshot(): RatesSnapshot {\n const bundled = getBundledSnapshot();\n const user = getUserSnapshot();\n if (!user) return bundled;\n // Merge: user entries override bundled for same date keys\n const merged: RatesSnapshot = {};\n for (const ccy of new Set([...Object.keys(bundled), ...Object.keys(user)])) {\n merged[ccy] = { ...(bundled[ccy] ?? {}), ...(user[ccy] ?? {}) };\n }\n return merged;\n}\n\nfunction subtractDays(isoDate: string, days: number): string {\n const d = new Date(isoDate + \"T00:00:00Z\");\n d.setUTCDate(d.getUTCDate() - days);\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Look up ECB rate for a currency on a given date.\n * Returns 1.0 for EUR. Walks back up to 3 calendar days for weekend/holiday gaps.\n * Returns null if not found within the window.\n *\n * @param currency ISO 4217 currency code\n * @param date YYYY-MM-DD trade date\n * @param snapshot Optional override (used in tests to inject stub data)\n */\nexport function lookupRate(\n currency: string,\n date: string,\n snapshot?: RatesSnapshot,\n): number | null {\n if (currency === \"EUR\") return 1.0;\n\n const s = snapshot ?? getActiveSnapshot();\n const currencyRates = s[currency];\n if (!currencyRates) return null; // unsupported currency\n\n for (let i = 0; i <= 3; i++) {\n const d = i === 0 ? date : subtractDays(date, i);\n if (currencyRates[d] !== undefined) {\n return currencyRates[d];\n }\n }\n return null;\n}\n","export type WarningEntry =\n | { code: \"MISSING_ISIN\"; row: number }\n | { code: \"UNSUPPORTED_CURRENCY\"; row: number; currency: string }\n | { code: \"NO_ECB_RATE\"; row: number; currency: string; date: string }\n | { code: \"QUANTITY_ZERO\"; row: number };\n\nexport function warningToEnglish(w: WarningEntry): string {\n switch (w.code) {\n case \"MISSING_ISIN\":\n return `Row ${w.row}: missing ISIN — skipped`;\n case \"UNSUPPORTED_CURRENCY\":\n return `Row ${w.row}: unsupported currency ${w.currency} — skipped`;\n case \"NO_ECB_RATE\":\n return `Row ${w.row}: no ECB rate for ${w.currency} on ${w.date} — skipped`;\n case \"QUANTITY_ZERO\":\n return `Row ${w.row}: quantity is 0 — skipped`;\n }\n}\n","import { Transaction } from \"../types.js\";\nimport { ParseError } from \"../errors.js\";\nimport {\n lookupRate,\n getActiveSnapshot,\n RatesSnapshot,\n} from \"../rates/index.js\";\nimport { WarningEntry, warningToEnglish } from \"./warnings.js\";\n\nconst REQUIRED_COLUMNS = [\n \"ISIN\",\n \"Quantity\",\n \"Price\",\n \"Date\",\n \"Local value\",\n \"Local value currency\",\n \"Transaction costs\",\n \"Transaction costs currency\",\n \"Product\",\n] as const;\n\n/**\n * Parse a single CSV line, respecting RFC-4180-style double-quote escaping.\n * Does not support embedded newlines in fields (not needed for DEGIRO exports).\n */\nfunction parseCSVRow(line: string): string[] {\n const fields: string[] = [];\n let i = 0;\n\n while (i <= line.length) {\n // End of line — stop (handles trailing comma by not emitting extra empty field)\n if (i === line.length) break;\n\n if (line[i] === '\"') {\n // Quoted field\n let field = \"\";\n i++; // skip opening quote\n while (i < line.length) {\n if (line[i] === '\"' && i + 1 < line.length && line[i + 1] === '\"') {\n // Escaped double-quote\n field += '\"';\n i += 2;\n } else if (line[i] === '\"') {\n i++; // skip closing quote\n break;\n } else {\n field += line[i++];\n }\n }\n fields.push(field);\n if (i < line.length && line[i] === \",\") i++; // skip delimiter\n } else {\n // Unquoted field\n const start = i;\n while (i < line.length && line[i] !== \",\") i++;\n fields.push(line.slice(start, i));\n if (i < line.length) i++; // skip delimiter\n }\n }\n\n return fields;\n}\n\n/**\n * Parse a full CSV string (CRLF or LF line endings) into a 2-D array of strings.\n */\nfunction parseCSV(input: string): string[][] {\n const normalized = input.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n return normalized.split(\"\\n\").map(parseCSVRow);\n}\n\n/**\n * Convert a DEGIRO date \"DD-MM-YYYY\" to ISO format \"YYYY-MM-DD\".\n */\nfunction parseDate(ddmmyyyy: string): string {\n const [dd, mm, yyyy] = ddmmyyyy.split(\"-\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\nexport class DEGIROParser {\n private _warningEntries: WarningEntry[] = [];\n private _snapshot?: RatesSnapshot;\n\n constructor(snapshot?: RatesSnapshot) {\n this._snapshot = snapshot;\n }\n\n parse(csv: string): Transaction[] {\n this._warningEntries = [];\n\n // Binary content (null bytes) is not valid CSV\n if (typeof csv !== \"string\" || csv.includes(\"\\x00\")) {\n throw new ParseError(\"INVALID_CSV\");\n }\n\n let rows: string[][];\n try {\n rows = parseCSV(csv);\n } catch {\n throw new ParseError(\"INVALID_CSV\");\n }\n\n if (rows.length === 0) {\n throw new ParseError(\"INVALID_CSV\");\n }\n\n // Build column-name → index map from the header row\n const header = rows[0];\n const colIndex: Record<string, number> = {};\n for (let i = 0; i < header.length; i++) {\n colIndex[header[i].trim()] = i;\n }\n\n // Validate that every required column is present\n for (const col of REQUIRED_COLUMNS) {\n if (colIndex[col] === undefined) {\n throw new ParseError(\"MISSING_COLUMN\", col);\n }\n }\n\n // Resolve the rates snapshot once for the whole parse run\n const snapshot: RatesSnapshot = this._snapshot ?? getActiveSnapshot();\n\n const transactions: Transaction[] = [];\n\n for (let r = 1; r < rows.length; r++) {\n const row = rows[r];\n // 1-indexed row number: header = 1, first data row = 2\n const rowIndex = r + 1;\n\n // Skip completely empty rows (including the trailing empty line after a\n // final newline, and rows that are all whitespace)\n if (row.every((cell) => cell.trim() === \"\")) continue;\n\n const get = (col: string): string => (row[colIndex[col]] ?? \"\").trim();\n\n // --- ISIN ---\n const isin = get(\"ISIN\");\n if (!isin) {\n this._warningEntries.push({ code: \"MISSING_ISIN\", row: rowIndex });\n continue;\n }\n\n // --- Quantity ---\n const rawQty = parseFloat(get(\"Quantity\"));\n if (isNaN(rawQty) || rawQty === 0) {\n this._warningEntries.push({ code: \"QUANTITY_ZERO\", row: rowIndex });\n continue;\n }\n\n const type: \"BUY\" | \"SELL\" = rawQty > 0 ? \"BUY\" : \"SELL\";\n const quantity = Math.abs(rawQty);\n\n // --- Date ---\n const isoDate = parseDate(get(\"Date\"));\n\n // --- Currency & FX ---\n const currency = get(\"Local value currency\");\n const rawTotalLocal = parseFloat(get(\"Local value\"));\n const totalLocal = isNaN(rawTotalLocal) ? 0 : rawTotalLocal;\n\n let totalEUR: number;\n let fxRate: number | undefined;\n\n if (currency === \"EUR\") {\n totalEUR = Math.abs(totalLocal);\n fxRate = undefined;\n } else {\n const rate = lookupRate(currency, isoDate, snapshot);\n if (rate === null) {\n if (snapshot[currency] === undefined) {\n this._warningEntries.push({\n code: \"UNSUPPORTED_CURRENCY\",\n row: rowIndex,\n currency,\n });\n } else {\n this._warningEntries.push({\n code: \"NO_ECB_RATE\",\n row: rowIndex,\n currency,\n date: isoDate,\n });\n }\n continue;\n }\n totalEUR = Math.abs(totalLocal) / rate;\n fxRate = rate;\n }\n\n // --- Fees ---\n const rawFees = parseFloat(get(\"Transaction costs\"));\n const feesEUR = Math.abs(isNaN(rawFees) ? 0 : rawFees);\n\n // --- Remaining fields ---\n const pricePerUnit = parseFloat(get(\"Price\"));\n const product = get(\"Product\");\n\n transactions.push({\n isin,\n product,\n date: isoDate,\n type,\n quantity,\n pricePerUnit,\n currency,\n totalLocal,\n totalEUR,\n feesEUR,\n fxRate,\n });\n }\n\n return transactions;\n }\n\n get warnings(): string[] {\n return this._warningEntries.map(warningToEnglish);\n }\n\n get warningEntries(): WarningEntry[] {\n return this._warningEntries;\n }\n}\n","import type {\n Transaction,\n MatchedLot,\n GainsReport,\n LotMethod,\n} from \"../types.js\";\nimport { CalculationError } from \"../errors.js\";\n\ninterface Lot {\n date: string;\n quantity: number;\n pricePerUnitEUR: number;\n feesEUR: number;\n originalQty: number;\n fxRate?: number;\n}\n\nfunction roundHalfUp(x: number): number {\n return (Math.sign(x) * Math.round(Math.abs(x) * 100)) / 100;\n}\n\nfunction inferTaxYear(transactions: Transaction[]): {\n year: number;\n multipleYears: boolean;\n} {\n const counts: Record<string, number> = {};\n for (const t of transactions) {\n const y = t.date.slice(0, 4);\n counts[y] = (counts[y] ?? 0) + 1;\n }\n const years = Object.keys(counts);\n if (years.length === 0)\n return { year: new Date().getFullYear(), multipleYears: false };\n const year = parseInt(\n years.reduce((a, b) => (counts[a] >= counts[b] ? a : b)),\n );\n return { year, multipleYears: years.length > 1 };\n}\n\nexport class Calculator {\n private readonly _transactions: Transaction[];\n private readonly _parseWarnings: string[];\n\n constructor(transactions: Transaction[], parseWarnings?: string[]) {\n this._transactions = transactions;\n this._parseWarnings = parseWarnings ?? [];\n }\n\n calculateGains(method: LotMethod): GainsReport {\n const warnings: string[] = [...this._parseWarnings];\n\n // 1. Sort ascending by date; BUY before SELL on same date\n const sorted = [...this._transactions].sort((a, b) => {\n if (a.date < b.date) return -1;\n if (a.date > b.date) return 1;\n // Same date: BUY before SELL\n if (a.type === \"BUY\" && b.type === \"SELL\") return -1;\n if (a.type === \"SELL\" && b.type === \"BUY\") return 1;\n return 0;\n });\n\n // 2. Tax year inference\n const { year: taxYear, multipleYears } = inferTaxYear(sorted);\n if (multipleYears) {\n warnings.push(\n \"CSV contains transactions from multiple years — filter to a single year for accurate reporting.\",\n );\n }\n\n // 3. Lot matching\n const openLots = new Map<string, Lot[]>();\n const matchedLots: MatchedLot[] = [];\n const ratesUsed: Record<string, number> = {};\n\n for (const tx of sorted) {\n // Collect rates used\n if (tx.fxRate !== undefined) {\n ratesUsed[`${tx.currency}:${tx.date}`] = tx.fxRate;\n }\n\n if (tx.type === \"BUY\") {\n const lot: Lot = {\n date: tx.date,\n quantity: tx.quantity,\n pricePerUnitEUR: tx.totalEUR / tx.quantity,\n feesEUR: tx.feesEUR,\n originalQty: tx.quantity,\n fxRate: tx.fxRate,\n };\n if (!openLots.has(tx.isin)) openLots.set(tx.isin, []);\n openLots.get(tx.isin)!.push(lot);\n } else {\n // SELL\n const lots = openLots.get(tx.isin);\n if (!lots || lots.length === 0) {\n throw new CalculationError(tx.isin, tx.date);\n }\n\n const sellPricePerUnitEUR = tx.totalEUR / tx.quantity;\n let remainingSellQty = tx.quantity;\n\n while (remainingSellQty > 0) {\n if (!lots || lots.length === 0) {\n throw new CalculationError(tx.isin, tx.date);\n }\n\n const lot = method === \"LIFO\" ? lots[lots.length - 1] : lots[0];\n const matchedQty = Math.min(lot.quantity, remainingSellQty);\n\n // Fee allocation\n const allocatedBuyFeesEUR =\n lot.feesEUR * (matchedQty / lot.originalQty);\n const allocatedSellFeesEUR = tx.feesEUR * (matchedQty / tx.quantity);\n\n const buyCostEUR =\n lot.pricePerUnitEUR * matchedQty + allocatedBuyFeesEUR;\n const sellProceedsEUR =\n sellPricePerUnitEUR * matchedQty - allocatedSellFeesEUR;\n const gainLossEUR = sellProceedsEUR - buyCostEUR;\n\n matchedLots.push({\n isin: tx.isin,\n product: tx.product,\n quantity: matchedQty,\n buyDate: lot.date,\n sellDate: tx.date,\n buyPriceEUR: lot.pricePerUnitEUR,\n sellPriceEUR: sellPricePerUnitEUR,\n buyCostEUR: roundHalfUp(buyCostEUR),\n sellProceedsEUR: roundHalfUp(sellProceedsEUR),\n gainLossEUR: roundHalfUp(gainLossEUR),\n buyFxRate: lot.fxRate,\n sellFxRate: tx.fxRate,\n });\n\n lot.quantity -= matchedQty;\n remainingSellQty -= matchedQty;\n\n if (lot.quantity <= 0) {\n if (method === \"LIFO\") {\n lots.pop();\n } else {\n lots.shift();\n }\n }\n }\n }\n }\n\n // 4. Aggregate\n let plusvalenze = 0;\n let minusvalenze = 0;\n for (const lot of matchedLots) {\n if (lot.gainLossEUR > 0) plusvalenze += lot.gainLossEUR;\n else minusvalenze += Math.abs(lot.gainLossEUR);\n }\n\n return {\n method,\n taxYear,\n plusvalenze: roundHalfUp(plusvalenze),\n minusvalenze: roundHalfUp(minusvalenze),\n netResult: roundHalfUp(plusvalenze - minusvalenze),\n lots: matchedLots,\n ratesUsed,\n warnings,\n generatedAt: new Date().toISOString(),\n };\n }\n}\n","import type { GainsReport, MatchedLot } from \"../types.js\";\nimport type { LocaleStrings } from \"../i18n/types.js\";\n\nconst SEPARATOR = \"─\".repeat(72);\n\nfunction formatEUR(amount: number, locale: string): string {\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount);\n}\n\nfunction formatGainLoss(amount: number, locale: string): string {\n const formatted = formatEUR(Math.abs(amount), locale);\n return amount >= 0 ? `+${formatted}` : `-${formatted}`;\n}\n\nexport function renderReport(report: GainsReport, s: LocaleStrings): string {\n const fmt = (n: number) => formatEUR(n, s.numberLocale);\n const lines: string[] = [];\n\n // Header\n lines.push(\n `${s.headerMethod}: ${report.method} | ${s.headerTaxYear}: ${report.taxYear}`,\n );\n lines.push(\"\");\n\n // Column headers\n const headers = [\n s.headerIsin.padEnd(14),\n s.headerProduct.padEnd(20),\n s.headerQty.padStart(5),\n s.headerBuyDate.padEnd(12),\n s.headerSellDate.padEnd(12),\n s.headerBuyEur.padStart(14),\n s.headerSellEur.padStart(13),\n s.headerGainLoss.padStart(17),\n ].join(\" \");\n lines.push(headers);\n\n // Lot rows\n for (const lot of report.lots) {\n const row = [\n lot.isin.padEnd(14),\n lot.product.substring(0, 20).padEnd(20),\n String(lot.quantity).padStart(5),\n lot.buyDate.padEnd(12),\n lot.sellDate.padEnd(12),\n fmt(lot.buyCostEUR).padStart(14),\n fmt(lot.sellProceedsEUR).padStart(13),\n formatGainLoss(lot.gainLossEUR, s.numberLocale).padStart(17),\n ].join(\" \");\n lines.push(row);\n }\n\n lines.push(\"\");\n lines.push(SEPARATOR);\n\n // Summary\n lines.push(`${s.summaryPlusvalenze}: ${fmt(report.plusvalenze)} EUR`);\n lines.push(`${s.summaryMinusvalenze}: ${fmt(report.minusvalenze)} EUR`);\n lines.push(`${s.summaryNetResult}: ${fmt(report.netResult)} EUR`);\n lines.push(\"\");\n lines.push(`${s.summaryWarnings}: ${report.warnings.length}`);\n lines.push(`${s.summaryGenerated}: ${report.generatedAt}`);\n lines.push(\"\");\n lines.push(s.disclaimer);\n\n return lines.join(\"\\n\");\n}\n","import * as fs from \"node:fs\";\nimport { DEGIROParser } from \"../../parser/index.js\";\nimport { ParseError } from \"../../errors.js\";\nimport type { LocaleStrings } from \"../../i18n/types.js\";\n\nexport async function runValidate(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n const filePath = positional[0];\n if (!filePath) {\n stderr.write(\"Usage: minus-tracker validate <file.csv>\\n\");\n return 2;\n }\n\n let csv: string;\n try {\n csv = fs.readFileSync(filePath, \"utf8\");\n } catch {\n stderr.write(`Cannot read file: ${filePath}\\n`);\n return 1;\n }\n\n const parser = new DEGIROParser();\n let transactions;\n try {\n transactions = parser.parse(csv);\n } catch (err) {\n if (err instanceof ParseError) {\n if (err.code === \"INVALID_CSV\") {\n stderr.write(s.errorInvalidCsv + \"\\n\");\n } else {\n stderr.write(s.errorMissingColumn(err.columnName!) + \"\\n\");\n }\n return 1;\n }\n throw err;\n }\n\n stdout.write(s.validateOk(transactions.length, 0) + \"\\n\");\n\n for (const entry of parser.warningEntries) {\n let reason: string;\n switch (entry.code) {\n case \"MISSING_ISIN\":\n reason = s.warnMissingIsin(entry.row);\n break;\n case \"UNSUPPORTED_CURRENCY\":\n reason = s.warnUnsupportedCurrency(entry.row, entry.currency);\n break;\n case \"NO_ECB_RATE\":\n reason = s.warnNoEcbRate(entry.row, entry.currency, entry.date);\n break;\n case \"QUANTITY_ZERO\":\n reason = s.warnQuantityZero(entry.row);\n break;\n }\n stdout.write(reason + \"\\n\");\n }\n\n return 0;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport * as https from \"node:https\";\nimport { getActiveSnapshot, type RatesSnapshot } from \"../../rates/index.js\";\nimport type { LocaleStrings } from \"../../i18n/types.js\";\n\nfunction getSnapshotPath(): string {\n const platform = process.platform;\n let configDir: string;\n if (platform === \"win32\") {\n configDir =\n process.env.APPDATA ?? path.join(os.homedir(), \"AppData\", \"Roaming\");\n } else {\n configDir =\n process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), \".config\");\n }\n return path.join(configDir, \"minus-tracker\", \"ecb-rates.json\");\n}\n\nfunction getCoverage(snapshot: RatesSnapshot): {\n start: string;\n end: string;\n currencies: string;\n} {\n let start = \"9999-12-31\";\n let end = \"0000-01-01\";\n const currencies: string[] = [];\n\n for (const [ccy, dates] of Object.entries(snapshot)) {\n currencies.push(ccy);\n for (const d of Object.keys(dates)) {\n if (d < start) start = d;\n if (d > end) end = d;\n }\n }\n return { start, end, currencies: currencies.sort().join(\", \") };\n}\n\nasync function fetchEcbData(currency: string): Promise<Record<string, number>> {\n return new Promise((resolve, reject) => {\n const url = `https://data-api.ecb.europa.eu/service/data/EXR/D.${currency}.EUR.SP00.A?format=csvdata&startPeriod=2019-01-01`;\n https\n .get(url, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n const rates: Record<string, number> = {};\n const lines = data.split(\"\\n\");\n for (const line of lines) {\n // Simple parsing: look for lines matching YYYY-MM-DD in the expected ECB CSV layout\n const dateMatch = line.match(\n /^(\\d{4}-\\d{2}-\\d{2}),.*?,.*?,.*?,.*?,([\\d.]+)/,\n );\n if (dateMatch) {\n rates[dateMatch[1]] = parseFloat(dateMatch[2]);\n }\n }\n resolve(rates);\n });\n res.on(\"error\", reject);\n })\n .on(\"error\", reject);\n });\n}\n\nexport async function runRates(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n if (flags[\"check\"]) {\n const snapshot = getActiveSnapshot();\n const { start, end, currencies } = getCoverage(snapshot);\n stdout.write(s.ratesCoverage(start, end, currencies) + \"\\n\");\n stdout.write(s.ratesGapsNone + \"\\n\");\n return 0;\n }\n\n if (flags[\"update\"]) {\n stdout.write(s.ratesUpdateFetching + \"\\n\");\n const snapshotPath = getSnapshotPath();\n\n let existing: RatesSnapshot = {};\n try {\n existing = JSON.parse(\n fs.readFileSync(snapshotPath, \"utf8\"),\n ) as RatesSnapshot;\n } catch {\n /* file doesn't exist yet */\n }\n\n let addedCount = 0;\n for (const currency of [\"USD\", \"GBP\", \"CHF\"]) {\n try {\n const newRates = await fetchEcbData(currency);\n const existing_ccy = existing[currency] ?? {};\n let added = 0;\n for (const [date, rate] of Object.entries(newRates)) {\n if (!existing_ccy[date]) added++;\n existing_ccy[date] = rate;\n }\n existing[currency] = existing_ccy;\n addedCount += added;\n } catch {\n stderr.write(`Failed to fetch ${currency} rates\\n`);\n }\n }\n\n fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });\n fs.writeFileSync(\n snapshotPath,\n JSON.stringify(existing, null, 2) + \"\\n\",\n \"utf8\",\n );\n stdout.write(s.ratesUpdateDone(addedCount) + \"\\n\");\n stdout.write(s.ratesSnapshotWritten(snapshotPath) + \"\\n\");\n return 0;\n }\n\n stderr.write(\"Usage: minus-tracker rates --check | --update\\n\");\n return 2;\n}\n","import { resolveLocale, saveLocale } from \"../../i18n/settings.js\";\nimport type { LocaleStrings, SupportedLocale } from \"../../i18n/types.js\";\n\nconst SUPPORTED_LOCALES: SupportedLocale[] = [\"it\", \"en\"];\n\nexport async function runConfig(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n if (flags[\"lang\"] !== undefined) {\n const lang = flags[\"lang\"] as string;\n if (!SUPPORTED_LOCALES.includes(lang as SupportedLocale)) {\n stderr.write(`Unsupported locale \"${lang}\". Use: it, en\\n`);\n return 2;\n }\n saveLocale(lang as SupportedLocale);\n stdout.write(s.configLangSet(lang as SupportedLocale) + \"\\n\");\n return 0;\n }\n\n if (flags[\"show\"]) {\n const locale = resolveLocale();\n stdout.write(s.configCurrentLang(locale) + \"\\n\");\n return 0;\n }\n\n stderr.write(\"Usage: minus-tracker config --lang <it|en> | --show\\n\");\n return 2;\n}\n"],"mappings":"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","names":["path","path","fs","path","fs","get","fs","fs","path","os","SUPPORTED_LOCALES"]}
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/i18n/it.ts","../../src/i18n/en.ts","../../src/i18n/settings.ts","../../src/i18n/index.ts","../../src/errors.ts","../../src/cli/commands/calc.ts","../../src/rates/index.ts","../../src/parser/warnings.ts","../../src/parser/index.ts","../../src/calculator/index.ts","../../src/cli/renderer.ts","../../src/cli/commands/validate.ts","../../src/cli/commands/rates.ts","../../src/cli/commands/config.ts"],"sourcesContent":["import { parseArgs } from \"node:util\";\nimport { resolveLocale, getStrings } from \"../i18n/index.js\";\nimport { ParseError, CalculationError } from \"../errors.js\";\nimport { runCalc } from \"./commands/calc.js\";\nimport { runValidate } from \"./commands/validate.js\";\nimport { runRates } from \"./commands/rates.js\";\nimport { runConfig } from \"./commands/config.js\";\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n args: process.argv.slice(2),\n options: {\n lang: { type: \"string\" },\n method: { type: \"string\" },\n year: { type: \"string\" },\n json: { type: \"boolean\", default: false },\n check: { type: \"boolean\", default: false },\n update: { type: \"boolean\", default: false },\n show: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n strict: false,\n });\n\n // Resolve locale once — exits 2 on invalid value\n const locale = resolveLocale(values.lang as string | undefined);\n const s = getStrings(locale);\n\n const command = positionals[0];\n const restPositionals = positionals.slice(1);\n const flags = values as Record<string, string | boolean>;\n\n const stdout = process.stdout;\n const stderr = process.stderr;\n\n let exitCode = 0;\n\n try {\n switch (command) {\n case \"calc\":\n exitCode = await runCalc(restPositionals, flags, s, stdout, stderr);\n break;\n case \"validate\":\n exitCode = await runValidate(restPositionals, flags, s, stdout, stderr);\n break;\n case \"rates\":\n exitCode = await runRates(restPositionals, flags, s, stdout, stderr);\n break;\n case \"config\":\n exitCode = await runConfig(restPositionals, flags, s, stdout, stderr);\n break;\n default:\n stderr.write(\n \"Usage: minus-tracker <calc|validate|rates|config> [options] [file]\\n\",\n );\n exitCode = 2;\n }\n } catch (err) {\n if (err instanceof ParseError) {\n if (err.code === \"INVALID_CSV\") {\n stderr.write(s.errorInvalidCsv + \"\\n\");\n } else {\n stderr.write(s.errorMissingColumn(err.columnName!) + \"\\n\");\n }\n exitCode = 1;\n } else if (err instanceof CalculationError) {\n stderr.write(s.errorNoOpenLots(err.isin, err.date) + \"\\n\");\n exitCode = 1;\n } else {\n throw err;\n }\n }\n\n process.exit(exitCode);\n}\n\nmain().catch((err) => {\n process.stderr.write(`Unhandled error: ${String(err)}\\n`);\n process.exit(1);\n});\n","import type { LocaleStrings } from \"./types.js\";\n\nexport const it: LocaleStrings = {\n numberLocale: \"it-IT\",\n\n errorInvalidCsv: \"CSV non valido: impossibile analizzare il file\",\n errorMissingColumn: (col) => `Colonna obbligatoria mancante: ${col}`,\n errorNoOpenLots: (isin, date) =>\n `Nessun lotto aperto per ISIN ${isin} in data ${date}`,\n\n warnMissingIsin: (row) => `Riga ${row}: ISIN mancante — riga ignorata`,\n warnUnsupportedCurrency: (row, currency) =>\n `Riga ${row}: valuta non supportata ${currency} — riga ignorata`,\n warnNoEcbRate: (row, currency, date) =>\n `Riga ${row}: nessun tasso BCE per ${currency} in data ${date} — riga ignorata`,\n warnQuantityZero: (row) => `Riga ${row}: quantità pari a 0 — riga ignorata`,\n\n warnMultipleYears:\n \"Il CSV contiene transazioni di più anni — filtra per un singolo anno per un calcolo accurato.\",\n\n headerMethod: \"METODO\",\n headerTaxYear: \"ANNO FISCALE\",\n headerIsin: \"ISIN\",\n headerProduct: \"TITOLO\",\n headerQty: \"QTÀ\",\n headerBuyDate: \"DATA ACQUISTO\",\n headerSellDate: \"DATA VENDITA\",\n headerBuyEur: \"ACQUISTO EUR\",\n headerSellEur: \"VENDITA EUR\",\n headerGainLoss: \"GUADAGNO/PERDITA\",\n\n summaryPlusvalenze: \"PLUSVALENZE\",\n summaryMinusvalenze: \"MINUSVALENZE\",\n summaryNetResult: \"RISULTATO NETTO\",\n summaryWarnings: \"AVVERTENZE\",\n summaryGenerated: \"Generato\",\n\n validateOk: (count, errors) =>\n `OK: ${count} transazioni analizzate, ${errors} errori gravi`,\n validateWarn: (count, reason) =>\n `AVVISO: ${count} righe ignorate (${reason})`,\n\n ratesCoverage: (start, end, currencies) =>\n `Copertura: ${start} → ${end} | Valute: ${currencies}`,\n ratesGapsNone: \"Lacune: nessuna\",\n ratesGaps: (list) => `Lacune: ${list}`,\n ratesUpdateFetching: \"Recupero dati BCE SDMX in corso...\",\n ratesUpdateDone: (n) => `Completato. Aggiunte ${n} nuove date.`,\n ratesSnapshotWritten: (path) => `Snapshot scritto in ${path}`,\n\n configLangSet: (lang) => `Lingua impostata su: ${lang}`,\n configCurrentLang: (lang) => `Lingua corrente: ${lang}`,\n\n disclaimer: \"minus-tracker è un ausilio al calcolo, non consulenza fiscale.\",\n};\n","import type { LocaleStrings } from \"./types.js\";\n\nexport const en: LocaleStrings = {\n numberLocale: \"en-US\",\n\n errorInvalidCsv: \"Invalid CSV: unable to parse\",\n errorMissingColumn: (col) => `Missing required column: ${col}`,\n errorNoOpenLots: (isin, date) => `No open lots for ISIN ${isin} on ${date}`,\n\n warnMissingIsin: (row) => `Row ${row}: missing ISIN — skipped`,\n warnUnsupportedCurrency: (row, currency) =>\n `Row ${row}: unsupported currency ${currency} — skipped`,\n warnNoEcbRate: (row, currency, date) =>\n `Row ${row}: no ECB rate for ${currency} on ${date} — skipped`,\n warnQuantityZero: (row) => `Row ${row}: quantity is 0 — skipped`,\n\n warnMultipleYears:\n \"CSV contains transactions from multiple years — filter to a single year for accurate reporting.\",\n\n headerMethod: \"METHOD\",\n headerTaxYear: \"TAX YEAR\",\n headerIsin: \"ISIN\",\n headerProduct: \"PRODUCT\",\n headerQty: \"QTY\",\n headerBuyDate: \"BUY DATE\",\n headerSellDate: \"SELL DATE\",\n headerBuyEur: \"BUY EUR\",\n headerSellEur: \"SELL EUR\",\n headerGainLoss: \"GAIN/LOSS\",\n\n summaryPlusvalenze: \"PLUSVALENZE\",\n summaryMinusvalenze: \"MINUSVALENZE\",\n summaryNetResult: \"NET RESULT\",\n summaryWarnings: \"WARNINGS\",\n summaryGenerated: \"Generated\",\n\n validateOk: (count, errors) =>\n `OK: ${count} transactions parsed, ${errors} hard errors`,\n validateWarn: (count, reason) => `WARN: ${count} rows skipped (${reason})`,\n\n ratesCoverage: (start, end, currencies) =>\n `Coverage: ${start} → ${end} | Currencies: ${currencies}`,\n ratesGapsNone: \"Gaps: none\",\n ratesGaps: (list) => `Gaps: ${list}`,\n ratesUpdateFetching: \"Fetching ECB SDMX...\",\n ratesUpdateDone: (n) => `Done. Added ${n} new dates.`,\n ratesSnapshotWritten: (path) => `Snapshot written to ${path}`,\n\n configLangSet: (lang) => `Language set to: ${lang}`,\n configCurrentLang: (lang) => `Current language: ${lang}`,\n\n disclaimer: \"minus-tracker è un ausilio al calcolo, non consulenza fiscale.\",\n};\n","import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { MinusTrackerConfig, SupportedLocale } from \"./types.js\";\n\nconst SUPPORTED_LOCALES: SupportedLocale[] = [\"it\", \"en\"];\n\nexport function getConfigPath(): string {\n if (process.platform === \"win32\") {\n const appData =\n process.env.APPDATA ?? path.join(os.homedir(), \"AppData\", \"Roaming\");\n return path.join(appData, \"minus-tracker\", \"config.json\");\n }\n const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), \".config\");\n return path.join(xdg, \"minus-tracker\", \"config.json\");\n}\n\nexport function readConfig(): MinusTrackerConfig {\n try {\n const raw = fs.readFileSync(getConfigPath(), \"utf8\");\n return JSON.parse(raw) as MinusTrackerConfig;\n } catch {\n return {};\n }\n}\n\nexport function saveLocale(lang: SupportedLocale): void {\n const configPath = getConfigPath();\n fs.mkdirSync(path.dirname(configPath), { recursive: true });\n const existing = readConfig();\n const updated: MinusTrackerConfig = { ...existing, locale: lang };\n fs.writeFileSync(configPath, JSON.stringify(updated, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport function resolveLocale(cliLang?: string): SupportedLocale {\n // 1. --lang flag\n if (cliLang !== undefined) {\n if (!SUPPORTED_LOCALES.includes(cliLang as SupportedLocale)) {\n process.stderr.write(`Unsupported locale \"${cliLang}\". Use: it, en\\n`);\n process.exit(2);\n }\n return cliLang as SupportedLocale;\n }\n // 2. env var\n const envLang = process.env.MINUS_TRACKER_LANG;\n if (envLang !== undefined) {\n if (!SUPPORTED_LOCALES.includes(envLang as SupportedLocale)) {\n process.stderr.write(`Unsupported locale \"${envLang}\". Use: it, en\\n`);\n process.exit(2);\n }\n return envLang as SupportedLocale;\n }\n // 3. config file\n const config = readConfig();\n if (config.locale && SUPPORTED_LOCALES.includes(config.locale)) {\n return config.locale;\n }\n // 4. default\n return \"it\";\n}\n","import { it } from \"./it.js\";\nimport { en } from \"./en.js\";\nimport type { LocaleStrings, SupportedLocale } from \"./types.js\";\n\nexport { it, en };\nexport type { LocaleStrings, SupportedLocale };\nexport type { MinusTrackerConfig } from \"./types.js\";\nexport {\n resolveLocale,\n saveLocale,\n readConfig,\n getConfigPath,\n} from \"./settings.js\";\n\nexport function getStrings(locale: SupportedLocale): LocaleStrings {\n return locale === \"it\" ? it : en;\n}\n","export class ParseError extends Error {\n readonly code: \"INVALID_CSV\" | \"MISSING_COLUMN\";\n readonly columnName?: string;\n\n constructor(code: \"INVALID_CSV\");\n constructor(code: \"MISSING_COLUMN\", columnName: string);\n constructor(code: ParseError[\"code\"], columnName?: string) {\n const msg =\n code === \"INVALID_CSV\"\n ? \"Invalid CSV: unable to parse\"\n : `Missing required column: ${columnName}`;\n super(msg);\n this.name = \"ParseError\";\n this.code = code;\n this.columnName = columnName;\n }\n}\n\nexport class CalculationError extends Error {\n readonly isin: string;\n readonly date: string;\n\n constructor(isin: string, date: string) {\n super(`No open lots for ISIN ${isin} on ${date}`);\n this.name = \"CalculationError\";\n this.isin = isin;\n this.date = date;\n }\n}\n","import * as fs from \"node:fs\";\nimport { DEGIROParser } from \"../../parser/index.js\";\nimport { Calculator } from \"../../calculator/index.js\";\nimport { ParseError } from \"../../errors.js\";\nimport { renderReport } from \"../renderer.js\";\nimport type { LocaleStrings } from \"../../i18n/types.js\";\nimport type { LotMethod } from \"../../types.js\";\n\nexport async function runCalc(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n const filePath = positional[0];\n if (!filePath) {\n stderr.write(\n \"Usage: minus-tracker calc [--method LIFO|FIFO] [--json] <file.csv>\\n\",\n );\n return 2;\n }\n\n let csv: string;\n try {\n csv = fs.readFileSync(filePath, \"utf8\");\n } catch {\n stderr.write(`Cannot read file: ${filePath}\\n`);\n return 1;\n }\n\n const method = (flags[\"method\"] as LotMethod) ?? \"LIFO\";\n if (method !== \"LIFO\" && method !== \"FIFO\") {\n stderr.write(\"--method must be LIFO or FIFO\\n\");\n return 2;\n }\n\n const parser = new DEGIROParser();\n let transactions;\n try {\n transactions = parser.parse(csv);\n } catch (err) {\n if (err instanceof ParseError) {\n if (err.code === \"INVALID_CSV\") {\n stderr.write(s.errorInvalidCsv + \"\\n\");\n } else {\n stderr.write(s.errorMissingColumn(err.columnName!) + \"\\n\");\n }\n return 1;\n }\n throw err;\n }\n\n const calculator = new Calculator(transactions, parser.warnings);\n const report = calculator.calculateGains(method);\n\n if (flags[\"json\"]) {\n stdout.write(JSON.stringify(report, null, 2) + \"\\n\");\n } else {\n stdout.write(renderReport(report, s) + \"\\n\");\n }\n return 0;\n}\n","import { fileURLToPath } from \"node:url\";\nimport * as path from \"node:path\";\nimport * as fs from \"node:fs\";\n\nexport type RatesSnapshot = Record<string, Record<string, number>>;\n\n// Load the bundled snapshot lazily; returns null if the file is absent (e.g. broken install)\nlet _bundled: RatesSnapshot | null | undefined = undefined;\n\nfunction getBundledSnapshot(): RatesSnapshot | null {\n if (_bundled !== undefined) return _bundled;\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const bundledPath = path.join(__dirname, \"../data/ecb-rates.json\");\n try {\n _bundled = JSON.parse(\n fs.readFileSync(bundledPath, \"utf8\"),\n ) as RatesSnapshot;\n } catch {\n _bundled = null;\n }\n return _bundled;\n}\n\nfunction getUserSnapshot(): RatesSnapshot | null {\n const platform = process.platform;\n let configDir: string;\n if (platform === \"win32\") {\n configDir =\n process.env.APPDATA ??\n path.join(process.env.USERPROFILE ?? \"~\", \"AppData\", \"Roaming\");\n } else {\n configDir =\n process.env.XDG_CONFIG_HOME ??\n path.join(process.env.HOME ?? \"~\", \".config\");\n }\n const userPath = path.join(configDir, \"minus-tracker\", \"ecb-rates.json\");\n try {\n return JSON.parse(fs.readFileSync(userPath, \"utf8\")) as RatesSnapshot;\n } catch {\n return null;\n }\n}\n\nexport function getActiveSnapshot(): RatesSnapshot {\n const bundled = getBundledSnapshot();\n const user = getUserSnapshot();\n if (!bundled && !user) {\n throw new Error(\n \"No ECB rates snapshot available. Run `minus-tracker rates --update` to fetch rates.\",\n );\n }\n if (!bundled) return user!;\n if (!user) return bundled;\n // Merge: user entries override bundled for same date keys\n const merged: RatesSnapshot = {};\n for (const ccy of new Set([...Object.keys(bundled), ...Object.keys(user)])) {\n merged[ccy] = { ...(bundled[ccy] ?? {}), ...(user[ccy] ?? {}) };\n }\n return merged;\n}\n\nfunction subtractDays(isoDate: string, days: number): string {\n const d = new Date(isoDate + \"T00:00:00Z\");\n d.setUTCDate(d.getUTCDate() - days);\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * Look up ECB rate for a currency on a given date.\n * Returns 1.0 for EUR. Walks back up to 3 calendar days for weekend/holiday gaps.\n * Returns null if not found within the window.\n *\n * @param currency ISO 4217 currency code\n * @param date YYYY-MM-DD trade date\n * @param snapshot Optional override (used in tests to inject stub data)\n */\nexport function lookupRate(\n currency: string,\n date: string,\n snapshot?: RatesSnapshot,\n): number | null {\n if (currency === \"EUR\") return 1.0;\n\n const s = snapshot ?? getActiveSnapshot();\n const currencyRates = s[currency];\n if (!currencyRates) return null; // unsupported currency\n\n for (let i = 0; i <= 3; i++) {\n const d = i === 0 ? date : subtractDays(date, i);\n if (currencyRates[d] !== undefined) {\n return currencyRates[d];\n }\n }\n return null;\n}\n","export type WarningEntry =\n | { code: \"MISSING_ISIN\"; row: number }\n | { code: \"UNSUPPORTED_CURRENCY\"; row: number; currency: string }\n | { code: \"NO_ECB_RATE\"; row: number; currency: string; date: string }\n | { code: \"QUANTITY_ZERO\"; row: number };\n\nexport function warningToEnglish(w: WarningEntry): string {\n switch (w.code) {\n case \"MISSING_ISIN\":\n return `Row ${w.row}: missing ISIN — skipped`;\n case \"UNSUPPORTED_CURRENCY\":\n return `Row ${w.row}: unsupported currency ${w.currency} — skipped`;\n case \"NO_ECB_RATE\":\n return `Row ${w.row}: no ECB rate for ${w.currency} on ${w.date} — skipped`;\n case \"QUANTITY_ZERO\":\n return `Row ${w.row}: quantity is 0 — skipped`;\n }\n}\n","import { Transaction } from \"../types.js\";\nimport { ParseError } from \"../errors.js\";\nimport {\n lookupRate,\n getActiveSnapshot,\n RatesSnapshot,\n} from \"../rates/index.js\";\nimport { WarningEntry, warningToEnglish } from \"./warnings.js\";\n\nconst REQUIRED_COLUMNS = [\n \"ISIN\",\n \"Quantity\",\n \"Price\",\n \"Date\",\n \"Local value\",\n \"Local value currency\",\n \"Transaction costs\",\n \"Transaction costs currency\",\n \"Product\",\n] as const;\n\n/**\n * Parse a single CSV line, respecting RFC-4180-style double-quote escaping.\n * Does not support embedded newlines in fields (not needed for DEGIRO exports).\n */\nfunction parseCSVRow(line: string): string[] {\n const fields: string[] = [];\n let i = 0;\n\n while (i <= line.length) {\n // End of line — stop (handles trailing comma by not emitting extra empty field)\n if (i === line.length) break;\n\n if (line[i] === '\"') {\n // Quoted field\n let field = \"\";\n i++; // skip opening quote\n while (i < line.length) {\n if (line[i] === '\"' && i + 1 < line.length && line[i + 1] === '\"') {\n // Escaped double-quote\n field += '\"';\n i += 2;\n } else if (line[i] === '\"') {\n i++; // skip closing quote\n break;\n } else {\n field += line[i++];\n }\n }\n fields.push(field);\n if (i < line.length && line[i] === \",\") i++; // skip delimiter\n } else {\n // Unquoted field\n const start = i;\n while (i < line.length && line[i] !== \",\") i++;\n fields.push(line.slice(start, i));\n if (i < line.length) i++; // skip delimiter\n }\n }\n\n return fields;\n}\n\n/**\n * Parse a full CSV string (CRLF or LF line endings) into a 2-D array of strings.\n */\nfunction parseCSV(input: string): string[][] {\n const normalized = input.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n return normalized.split(\"\\n\").map(parseCSVRow);\n}\n\n/**\n * Convert a DEGIRO date \"DD-MM-YYYY\" to ISO format \"YYYY-MM-DD\".\n */\nfunction parseDate(ddmmyyyy: string): string {\n const [dd, mm, yyyy] = ddmmyyyy.split(\"-\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\nexport class DEGIROParser {\n private _warningEntries: WarningEntry[] = [];\n private _snapshot?: RatesSnapshot;\n\n constructor(snapshot?: RatesSnapshot) {\n this._snapshot = snapshot;\n }\n\n parse(csv: string): Transaction[] {\n this._warningEntries = [];\n\n // Binary content (null bytes) is not valid CSV\n if (typeof csv !== \"string\" || csv.includes(\"\\x00\")) {\n throw new ParseError(\"INVALID_CSV\");\n }\n\n let rows: string[][];\n try {\n rows = parseCSV(csv);\n } catch {\n throw new ParseError(\"INVALID_CSV\");\n }\n\n if (rows.length === 0) {\n throw new ParseError(\"INVALID_CSV\");\n }\n\n // Build column-name → index map from the header row\n const header = rows[0];\n const colIndex: Record<string, number> = {};\n for (let i = 0; i < header.length; i++) {\n colIndex[header[i].trim()] = i;\n }\n\n // Validate that every required column is present\n for (const col of REQUIRED_COLUMNS) {\n if (colIndex[col] === undefined) {\n throw new ParseError(\"MISSING_COLUMN\", col);\n }\n }\n\n // Resolve the rates snapshot once for the whole parse run\n const snapshot: RatesSnapshot = this._snapshot ?? getActiveSnapshot();\n\n const transactions: Transaction[] = [];\n\n for (let r = 1; r < rows.length; r++) {\n const row = rows[r];\n // 1-indexed row number: header = 1, first data row = 2\n const rowIndex = r + 1;\n\n // Skip completely empty rows (including the trailing empty line after a\n // final newline, and rows that are all whitespace)\n if (row.every((cell) => cell.trim() === \"\")) continue;\n\n const get = (col: string): string => (row[colIndex[col]] ?? \"\").trim();\n\n // --- ISIN ---\n const isin = get(\"ISIN\");\n if (!isin) {\n this._warningEntries.push({ code: \"MISSING_ISIN\", row: rowIndex });\n continue;\n }\n\n // --- Quantity ---\n const rawQty = parseFloat(get(\"Quantity\"));\n if (isNaN(rawQty) || rawQty === 0) {\n this._warningEntries.push({ code: \"QUANTITY_ZERO\", row: rowIndex });\n continue;\n }\n\n const type: \"BUY\" | \"SELL\" = rawQty > 0 ? \"BUY\" : \"SELL\";\n const quantity = Math.abs(rawQty);\n\n // --- Date ---\n const isoDate = parseDate(get(\"Date\"));\n\n // --- Currency & FX ---\n const currency = get(\"Local value currency\");\n const rawTotalLocal = parseFloat(get(\"Local value\"));\n const totalLocal = isNaN(rawTotalLocal) ? 0 : rawTotalLocal;\n\n let totalEUR: number;\n let fxRate: number | undefined;\n\n if (currency === \"EUR\") {\n totalEUR = Math.abs(totalLocal);\n fxRate = undefined;\n } else {\n const rate = lookupRate(currency, isoDate, snapshot);\n if (rate === null) {\n if (snapshot[currency] === undefined) {\n this._warningEntries.push({\n code: \"UNSUPPORTED_CURRENCY\",\n row: rowIndex,\n currency,\n });\n } else {\n this._warningEntries.push({\n code: \"NO_ECB_RATE\",\n row: rowIndex,\n currency,\n date: isoDate,\n });\n }\n continue;\n }\n totalEUR = Math.abs(totalLocal) / rate;\n fxRate = rate;\n }\n\n // --- Fees ---\n const rawFees = parseFloat(get(\"Transaction costs\"));\n const feesEUR = Math.abs(isNaN(rawFees) ? 0 : rawFees);\n\n // --- Remaining fields ---\n const pricePerUnit = parseFloat(get(\"Price\"));\n const product = get(\"Product\");\n\n transactions.push({\n isin,\n product,\n date: isoDate,\n type,\n quantity,\n pricePerUnit,\n currency,\n totalLocal,\n totalEUR,\n feesEUR,\n fxRate,\n });\n }\n\n return transactions;\n }\n\n get warnings(): string[] {\n return this._warningEntries.map(warningToEnglish);\n }\n\n get warningEntries(): WarningEntry[] {\n return this._warningEntries;\n }\n}\n","import type {\n Transaction,\n MatchedLot,\n GainsReport,\n LotMethod,\n} from \"../types.js\";\nimport { CalculationError } from \"../errors.js\";\n\ninterface Lot {\n date: string;\n quantity: number;\n pricePerUnitEUR: number;\n feesEUR: number;\n originalQty: number;\n fxRate?: number;\n}\n\nfunction roundHalfUp(x: number): number {\n return (Math.sign(x) * Math.round(Math.abs(x) * 100)) / 100;\n}\n\nfunction inferTaxYear(transactions: Transaction[]): {\n year: number;\n multipleYears: boolean;\n} {\n const counts: Record<string, number> = {};\n for (const t of transactions) {\n const y = t.date.slice(0, 4);\n counts[y] = (counts[y] ?? 0) + 1;\n }\n const years = Object.keys(counts);\n if (years.length === 0)\n return { year: new Date().getFullYear(), multipleYears: false };\n const year = parseInt(\n years.reduce((a, b) => (counts[a] >= counts[b] ? a : b)),\n );\n return { year, multipleYears: years.length > 1 };\n}\n\nexport class Calculator {\n private readonly _transactions: Transaction[];\n private readonly _parseWarnings: string[];\n\n constructor(transactions: Transaction[], parseWarnings?: string[]) {\n this._transactions = transactions;\n this._parseWarnings = parseWarnings ?? [];\n }\n\n calculateGains(method: LotMethod): GainsReport {\n const warnings: string[] = [...this._parseWarnings];\n\n // 1. Sort ascending by date; BUY before SELL on same date\n const sorted = [...this._transactions].sort((a, b) => {\n if (a.date < b.date) return -1;\n if (a.date > b.date) return 1;\n // Same date: BUY before SELL\n if (a.type === \"BUY\" && b.type === \"SELL\") return -1;\n if (a.type === \"SELL\" && b.type === \"BUY\") return 1;\n return 0;\n });\n\n // 2. Tax year inference\n const { year: taxYear, multipleYears } = inferTaxYear(sorted);\n if (multipleYears) {\n warnings.push(\n \"CSV contains transactions from multiple years — filter to a single year for accurate reporting.\",\n );\n }\n\n // 3. Lot matching\n const openLots = new Map<string, Lot[]>();\n const matchedLots: MatchedLot[] = [];\n const ratesUsed: Record<string, number> = {};\n\n for (const tx of sorted) {\n // Collect rates used\n if (tx.fxRate !== undefined) {\n ratesUsed[`${tx.currency}:${tx.date}`] = tx.fxRate;\n }\n\n if (tx.type === \"BUY\") {\n const lot: Lot = {\n date: tx.date,\n quantity: tx.quantity,\n pricePerUnitEUR: tx.totalEUR / tx.quantity,\n feesEUR: tx.feesEUR,\n originalQty: tx.quantity,\n fxRate: tx.fxRate,\n };\n if (!openLots.has(tx.isin)) openLots.set(tx.isin, []);\n openLots.get(tx.isin)!.push(lot);\n } else {\n // SELL\n const lots = openLots.get(tx.isin);\n if (!lots || lots.length === 0) {\n throw new CalculationError(tx.isin, tx.date);\n }\n\n const sellPricePerUnitEUR = tx.totalEUR / tx.quantity;\n let remainingSellQty = tx.quantity;\n\n while (remainingSellQty > 0) {\n if (!lots || lots.length === 0) {\n throw new CalculationError(tx.isin, tx.date);\n }\n\n const lot = method === \"LIFO\" ? lots[lots.length - 1] : lots[0];\n const matchedQty = Math.min(lot.quantity, remainingSellQty);\n\n // Fee allocation\n const allocatedBuyFeesEUR =\n lot.feesEUR * (matchedQty / lot.originalQty);\n const allocatedSellFeesEUR = tx.feesEUR * (matchedQty / tx.quantity);\n\n const buyCostEUR =\n lot.pricePerUnitEUR * matchedQty + allocatedBuyFeesEUR;\n const sellProceedsEUR =\n sellPricePerUnitEUR * matchedQty - allocatedSellFeesEUR;\n const gainLossEUR = sellProceedsEUR - buyCostEUR;\n\n matchedLots.push({\n isin: tx.isin,\n product: tx.product,\n quantity: matchedQty,\n buyDate: lot.date,\n sellDate: tx.date,\n buyPriceEUR: lot.pricePerUnitEUR,\n sellPriceEUR: sellPricePerUnitEUR,\n buyCostEUR: roundHalfUp(buyCostEUR),\n sellProceedsEUR: roundHalfUp(sellProceedsEUR),\n gainLossEUR: roundHalfUp(gainLossEUR),\n buyFxRate: lot.fxRate,\n sellFxRate: tx.fxRate,\n });\n\n lot.quantity -= matchedQty;\n remainingSellQty -= matchedQty;\n\n if (lot.quantity <= 0) {\n if (method === \"LIFO\") {\n lots.pop();\n } else {\n lots.shift();\n }\n }\n }\n }\n }\n\n // 4. Aggregate\n let plusvalenze = 0;\n let minusvalenze = 0;\n for (const lot of matchedLots) {\n if (lot.gainLossEUR > 0) plusvalenze += lot.gainLossEUR;\n else minusvalenze += Math.abs(lot.gainLossEUR);\n }\n\n return {\n method,\n taxYear,\n plusvalenze: roundHalfUp(plusvalenze),\n minusvalenze: roundHalfUp(minusvalenze),\n netResult: roundHalfUp(plusvalenze - minusvalenze),\n lots: matchedLots,\n ratesUsed,\n warnings,\n generatedAt: new Date().toISOString(),\n };\n }\n}\n","import type { GainsReport, MatchedLot } from \"../types.js\";\nimport type { LocaleStrings } from \"../i18n/types.js\";\n\nconst SEPARATOR = \"─\".repeat(72);\n\nfunction formatEUR(amount: number, locale: string): string {\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount);\n}\n\nfunction formatGainLoss(amount: number, locale: string): string {\n const formatted = formatEUR(Math.abs(amount), locale);\n return amount >= 0 ? `+${formatted}` : `-${formatted}`;\n}\n\nexport function renderReport(report: GainsReport, s: LocaleStrings): string {\n const fmt = (n: number) => formatEUR(n, s.numberLocale);\n const lines: string[] = [];\n\n // Header\n lines.push(\n `${s.headerMethod}: ${report.method} | ${s.headerTaxYear}: ${report.taxYear}`,\n );\n lines.push(\"\");\n\n // Column headers\n const headers = [\n s.headerIsin.padEnd(14),\n s.headerProduct.padEnd(20),\n s.headerQty.padStart(5),\n s.headerBuyDate.padEnd(12),\n s.headerSellDate.padEnd(12),\n s.headerBuyEur.padStart(14),\n s.headerSellEur.padStart(13),\n s.headerGainLoss.padStart(17),\n ].join(\" \");\n lines.push(headers);\n\n // Lot rows\n for (const lot of report.lots) {\n const row = [\n lot.isin.padEnd(14),\n lot.product.substring(0, 20).padEnd(20),\n String(lot.quantity).padStart(5),\n lot.buyDate.padEnd(12),\n lot.sellDate.padEnd(12),\n fmt(lot.buyCostEUR).padStart(14),\n fmt(lot.sellProceedsEUR).padStart(13),\n formatGainLoss(lot.gainLossEUR, s.numberLocale).padStart(17),\n ].join(\" \");\n lines.push(row);\n }\n\n lines.push(\"\");\n lines.push(SEPARATOR);\n\n // Summary\n lines.push(`${s.summaryPlusvalenze}: ${fmt(report.plusvalenze)} EUR`);\n lines.push(`${s.summaryMinusvalenze}: ${fmt(report.minusvalenze)} EUR`);\n lines.push(`${s.summaryNetResult}: ${fmt(report.netResult)} EUR`);\n lines.push(\"\");\n lines.push(`${s.summaryWarnings}: ${report.warnings.length}`);\n lines.push(`${s.summaryGenerated}: ${report.generatedAt}`);\n lines.push(\"\");\n lines.push(s.disclaimer);\n\n return lines.join(\"\\n\");\n}\n","import * as fs from \"node:fs\";\nimport { DEGIROParser } from \"../../parser/index.js\";\nimport { ParseError } from \"../../errors.js\";\nimport type { LocaleStrings } from \"../../i18n/types.js\";\n\nexport async function runValidate(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n const filePath = positional[0];\n if (!filePath) {\n stderr.write(\"Usage: minus-tracker validate <file.csv>\\n\");\n return 2;\n }\n\n let csv: string;\n try {\n csv = fs.readFileSync(filePath, \"utf8\");\n } catch {\n stderr.write(`Cannot read file: ${filePath}\\n`);\n return 1;\n }\n\n const parser = new DEGIROParser();\n let transactions;\n try {\n transactions = parser.parse(csv);\n } catch (err) {\n if (err instanceof ParseError) {\n if (err.code === \"INVALID_CSV\") {\n stderr.write(s.errorInvalidCsv + \"\\n\");\n } else {\n stderr.write(s.errorMissingColumn(err.columnName!) + \"\\n\");\n }\n return 1;\n }\n throw err;\n }\n\n stdout.write(s.validateOk(transactions.length, 0) + \"\\n\");\n\n for (const entry of parser.warningEntries) {\n let reason: string;\n switch (entry.code) {\n case \"MISSING_ISIN\":\n reason = s.warnMissingIsin(entry.row);\n break;\n case \"UNSUPPORTED_CURRENCY\":\n reason = s.warnUnsupportedCurrency(entry.row, entry.currency);\n break;\n case \"NO_ECB_RATE\":\n reason = s.warnNoEcbRate(entry.row, entry.currency, entry.date);\n break;\n case \"QUANTITY_ZERO\":\n reason = s.warnQuantityZero(entry.row);\n break;\n }\n stdout.write(reason + \"\\n\");\n }\n\n return 0;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport * as https from \"node:https\";\nimport { getActiveSnapshot, type RatesSnapshot } from \"../../rates/index.js\";\nimport type { LocaleStrings } from \"../../i18n/types.js\";\n\nfunction getSnapshotPath(): string {\n const platform = process.platform;\n let configDir: string;\n if (platform === \"win32\") {\n configDir =\n process.env.APPDATA ?? path.join(os.homedir(), \"AppData\", \"Roaming\");\n } else {\n configDir =\n process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), \".config\");\n }\n return path.join(configDir, \"minus-tracker\", \"ecb-rates.json\");\n}\n\nfunction getCoverage(snapshot: RatesSnapshot): {\n start: string;\n end: string;\n currencies: string;\n} {\n let start = \"9999-12-31\";\n let end = \"0000-01-01\";\n const currencies: string[] = [];\n\n for (const [ccy, dates] of Object.entries(snapshot)) {\n currencies.push(ccy);\n for (const d of Object.keys(dates)) {\n if (d < start) start = d;\n if (d > end) end = d;\n }\n }\n return { start, end, currencies: currencies.sort().join(\", \") };\n}\n\nasync function fetchEcbData(currency: string): Promise<Record<string, number>> {\n return new Promise((resolve, reject) => {\n const url = `https://data-api.ecb.europa.eu/service/data/EXR/D.${currency}.EUR.SP00.A?format=csvdata&startPeriod=2019-01-01`;\n https\n .get(url, (res) => {\n let data = \"\";\n res.on(\"data\", (chunk) => (data += chunk));\n res.on(\"end\", () => {\n const rates: Record<string, number> = {};\n const lines = data.split(\"\\n\");\n // ECB SDMX csvdata format:\n // KEY,FREQ,CURRENCY,CURRENCY_DENOM,EXR_TYPE,EXR_SUFFIX,TIME_PERIOD,OBS_VALUE,...\n // DATE is column index 6, rate is column index 7\n for (const line of lines) {\n const parts = line.split(\",\");\n if (parts.length < 8) continue;\n const date = parts[6].trim();\n const rate = parseFloat(parts[7].trim());\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(date) && !isNaN(rate) && rate > 0) {\n rates[date] = rate;\n }\n }\n resolve(rates);\n });\n res.on(\"error\", reject);\n })\n .on(\"error\", reject);\n });\n}\n\nexport async function runRates(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n if (flags[\"check\"]) {\n const snapshot = getActiveSnapshot();\n const { start, end, currencies } = getCoverage(snapshot);\n stdout.write(s.ratesCoverage(start, end, currencies) + \"\\n\");\n stdout.write(s.ratesGapsNone + \"\\n\");\n return 0;\n }\n\n if (flags[\"update\"]) {\n stdout.write(s.ratesUpdateFetching + \"\\n\");\n const snapshotPath = getSnapshotPath();\n\n let existing: RatesSnapshot = {};\n try {\n existing = JSON.parse(\n fs.readFileSync(snapshotPath, \"utf8\"),\n ) as RatesSnapshot;\n } catch {\n /* file doesn't exist yet */\n }\n\n let addedCount = 0;\n for (const currency of [\"USD\", \"GBP\", \"CHF\"]) {\n try {\n const newRates = await fetchEcbData(currency);\n const existing_ccy = existing[currency] ?? {};\n let added = 0;\n for (const [date, rate] of Object.entries(newRates)) {\n if (!existing_ccy[date]) added++;\n existing_ccy[date] = rate;\n }\n existing[currency] = existing_ccy;\n addedCount += added;\n } catch {\n stderr.write(`Failed to fetch ${currency} rates\\n`);\n }\n }\n\n fs.mkdirSync(path.dirname(snapshotPath), { recursive: true });\n fs.writeFileSync(\n snapshotPath,\n JSON.stringify(existing, null, 2) + \"\\n\",\n \"utf8\",\n );\n stdout.write(s.ratesUpdateDone(addedCount) + \"\\n\");\n stdout.write(s.ratesSnapshotWritten(snapshotPath) + \"\\n\");\n return 0;\n }\n\n stderr.write(\"Usage: minus-tracker rates --check | --update\\n\");\n return 2;\n}\n","import { resolveLocale, saveLocale } from \"../../i18n/settings.js\";\nimport type { LocaleStrings, SupportedLocale } from \"../../i18n/types.js\";\n\nconst SUPPORTED_LOCALES: SupportedLocale[] = [\"it\", \"en\"];\n\nexport async function runConfig(\n positional: string[],\n flags: Record<string, string | boolean>,\n s: LocaleStrings,\n stdout: NodeJS.WritableStream,\n stderr: NodeJS.WritableStream,\n): Promise<number> {\n if (flags[\"lang\"] !== undefined) {\n const lang = flags[\"lang\"] as string;\n if (!SUPPORTED_LOCALES.includes(lang as SupportedLocale)) {\n stderr.write(`Unsupported locale \"${lang}\". Use: it, en\\n`);\n return 2;\n }\n saveLocale(lang as SupportedLocale);\n stdout.write(s.configLangSet(lang as SupportedLocale) + \"\\n\");\n return 0;\n }\n\n if (flags[\"show\"]) {\n const locale = resolveLocale();\n stdout.write(s.configCurrentLang(locale) + \"\\n\");\n return 0;\n }\n\n stderr.write(\"Usage: minus-tracker config --lang <it|en> | --show\\n\");\n return 2;\n}\n"],"mappings":"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","names":["path","path","fs","path","fs","get","fs","fs","path","os","SUPPORTED_LOCALES"]}