@gabrielerandelli/minus-tracker 0.8.1 → 0.8.2
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 +20 -20
- package/dist/cli/index.js +38 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +25 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +43 -5
- package/dist/mcp/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,16 +113,16 @@ npx @gabrielerandelli/minus-tracker calc trades.csv
|
|
|
113
113
|
|
|
114
114
|
### Utilizzo CLI
|
|
115
115
|
|
|
116
|
-
| Comando | Flag principali | Note
|
|
117
|
-
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
-
| `calc <file.csv>` | `--method LIFO\|FIFO` (default: LIFO), `--lang it\|en`, `--json`, `--export-dichiarazione [path]`, `--carry-forward` | Non aggiorna mai i tassi BCE da solo — esegui `rates --update` periodicamente; usa il sidecar di `classify` se presente
|
|
119
|
-
| `classify <file.csv>` | `--offline` | Classifica gli strumenti (Bucket A/B) e crea/aggiorna il sidecar `*.classify.json`
|
|
120
|
-
| `validate <file.csv>` | `--lang it\|en` | Exit 0 con avvisi; exit 1 in caso di errori bloccanti
|
|
121
|
-
| `rates --check` | — | Mostra la copertura della snapshot BCE in locale
|
|
122
|
-
| `rates --update` | — | Scarica i tassi aggiornati dall'API BCE
|
|
123
|
-
| `config --lang it\|en` | — | Salva la lingua preferita
|
|
124
|
-
| `config --show` | — | Mostra la lingua correntemente impostata
|
|
125
|
-
| `stress-test` | `--range N-M`, `--keep`, `--json`, `--output-dir` | Documentato in fondo
|
|
116
|
+
| Comando | Flag principali | Note |
|
|
117
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `calc <file.csv>` | `--method LIFO\|FIFO` (default: LIFO), `--lang it\|en`, `--json`, `--export-dichiarazione [path]`, `--carry-forward` | Non aggiorna mai i tassi BCE da solo — esegui `rates --update` periodicamente; usa il sidecar di `classify` se presente |
|
|
119
|
+
| `classify <file.csv>` | `--offline` | Classifica gli strumenti (Bucket A/B) e crea/aggiorna il sidecar `*.classify.json`. Richiede un terminale interattivo (TTY), oppure il flag `--offline` in contesti scriptati/CI |
|
|
120
|
+
| `validate <file.csv>` | `--lang it\|en` | Exit 0 con avvisi; exit 1 in caso di errori bloccanti |
|
|
121
|
+
| `rates --check` | — | Mostra la copertura della snapshot BCE in locale |
|
|
122
|
+
| `rates --update` | — | Scarica i tassi aggiornati dall'API BCE |
|
|
123
|
+
| `config --lang it\|en` | — | Salva la lingua preferita |
|
|
124
|
+
| `config --show` | — | Mostra la lingua correntemente impostata |
|
|
125
|
+
| `stress-test` | `--range N-M`, `--keep`, `--json`, `--output-dir` | Documentato in fondo |
|
|
126
126
|
|
|
127
127
|
Precedenza lingua: `--lang` > lingua salvata > italiano (default).
|
|
128
128
|
|
|
@@ -409,16 +409,16 @@ npx @gabrielerandelli/minus-tracker calc trades.csv
|
|
|
409
409
|
|
|
410
410
|
### CLI Usage
|
|
411
411
|
|
|
412
|
-
| Command | Key flags | Notes
|
|
413
|
-
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
414
|
-
| `calc <file.csv>` | `--method LIFO\|FIFO` (default: LIFO), `--lang it\|en`, `--json`, `--export-dichiarazione [path]`, `--carry-forward` | Never fetches ECB rates on its own — run `rates --update` periodically; uses `classify`'s sidecar when present
|
|
415
|
-
| `classify <file.csv>` | `--offline` | Classifies instruments (Bucket A/B) and creates/updates the `*.classify.json` sidecar
|
|
416
|
-
| `validate <file.csv>` | `--lang it\|en` | Exit 0 with warnings; exit 1 on hard errors
|
|
417
|
-
| `rates --check` | — | Shows bundled ECB snapshot coverage
|
|
418
|
-
| `rates --update` | — | Fetches fresh rates from the ECB API
|
|
419
|
-
| `config --lang it\|en` | — | Saves language preference
|
|
420
|
-
| `config --show` | — | Shows current language setting
|
|
421
|
-
| `stress-test` | `--range N-M`, `--keep`, `--json`, `--output-dir` | Documented below
|
|
412
|
+
| Command | Key flags | Notes |
|
|
413
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
414
|
+
| `calc <file.csv>` | `--method LIFO\|FIFO` (default: LIFO), `--lang it\|en`, `--json`, `--export-dichiarazione [path]`, `--carry-forward` | Never fetches ECB rates on its own — run `rates --update` periodically; uses `classify`'s sidecar when present |
|
|
415
|
+
| `classify <file.csv>` | `--offline` | Classifies instruments (Bucket A/B) and creates/updates the `*.classify.json` sidecar. Requires an interactive terminal (TTY), or the `--offline` flag in scripted/CI contexts |
|
|
416
|
+
| `validate <file.csv>` | `--lang it\|en` | Exit 0 with warnings; exit 1 on hard errors |
|
|
417
|
+
| `rates --check` | — | Shows bundled ECB snapshot coverage |
|
|
418
|
+
| `rates --update` | — | Fetches fresh rates from the ECB API |
|
|
419
|
+
| `config --lang it\|en` | — | Saves language preference |
|
|
420
|
+
| `config --show` | — | Shows current language setting |
|
|
421
|
+
| `stress-test` | `--range N-M`, `--keep`, `--json`, `--output-dir` | Documented below |
|
|
422
422
|
|
|
423
423
|
Language precedence: `--lang` flag > saved config > Italian (default).
|
|
424
424
|
|
package/dist/cli/index.js
CHANGED
|
@@ -383,6 +383,16 @@ var REQUIRED_COLUMNS = [
|
|
|
383
383
|
];
|
|
384
384
|
var INCOME_KEYWORDS = ["DIVIDEND", "COUPON", "INTEREST", "CEDOLA"];
|
|
385
385
|
var TAX_KEYWORDS = ["DIVIDEND TAX", "WITHHOLDING", "RITENUTA"];
|
|
386
|
+
var BINARY_GARBAGE_CONTROL_CHAR_RATIO = 0.1;
|
|
387
|
+
function looksLikeBinaryGarbage(firstLine) {
|
|
388
|
+
if (firstLine.length === 0) return false;
|
|
389
|
+
let controlChars = 0;
|
|
390
|
+
for (let i = 0; i < firstLine.length; i++) {
|
|
391
|
+
const code = firstLine.charCodeAt(i);
|
|
392
|
+
if (code < 32 && code !== 9) controlChars++;
|
|
393
|
+
}
|
|
394
|
+
return controlChars / firstLine.length > BINARY_GARBAGE_CONTROL_CHAR_RATIO;
|
|
395
|
+
}
|
|
386
396
|
function parseCSVRow(line) {
|
|
387
397
|
const fields = [];
|
|
388
398
|
let i = 0;
|
|
@@ -445,6 +455,10 @@ var DEGIROParser = class {
|
|
|
445
455
|
if (typeof csv !== "string" || csv.includes("\0")) {
|
|
446
456
|
throw new ParseError("INVALID_CSV");
|
|
447
457
|
}
|
|
458
|
+
const firstLine = csv.split("\n", 1)[0] ?? "";
|
|
459
|
+
if (looksLikeBinaryGarbage(firstLine)) {
|
|
460
|
+
throw new ParseError("INVALID_CSV");
|
|
461
|
+
}
|
|
448
462
|
let rows;
|
|
449
463
|
try {
|
|
450
464
|
rows = parseCSV(csv);
|
|
@@ -960,12 +974,19 @@ var Calculator = class {
|
|
|
960
974
|
);
|
|
961
975
|
let remaining = bPlusvalenze - bMinusvalenze;
|
|
962
976
|
let carryForwardApplied = 0;
|
|
977
|
+
const carryForwardEntriesRemaining = [];
|
|
963
978
|
for (const entry of carryForwards) {
|
|
964
979
|
if (taxYear - entry.year > 4) continue;
|
|
965
|
-
|
|
966
|
-
const consumed = Math.min(entry.amount, remaining);
|
|
980
|
+
const consumed = remaining > 0 ? Math.min(entry.amount, remaining) : 0;
|
|
967
981
|
carryForwardApplied += consumed;
|
|
968
982
|
remaining -= consumed;
|
|
983
|
+
const residual = roundHalfUp2(entry.amount - consumed);
|
|
984
|
+
if (residual > 0) {
|
|
985
|
+
carryForwardEntriesRemaining.push({
|
|
986
|
+
annoOrigine: entry.year,
|
|
987
|
+
importo: residual
|
|
988
|
+
});
|
|
989
|
+
}
|
|
969
990
|
}
|
|
970
991
|
carryForwardApplied = roundHalfUp2(carryForwardApplied);
|
|
971
992
|
const bNetResult = roundHalfUp2(
|
|
@@ -977,6 +998,7 @@ var Calculator = class {
|
|
|
977
998
|
minusvalenze: bMinusvalenze,
|
|
978
999
|
carryForwardApplied,
|
|
979
1000
|
carryForwardRemaining,
|
|
1001
|
+
carryForwardEntriesRemaining,
|
|
980
1002
|
netResult: bNetResult
|
|
981
1003
|
};
|
|
982
1004
|
const allIncomeRows = this._options.incomeRows ?? [];
|
|
@@ -1021,7 +1043,7 @@ var Calculator = class {
|
|
|
1021
1043
|
const hasOther = allIsins.some((isin) => !isin.startsWith("IE"));
|
|
1022
1044
|
if (hasIePrefix && hasOther) {
|
|
1023
1045
|
warnings.push(
|
|
1024
|
-
"
|
|
1046
|
+
"WARNING: CSV contains mixed instrument types (e.g. ETFs + Stocks).\n Single-bucket calculation may not be fiscally correct.\n Run: minus-tracker classify trades.csv"
|
|
1025
1047
|
);
|
|
1026
1048
|
}
|
|
1027
1049
|
}
|
|
@@ -2370,6 +2392,7 @@ async function runStressTest(_positionals, flags, stdout, stderr) {
|
|
|
2370
2392
|
const cliBin = process.argv[1];
|
|
2371
2393
|
let outputDir;
|
|
2372
2394
|
const outputDirFlag = flags["output-dir"];
|
|
2395
|
+
const isAutoGenerated = !outputDirFlag;
|
|
2373
2396
|
if (outputDirFlag) {
|
|
2374
2397
|
outputDir = outputDirFlag;
|
|
2375
2398
|
fs7.mkdirSync(outputDir, { recursive: true });
|
|
@@ -2410,8 +2433,15 @@ async function runStressTest(_positionals, flags, stdout, stderr) {
|
|
|
2410
2433
|
} else {
|
|
2411
2434
|
stdout.write(formatTable(report) + "\n");
|
|
2412
2435
|
}
|
|
2413
|
-
if (
|
|
2414
|
-
|
|
2436
|
+
if (isAutoGenerated) {
|
|
2437
|
+
if (!flags["keep"]) {
|
|
2438
|
+
fs7.rmSync(outputDir, { recursive: true, force: true });
|
|
2439
|
+
}
|
|
2440
|
+
} else if (!flags["keep"]) {
|
|
2441
|
+
stdout.write(
|
|
2442
|
+
`Note: --output-dir was user-specified \u2014 leaving ${outputDir} in place.
|
|
2443
|
+
`
|
|
2444
|
+
);
|
|
2415
2445
|
}
|
|
2416
2446
|
return report.failed === 0 ? 0 : 1;
|
|
2417
2447
|
}
|
|
@@ -2457,43 +2487,9 @@ async function runClassify(positional, flags, s, stdout, stderr) {
|
|
|
2457
2487
|
const sidecarPath = base + ".classify.json";
|
|
2458
2488
|
if (offline) {
|
|
2459
2489
|
stdout.write(s.classifyOfflineWarning + "\n");
|
|
2460
|
-
const
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
isinToProduct.set(tx.isin, tx.product);
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
const classifications = {};
|
|
2467
|
-
for (const [isin, product] of isinToProduct.entries()) {
|
|
2468
|
-
classifications[isin] = {
|
|
2469
|
-
product,
|
|
2470
|
-
assetClass: "ETF",
|
|
2471
|
-
bucketGain: "A",
|
|
2472
|
-
bucketLoss: "B",
|
|
2473
|
-
taxRate: 0.26,
|
|
2474
|
-
whiteListed: null,
|
|
2475
|
-
confirmedByUser: true,
|
|
2476
|
-
source: "user"
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
const sidecarContent = JSON.stringify(
|
|
2480
|
-
{
|
|
2481
|
-
version: 1,
|
|
2482
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2483
|
-
classifications,
|
|
2484
|
-
warnings: []
|
|
2485
|
-
},
|
|
2486
|
-
null,
|
|
2487
|
-
2
|
|
2488
|
-
);
|
|
2489
|
-
try {
|
|
2490
|
-
fs8.writeFileSync(sidecarPath, sidecarContent, "utf-8");
|
|
2491
|
-
} catch {
|
|
2492
|
-
stderr.write(`Cannot write sidecar: ${sidecarPath}
|
|
2493
|
-
`);
|
|
2494
|
-
return 1;
|
|
2495
|
-
}
|
|
2496
|
-
stdout.write(s.classifyWritten(sidecarPath) + "\n");
|
|
2490
|
+
const classifier2 = new Classifier({ interactive: false });
|
|
2491
|
+
await classifier2.classify(transactions, sidecarPath, { offline: true });
|
|
2492
|
+
stdout.write(s.classifyWritten(path6.resolve(sidecarPath)) + "\n");
|
|
2497
2493
|
return 0;
|
|
2498
2494
|
}
|
|
2499
2495
|
const classifier = new Classifier({ interactive: true });
|