Skip to content

fix: resolve full code-review findings (fiscal correctness, parsers, generators, web)#183

Merged
GeiserX merged 2 commits into
mainfrom
fix/review-findings
May 29, 2026
Merged

fix: resolve full code-review findings (fiscal correctness, parsers, generators, web)#183
GeiserX merged 2 commits into
mainfrom
fix/review-findings

Conversation

@GeiserX
Copy link
Copy Markdown
Owner

@GeiserX GeiserX commented May 29, 2026

Summary

Implements every finding from the whole-repo deep review (critical → nit). Focus was fiscal/legal correctness for Spanish IRPF, with parser robustness, AEAT format fixes, web hardening, and infra. Decomposed and executed in parallel, then verified by independent code-review / test / architecture passes.

Fiscal correctness (engine)

  • Savings brackets unified & year-parameterized — new src/engine/tax-brackets.ts is the single source of truth. Fixes the stale 28% top rate in the web tax-estimate card (2025+ is 30% per Ley 7/2024). Removes the table that was duplicated and divergent across charts.ts and double-taxation.ts.
  • Double taxation (Art. 80, Casilla 0588) — threads the tax year and adds a per-country treaty-rate cap (conservative 15% default) so excess foreign withholding is no longer over-credited.
  • Wash sale (Art. 33.5.f) — fixes calendar-month overflow in the window math, and applies the 1-year window to all unlisted assets (no-ISIN + crypto), not crypto only.
  • FIFO — option exercise now uses the strike price (not market price) for the resulting share basis.
  • ECB — validates CSV header / resolves columns by name (resilient to ECB column reordering) and guards against a 0 rate producing Infinity.
  • Crypto income — staking rewards paid in the coin (e.g. Kraken) have no ECB rate; the report now skips and warns (value manually, Casilla 0027) instead of crashing.

Parsers

  • parseNumber keeps European comma-decimal semantics; only multi-comma values are treated as thousands (avoids 1000× errors on broker prices like 2,082).
  • Trade Republic carries money as Decimal-from-string (no lossy parseFloat).
  • Scalable detects ETFs as FUND; Binance crypto carries empty ISIN (no false wash-sale keys); Kraken/Coinbase staking moved out of the dividend pool.

AEAT generators

  • Modelo 720 numPad rounds half-up instead of truncating cents; the holder field (pos 36-75) now carries the filer name, not the security description. 500-byte ISO-8859-15 layout preserved.
  • Interest guide box shows Casilla 0027 (was a stale 0033).

Web / security

  • Tax-bracket card derives from the shared year-keyed bands.
  • Profile is loaded from localStorage via a key whitelist (prototype-pollution safe).
  • Oversized uploads are rejected before parsing (DoS guard).

Infra / docs

  • nginx: Content-Security-Policy + related security headers.
  • CLAUDE.md: corrected supported-broker count.
  • Added tsconfig.test.json + typecheck:tests so tests are type-checked in CI.

Test plan

  • npx vitest run1145 passing (55 files), incl. new regression tests for every fix
  • npx tsc --noEmit -p tsconfig.json — clean
  • npx tsc --noEmit -p tsconfig.test.json — clean (tests now type-checked)
  • npm run build — web bundle builds

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for Trade Republic and Trading 212 broker CSV parsers (13 supported brokers total)
    • Implemented 50MB file upload size limit with user-friendly error messaging
    • Enhanced security with Content Security Policy headers
  • Bug Fixes

    • Corrected Spanish tax form field references (Casilla 0027 for interest income instead of 0033)
    • Reclassified crypto staking rewards as interest income instead of dividends
    • Improved European number parsing for decimal and thousands separators
    • Fixed FIFO cost-basis calculation for option exercises
    • Enhanced ECB exchange rate handling for unresolvable currencies
    • Fixed monetary value precision in Trade Republic parser
  • Documentation

    • Updated guidance text and translations across 5 languages
  • Tests & Tooling

    • Expanded test coverage for tax calculations and parser improvements
    • Added TypeScript type-checking script for test code

Review Change Stack

…tors, web

Fiscal-correctness and robustness fixes from a whole-repo review.

Engine:
- Add year-parameterized savings-bracket module (tax-brackets.ts) as the single
  source of truth; fixes stale 28% top rate in web charts (2025+ is 30% per Ley
  7/2024) and removes the divergent table previously duplicated in two places.
- double-taxation: thread tax year, add per-country treaty-rate cap (default 15%)
  so excess foreign withholding is not over-credited (Art. 80 LIRPF).
- wash-sale: clamp calendar-month arithmetic (no day overflow), apply the 1-year
  anti-churning window to all unlisted assets (no-ISIN + crypto), not crypto only.
- ecb: validate CSV header / look up columns by name, guard against rate=0.
- fifo: option exercise uses strike (not market price) for the share basis.
- Skip crypto-denominated income (e.g. Kraken staking, paid in the coin) that has
  no ECB rate; warn the user to value it manually instead of crashing the report.

Parsers:
- csv-utils parseNumber: keep European comma-decimal semantics; only treat
  multi-comma values as thousands (avoids 1000x errors on broker prices).
- trade-republic: carry money as Decimal-from-string, not lossy parseFloat.
- scalable: detect ETFs as FUND via the instrument-type column.
- kraken/coinbase: reclassify staking rewards out of the dividend pool.
- binance: crypto carries empty ISIN (avoids false wash-sale homogeneity keys).

Generators:
- modelo720: round (half-up) instead of truncating cents; write the filer name in
  the holder field (positions 36-75), keeping the 500-byte record layout.
- section-guide: interest box shows Casilla 0027 (was a stale 0033).

Web:
- charts: derive the tax-bracket card from the shared year-keyed bands.
- profile: load from localStorage via a whitelist (prototype-pollution safe).
- main: reject oversized uploads before parsing (DoS guard).

Infra/docs:
- nginx: add Content-Security-Policy and related security headers.
- CLAUDE.md: correct the supported-broker count.
- Add tsconfig.test.json + typecheck:tests so tests are type-checked in CI.

All 1145 tests pass; src and test typechecks clean.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 42805bad-60bf-492d-bd57-5de0788c4796

📥 Commits

Reviewing files that changed from the base of the PR and between 0bd38ea and 93dafa0.

📒 Files selected for processing (8)
  • .gitignore
  • CLAUDE.md
  • src/engine/ecb.ts
  • src/generators/modelo720.ts
  • src/generators/report.ts
  • src/web/profile.ts
  • tests/engine/ecb-fetch.test.ts
  • tests/generators/modelo720.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/generators/report.ts
  • tests/generators/modelo720.test.ts
  • src/generators/modelo720.ts

📝 Walkthrough

Walkthrough

This PR consolidates Spanish tax-year-aware dividend/interest handling across the report and parsing pipeline. It introduces a reusable tax-brackets module, refactors double-taxation to cap treaty withholding, reclassifies staking rewards as interest in multiple brokers, hardens number/ECB parsing, and improves FIFO/wash-sale logic with month-aware windows. Updates translations and UI to reference the correct tax form field (casilla 0027 instead of 0033) and enforces file upload size limits on the web frontend.

Changes

Core Tax & Report Engine

Layer / File(s) Summary
Tax bracket system foundation
src/engine/tax-brackets.ts, tests/engine/tax-brackets.test.ts
New module defines year-specific Spanish savings-income brackets (2021–2025+) with SavingsBand interface and progressive tax calculation by marginal band; tests verify yearly rate transitions and cumulative amounts.
Double taxation refactoring
src/engine/double-taxation.ts, tests/engine/double-taxation.test.ts
Function signature adds year parameter, introduces treaty dividend withholding caps (15% default, per-country overridable) that limit foreign creditable tax to min(actual withholding, grossIncome * treatyRate); test suite updated with 2025 year context and treaty-cap assertions.
ECB rate resilience and crypto filtering
src/engine/ecb.ts, src/engine/fx-fifo.ts, tests/engine/ecb-fetch.test.ts
fetchEcbRates becomes header-aware, detects data presence, locates TIME_PERIOD/OBS_VALUE by name, and skips rows with missing/invalid/zero values; FxFifoEngine skips crypto-denominated transactions from ECB lookup; tests verify header-driven column lookup, error handling, and zero-value skipping.
Report generation with crypto-income handling
src/generators/report.ts, tests/generators/report.test.ts
Filters interest entries whose currency is not ECB-resolvable, counts them separately, and emits a warning directing manual Casilla 0027 entry; invokes calculateDoubleTaxation with new year parameter; test verifies no crash on unresolvable currencies.

Parser Improvements

Layer / File(s) Summary
Parser income classification
src/parsers/coinbase.ts, src/parsers/kraken.ts, tests/parsers/coinbase*.test.ts, tests/parsers/kraken*.test.ts
Coinbase and Kraken updated to classify staking/rewards income as "Broker Interest Received" instead of "Dividends"; test suites updated to expect the new classification.
Trade Republic money precision
src/parsers/trade-republic.ts
Introduces numStr helper to normalize CSV money columns into Decimal-compatible strings instead of parseFloat numbers; all monetary fields (amount, shares, price, fee, tax) now parsed as strings to preserve full precision; test coverage maintained.
CSV number parsing ambiguity
src/parsers/csv-utils.ts, tests/parsers/csv-utils.test.ts
parseNumber now disambiguates single comma (decimal separator) from multiple commas (thousands separators) instead of always treating commas as decimal; tests verify comma-only format disambiguation for 1–4+ trailing digits.
Binance & Scalable asset classification
src/parsers/binance.ts, src/parsers/scalable.ts, tests/parsers/binance.test.ts, tests/parsers/scalable.test.ts
Binance crypto trades now emit empty isin (not symbol) to avoid wash-sale collisions; Scalable Capital now reads CSV assetType column and maps to assetCategory ("FUND" for ETF, "STK" default); tests updated.
eToro short position tests
tests/parsers/etoro-xlsx.test.ts
Added Spanish-format short trade tests validating opening/closing leg direction, indicator, and price ordering for profitable and losing scenarios.

FIFO & Wash-Sale Engine

Layer / File(s) Summary
Wash-sale month-aware windows and FIFO improvements
src/engine/wash-sale.ts, src/engine/fifo.ts, tests/engine/wash-sale.test.ts, tests/engine/fifo.test.ts, tests/engine/options-v0137-23.test.ts
Wash-sale detection uses new windowMonths(assetCategory, isin) helper (12 months for crypto/unlisted, 2 months for listed); new exported addMonths(date, months) clamps day-of-month to last valid target month; FIFO option-exercise/assignment now always uses contractual strike (not market price) for underlying cost basis; tests verify same-day lot ordering, reverse-split cost preservation, and option strike integration.

Modelo 720 & Output Precision

Layer / File(s) Summary
Modelo 720 rounding and name field
src/generators/modelo720.ts, tests/generators/modelo720.test.ts
numPad applies ROUND_HALF_UP before computing integer/fractional parts so rounding carry is reflected in both; buildDetailRecord populates holder name from config.surname + config.name instead of position description; added comprehensive test coverage for rounding behavior, integer overflow, and record-length validation.

Web Frontend & Profile

Layer / File(s) Summary
File upload size limiting and profile security
src/web/main.ts, src/web/profile.ts
Web entry point enforces 50 MB per-file limit, rejects oversized files, renders translated "file too large" message, and conditionally refreshes detection; getProfile() selectively copies only known FiscalProfile fields with runtime type checks instead of spreading JSON.
Tax bracket display and chart integration
src/web/charts.ts, src/web/section-guide.ts
Chart rendering imports getSavingsBands, generates dynamic bracket definitions via displayBrackets(year) with Spanish formatting and fixed colors; renderTaxBracketCard receives report.year as new argument; interest guide section updated to reference casilla 0027.

Translations & Configuration

Layer / File(s) Summary
Internationalization updates
src/i18n/locales/*.ts
All five locales (Català, English, Spanish, Euskara, Galician) add new error.file_too_large translation and update guide_rw.interest_importe_hint to reference DeclaRenta casilla 0027 instead of 0033.
Build and infrastructure
tsconfig.test.json, package.json, CLAUDE.md, nginx.conf, .gitignore
Added tsconfig.test.json for test compilation; added typecheck:tests npm script; updated CLAUDE.md to reflect 13 brokers (added Trade Republic, Trading 212); added nginx Content-Security-Policy header; updated .gitignore to exclude agent tooling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • GeiserX/DeclaRenta#129: Overlaps on src/engine/fifo.ts option-handling logic—main PR fixes option strike for cost-basis integration while retrieved PR overhauls the full options lifecycle.
  • GeiserX/DeclaRenta#135: Related to Trade Republic parser updates in src/parsers/trade-republic.ts for money field handling.
  • GeiserX/DeclaRenta#180: Both modify src/parsers/csv-utils.ts number parsing—main PR changes comma/decimal disambiguation while retrieved PR adds parenthesized-negative handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: resolving code-review findings across multiple domains (fiscal correctness, parsers, generators, web).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/review-findings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/web/main.ts (1)

294-324: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve the oversized-file warning when valid files already exist.

If the user already has pending files and then adds only oversized ones, Line 322 still calls updateDetectionStatus(), which immediately overwrites the warning from Lines 294-303. Gate this on accepted.length > 0, or keep the oversized warning in separate state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/web/main.ts` around lines 294 - 324, The oversized-file warning is being
overwritten because updateDetectionStatus() runs even when the current drop
contained only oversized files; change the final guard so detection is refreshed
only when there were accepted files in this add or when there are pending files
and no oversized files to display. Concretely, in the block after
renderFileList() adjust the conditional that calls updateDetectionStatus() to
require accepted.length > 0 OR (pendingFiles.length > 0 && oversized.length ===
0) so the message created using oversized and the "detection-status" element is
preserved; reference oversized, accepted, pendingFiles, and
updateDetectionStatus() when locating the code to modify.
src/web/charts.ts (1)

269-316: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep tax-bracket estimate math in Decimal (avoid Number drift)

In src/web/charts.ts (displayBrackets + renderTaxBracketCard), the code converts b.rate to number and performs the full bracket allocation, totalTax/netTax, and effectiveRate using JS Number. This violates the monetary-math rule and can desync the chart from the engine. Change renderTaxBracketCard to accept Decimal inputs (and/or use calculateSavingsTax(income: Decimal, year) from src/engine/tax-brackets.ts), keep all intermediate tax/rate/amount computations in Decimal, and only convert at the final formatting step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/web/charts.ts` around lines 269 - 316, The tax-bracket math currently
converts b.rate to Number in displayBrackets and runs all bracket allocation and
totals as JS Numbers in renderTaxBracketCard (variables rows, totalTax, netTax,
effectiveRate), which risks precision drift; keep all rates, amounts and tax
arithmetic in Decimal by returning Decimal rates from displayBrackets (or stop
converting b.rate.toNumber()), perform bracket allocation using Decimal math
inside renderTaxBracketCard (use Decimal arithmetic for remaining, width,
amount, tax, totalTax, netTax, effectiveRate) and only convert to Number/string
when formatting for output; alternatively call the engine helper
calculateSavingsTax(income: Decimal, year) to compute per-bracket Decimals and
derive rows for rendering so the chart stays in sync with the engine.
src/parsers/trade-republic.ts (1)

87-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep sign/zero checks on Decimal, not Number.

  • src/parsers/trade-republic.ts num() uses parseFloat(...) and returns number, and the parser then branches on those Numbers:
    • if (shares === 0) continue;
    • if (tax !== 0) with tax > 0 ? ... : ...
    • if (amount > 0)
  • Refactor to derive Decimal from numStr() and use isZero()/isPositive() for these control-flow checks.
♻️ Proposed fix
-/** Numeric value of a column, for sign/zero comparisons only (never money). */
-function num(fields: string[], col: number): number {
-  const v = field(fields, col);
-  if (!v) return 0;
-  return parseFloat(parseNumber(v)) || 0;
+/** Decimal value of a column for sign/zero comparisons. */
+function dec(fields: string[], col: number): Decimal {
+  return new Decimal(numStr(fields, col));
 }
@@
-    const amount = num(fields, cols.amount);
-    const shares = num(fields, cols.shares);
-    const tax = num(fields, cols.tax);
+    const amount = dec(fields, cols.amount);
+    const shares = dec(fields, cols.shares);
+    const tax = dec(fields, cols.tax);
@@
-      if (shares === 0) continue;
+      if (shares.isZero()) continue;
@@
-      const absShares = new Decimal(sharesStr).abs().toString();
-      const absAmount = new Decimal(amountStr).abs().toString();
+      const absShares = shares.abs().toString();
+      const absAmount = amount.abs().toString();
       const absFee = new Decimal(feeStr).abs();
-      const absTax = new Decimal(taxStr).abs();
+      const absTax = tax.abs();
@@
-      if (tax !== 0) {
+      if (!tax.isZero()) {
@@
-          amount: tax > 0 ? new Decimal(taxStr).abs().neg().toString() : taxStr,
+          amount: tax.isPositive() ? tax.abs().neg().toString() : taxStr,
@@
-      if (amount > 0) {
+      if (amount.isPositive()) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parsers/trade-republic.ts` around lines 87 - 92, The num() helper
currently returns a JavaScript number via parseFloat, which breaks the
requirement to do sign/zero checks using Decimal; change num() to return a
Decimal instead: inside num(fields, col) call the existing numStr(fields, col)
(or extract the string via field/parseNumber), construct a new Decimal(...) from
that string (return Decimal(0) when empty/invalid), and then update downstream
control-flow to use Decimal methods (isZero(), isPositive(), and
decimal.comparedTo(0) where needed) instead of numeric comparisons like === 0, >
0 or !== 0; keep function name num() and usages intact so callers get a Decimal
object for sign/zero checks.
src/parsers/scalable.ts (1)

11-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep Scalable monetary fields on Decimal end-to-end.

src/parsers/scalable.ts still converts parsed shares/fee (and also withholding tax) through parseFloat/Math.abs, then interpolates JS number values into emitted Trade.quantity/Trade.commission/withholding cashTransactions.amount, reintroducing binary rounding. Keep these values as Decimal until you emit strings.

Proposed fix
+import Decimal from "decimal.js";
 import type { BrokerParser, Statement } from "../types/broker.js";
 import type { Trade, CashTransaction } from "../types/ibkr.js";
 import {
   parseCsvLine,
   parseNumber,
@@
-    const sharesNum = parseFloat(parseNumber(shares));
-    if (sharesNum === 0) continue;
+    const sharesQty = new Decimal(shares);
+    if (sharesQty.isZero()) continue;
@@
-    const absShares = Math.abs(sharesNum);
-    const feeNum = parseFloat(parseNumber(fee));
+    const absShares = sharesQty.abs();
+    const feeAmount = new Decimal(fee);
@@
-      quantity: isSell ? `-${absShares}` : `${absShares}`,
+      quantity: isSell ? absShares.neg().toString() : absShares.toString(),
@@
-      commission: feeNum !== 0 ? `-${Math.abs(feeNum)}` : "0",
+      commission: feeAmount.isZero() ? "0" : feeAmount.abs().neg().toString(),

Also applies to: 140-155 (withholding taxNum block), in addition to 159-197 (trade block).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parsers/scalable.ts` around lines 11 - 19, The parser currently converts
Scalable monetary fields (shares, fee, withholding tax / taxNum) to native JS
numbers via parseFloat/Math.abs before populating Trade.quantity,
Trade.commission and cashTransactions.amount, which reintroduces binary
rounding; change the logic in the taxNum block and the trade block so you keep
parsed values as Decimal instances (use Decimal.abs/Decimal methods instead of
Math.abs/parseFloat) throughout processing and only serialize to strings when
assigning to the output fields (e.g., set Trade.quantity =
decimalQuantity.toString(), Trade.commission = decimalFee.toString(),
cashTransactions.amount = decimalTax.toString()); update any intermediate
variables referenced in these blocks (shares, fee, tax, taxNum, and where Trade
is constructed) to be Decimal types and remove parseFloat/Math.abs usage.
🧹 Nitpick comments (4)
tests/engine/double-taxation.test.ts (1)

23-154: ⚡ Quick win

Add one test for the totalSavingsBase branch.

Every case here calls calculateDoubleTaxation(entries, YEAR) without the third arg, so the effective-average-rate path used from src/generators/report.ts is still untested. A regression there would change Casilla 0588 while this suite stays green.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/engine/double-taxation.test.ts` around lines 23 - 154, Tests never
exercise the "totalSavingsBase" / effective-average-rate branch because
calculateDoubleTaxation is always called with two args; add one test that calls
calculateDoubleTaxation(entries, YEAR, someTotalSavingsBase) to trigger the
alternate code path (use the existing makeEntry helper to construct entries and
reuse YEAR), assert expected totals and per-country deductionAllowed/taxPaid for
that scenario, and place it alongside the other "it" cases to ensure the branch
in calculateDoubleTaxation (and the logic in src/generators/report.ts that reads
totalSavingsBase) is covered.
AGENTS.md (2)

104-111: 💤 Low value

Optional: Add language specification to checklist code block.

Same as SKILL.md: the session closing checklist code block should specify a language for consistent rendering.

📝 Optional fix
-```
+```text
 [ ] 1. git add + git commit
 [ ] 2. git push
 [ ] 3. gh pr checks <PR> --watch 2>&1 (IMPORTANT: WAIT for final summary, do NOT tell user it is done until you confirm it passes CI!)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 104 - 111, The session closing checklist code block
in AGENTS.md lacks a language specifier causing inconsistent rendering; update
the fenced code block surrounding the checklist (the "session closing checklist
code block") to include a language tag such as "text" (matching SKILL.md) so the
fence starts with ```text and the closing fence remains ```, ensuring consistent
formatting across docs.

255-272: ⚖️ Poor tradeoff

Optional: Fill in project-specific sections.

The placeholder sections (Build & Test, Architecture Overview, Conventions & Patterns) are clearly marked but could be populated with DeclaRenta-specific details if needed for agent context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 255 - 272, Populate the placeholder sections in
AGENTS.md: replace the "Build & Test" block with DeclaRenta's actual build and
test commands (install, build, test, CI steps), fill "Architecture Overview"
with a concise diagram/description of key components and data flow for
DeclaRenta (services, databases, agents), and document "Conventions & Patterns"
with your repo's coding standards, branching/PR rules, and naming/typing
patterns so agents can rely on them; update the exact headings "Build & Test",
"Architecture Overview", and "Conventions & Patterns" in AGENTS.md accordingly.
.claude/skills/tbd/SKILL.md (1)

111-118: 💤 Low value

Optional: Add language specification to checklist code block.

The fenced code block containing the session closing checklist has no language specified. Adding a language identifier (e.g., text or markdown) would improve rendering consistency.

📝 Optional fix
-```
+```text
 [ ] 1. git add + git commit
 [ ] 2. git push
 [ ] 3. gh pr checks <PR> --watch 2>&1 (IMPORTANT: WAIT for final summary, do NOT tell user it is done until you confirm it passes CI!)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/tbd/SKILL.md around lines 111 - 118, The fenced checklist
code block in SKILL.md lacks a language identifier; update the triple-backtick
fence that encloses the session closing checklist to include a language
specifier like `text` or `markdown` (e.g., change ``` to ```text) so the block
renders consistently—locate the checklist block in .claude/skills/tbd/SKILL.md
and add the language tag to the opening fence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/hooks/tbd-closing-reminder.sh:
- Around line 5-12: The hook currently uses jq to set command from input
(command=$(echo "$input" | jq -r '.tool_input.command // empty')), but jq may
not be installed; either ensure jq is provisioned in the session bootstrap or
make the hook robust: replace the jq usage with a fallback JSON extractor (e.g.,
try jq first and if missing use Python: command=$(echo "$input" | python3 -c
'import sys,json;
print(json.load(sys.stdin).get("tool_input",{}).get("command",""))') or
similar), and keep the subsequent git push detection logic (the checks against
"$command" and the tbd closing invocation) unchanged so the hook still triggers
when jq is absent.

In @.claude/scripts/tbd-session.sh:
- Around line 28-46: The bootstrap installs a floating get-tbd version; change
.claude/scripts/tbd-session.sh to read the pinned version from .tbd/config.yml
(tbd_version) into a shell variable (e.g., TBD_VERSION) and use that when
installing: append @${TBD_VERSION} to every package install invocation (npm
install -g get-tbd@${TBD_VERSION}, npm install --prefix ~/.local
get-tbd@${TBD_VERSION}, pnpm add -g get-tbd@${TBD_VERSION}, yarn global add
get-tbd@${TBD_VERSION}) and keep the existing local-install symlink logic
pointing to ~/.local/node_modules/.bin/tbd; ensure the script fails with a clear
error if tbd_version is missing or cannot be parsed.

In `@src/engine/ecb.ts`:
- Around line 102-103: The expression that computes hasDataRows uses unnecessary
optional chaining on l (lines.slice(1).some((l) => l?.trim())), but lines come
from csv.split("\n") so l is always a string; replace the predicate to call
trim() directly (e.g., (l) => l.trim() or Boolean(l.trim())) to satisfy the
no-unnecessary-condition lint rule and keep the same semantics; update the
hasDataRows calculation accordingly in the code that defines hasDataRows.

In `@src/generators/modelo720.ts`:
- Around line 220-225: After rounding the absolute Decimal `dec` to `decLen`
with Decimal.ROUND_HALF_UP, detect if the integer portion overflows its fixed
width: compute the integer string (e.g., from `dec.floor().toString()` or
`intPart` before padding) and if its length exceeds `intLen`, fail fast (throw
an Error or return a validation failure) instead of silently truncating/padding;
do this check prior to creating `intPart`/`fracPart` and ensure the thrown
message includes the offending `value`, `intLen`, and `decLen` for debugging.

In `@src/generators/report.ts`:
- Around line 176-185: The warning about crypto-denominated unresolvableInterest
is only being pushed into allMessages, so callers that read summary.warnings
won't see it; update the block that checks unresolvableInterest > 0 to also push
the same warning object into summary.warnings (or the warnings array that
populates summary.warnings) in addition to allMessages, referencing the existing
symbols unresolvableInterest, allMessages and summary.warnings (or the local
warnings accumulator) so both message lists are kept in sync.

In `@src/i18n/locales/es.ts`:
- Line 470: Summary: The coding guideline text still lists interest income as
casilla 0033 but should be updated to 0027. Fix: find the guideline string
"Casilla values: 0327-0328 (capital gains), 0029 (dividends), 0033 (interest
income), ..." in the documentation/generator content and change "0033 (interest
income)" to "0027 (interest income)"; ensure any nearby references or examples
that mention 0033 are updated consistently and add a short note or bead entry
indicating the guideline correction for future tracking.

In `@src/web/profile.ts`:
- Line 53: Replace the current non-integer-safe check for the year field by
validating that parsed.year is an integer: change the condition that sets
profile.year (currently "typeof parsed.year === 'number' &&
Number.isFinite(parsed.year)") to use Number.isInteger(parsed.year) so only
whole-year values are accepted when assigning profile.year.

In `@tests/engine/ecb-fetch.test.ts`:
- Around line 204-215: The test converts ECB rate strings with parseFloat and
compares with a hardcoded float which is precision-sensitive; update the
assertion to use Decimal for both the parsed rate and the expected reciprocal
(e.g., use Decimal to wrap rates.get("2025-01-02")!.get("USD")! and compute
Decimal(1).div(Decimal("1.035"))) and compare using Decimal's comparison or
toNumber with toFixed as appropriate. Modify the test in ec b-fetch.test.ts
around the fetchEcbRates(...) call and the rate assertion to import and use the
project's Decimal class (or decimal.js) instead of parseFloat and numeric
literals so the equality uses high-precision Decimal operations. Ensure you
reference fetchEcbRates and the rates Map key lookup when making the
replacement.

---

Outside diff comments:
In `@src/parsers/scalable.ts`:
- Around line 11-19: The parser currently converts Scalable monetary fields
(shares, fee, withholding tax / taxNum) to native JS numbers via
parseFloat/Math.abs before populating Trade.quantity, Trade.commission and
cashTransactions.amount, which reintroduces binary rounding; change the logic in
the taxNum block and the trade block so you keep parsed values as Decimal
instances (use Decimal.abs/Decimal methods instead of Math.abs/parseFloat)
throughout processing and only serialize to strings when assigning to the output
fields (e.g., set Trade.quantity = decimalQuantity.toString(), Trade.commission
= decimalFee.toString(), cashTransactions.amount = decimalTax.toString());
update any intermediate variables referenced in these blocks (shares, fee, tax,
taxNum, and where Trade is constructed) to be Decimal types and remove
parseFloat/Math.abs usage.

In `@src/parsers/trade-republic.ts`:
- Around line 87-92: The num() helper currently returns a JavaScript number via
parseFloat, which breaks the requirement to do sign/zero checks using Decimal;
change num() to return a Decimal instead: inside num(fields, col) call the
existing numStr(fields, col) (or extract the string via field/parseNumber),
construct a new Decimal(...) from that string (return Decimal(0) when
empty/invalid), and then update downstream control-flow to use Decimal methods
(isZero(), isPositive(), and decimal.comparedTo(0) where needed) instead of
numeric comparisons like === 0, > 0 or !== 0; keep function name num() and
usages intact so callers get a Decimal object for sign/zero checks.

In `@src/web/charts.ts`:
- Around line 269-316: The tax-bracket math currently converts b.rate to Number
in displayBrackets and runs all bracket allocation and totals as JS Numbers in
renderTaxBracketCard (variables rows, totalTax, netTax, effectiveRate), which
risks precision drift; keep all rates, amounts and tax arithmetic in Decimal by
returning Decimal rates from displayBrackets (or stop converting
b.rate.toNumber()), perform bracket allocation using Decimal math inside
renderTaxBracketCard (use Decimal arithmetic for remaining, width, amount, tax,
totalTax, netTax, effectiveRate) and only convert to Number/string when
formatting for output; alternatively call the engine helper
calculateSavingsTax(income: Decimal, year) to compute per-bracket Decimals and
derive rows for rendering so the chart stays in sync with the engine.

In `@src/web/main.ts`:
- Around line 294-324: The oversized-file warning is being overwritten because
updateDetectionStatus() runs even when the current drop contained only oversized
files; change the final guard so detection is refreshed only when there were
accepted files in this add or when there are pending files and no oversized
files to display. Concretely, in the block after renderFileList() adjust the
conditional that calls updateDetectionStatus() to require accepted.length > 0 OR
(pendingFiles.length > 0 && oversized.length === 0) so the message created using
oversized and the "detection-status" element is preserved; reference oversized,
accepted, pendingFiles, and updateDetectionStatus() when locating the code to
modify.

---

Nitpick comments:
In @.claude/skills/tbd/SKILL.md:
- Around line 111-118: The fenced checklist code block in SKILL.md lacks a
language identifier; update the triple-backtick fence that encloses the session
closing checklist to include a language specifier like `text` or `markdown`
(e.g., change ``` to ```text) so the block renders consistently—locate the
checklist block in .claude/skills/tbd/SKILL.md and add the language tag to the
opening fence.

In `@AGENTS.md`:
- Around line 104-111: The session closing checklist code block in AGENTS.md
lacks a language specifier causing inconsistent rendering; update the fenced
code block surrounding the checklist (the "session closing checklist code
block") to include a language tag such as "text" (matching SKILL.md) so the
fence starts with ```text and the closing fence remains ```, ensuring consistent
formatting across docs.
- Around line 255-272: Populate the placeholder sections in AGENTS.md: replace
the "Build & Test" block with DeclaRenta's actual build and test commands
(install, build, test, CI steps), fill "Architecture Overview" with a concise
diagram/description of key components and data flow for DeclaRenta (services,
databases, agents), and document "Conventions & Patterns" with your repo's
coding standards, branching/PR rules, and naming/typing patterns so agents can
rely on them; update the exact headings "Build & Test", "Architecture Overview",
and "Conventions & Patterns" in AGENTS.md accordingly.

In `@tests/engine/double-taxation.test.ts`:
- Around line 23-154: Tests never exercise the "totalSavingsBase" /
effective-average-rate branch because calculateDoubleTaxation is always called
with two args; add one test that calls calculateDoubleTaxation(entries, YEAR,
someTotalSavingsBase) to trigger the alternate code path (use the existing
makeEntry helper to construct entries and reuse YEAR), assert expected totals
and per-country deductionAllowed/taxPaid for that scenario, and place it
alongside the other "it" cases to ensure the branch in calculateDoubleTaxation
(and the logic in src/generators/report.ts that reads totalSavingsBase) is
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 69c8a2f7-1feb-4035-9077-b4203dffe703

📥 Commits

Reviewing files that changed from the base of the PR and between e3d87a0 and 0bd38ea.

📒 Files selected for processing (54)
  • .claude/.gitignore
  • .claude/hooks/tbd-closing-reminder.sh
  • .claude/scripts/ensure-gh-cli.sh
  • .claude/scripts/tbd-session.sh
  • .claude/settings.json
  • .claude/skills/tbd/SKILL.md
  • .tbd/.gitattributes
  • .tbd/.gitignore
  • .tbd/config.yml
  • AGENTS.md
  • CLAUDE.md
  • nginx.conf
  • package.json
  • src/engine/double-taxation.ts
  • src/engine/ecb.ts
  • src/engine/fifo.ts
  • src/engine/fx-fifo.ts
  • src/engine/tax-brackets.ts
  • src/engine/wash-sale.ts
  • src/generators/modelo720.ts
  • src/generators/report.ts
  • src/i18n/locales/ca.ts
  • src/i18n/locales/en.ts
  • src/i18n/locales/es.ts
  • src/i18n/locales/eu.ts
  • src/i18n/locales/gl.ts
  • src/parsers/binance.ts
  • src/parsers/coinbase.ts
  • src/parsers/csv-utils.ts
  • src/parsers/kraken.ts
  • src/parsers/scalable.ts
  • src/parsers/trade-republic.ts
  • src/web/charts.ts
  • src/web/main.ts
  • src/web/profile.ts
  • src/web/section-guide.ts
  • tests/engine/double-taxation.test.ts
  • tests/engine/ecb-fetch.test.ts
  • tests/engine/fifo.test.ts
  • tests/engine/options-v0137-23.test.ts
  • tests/engine/tax-brackets.test.ts
  • tests/engine/wash-sale.test.ts
  • tests/generators/modelo720.test.ts
  • tests/generators/report.test.ts
  • tests/parsers/binance.test.ts
  • tests/parsers/coinbase-branches.test.ts
  • tests/parsers/coinbase.test.ts
  • tests/parsers/csv-utils.test.ts
  • tests/parsers/etoro-xlsx.test.ts
  • tests/parsers/fixtures.test.ts
  • tests/parsers/kraken-branches.test.ts
  • tests/parsers/kraken.test.ts
  • tests/parsers/scalable.test.ts
  • tsconfig.test.json

Comment thread .claude/hooks/tbd-closing-reminder.sh Outdated
Comment thread .claude/scripts/tbd-session.sh Outdated
Comment thread src/engine/ecb.ts Outdated
Comment thread src/generators/modelo720.ts
Comment thread src/generators/report.ts
Comment thread src/i18n/locales/es.ts
Comment thread src/web/profile.ts Outdated
Comment thread tests/engine/ecb-fetch.test.ts Outdated
- ecb.ts: drop unnecessary optional chain (CI eslint failure)
- modelo720.ts: throw on integer-field overflow instead of silently
  widening the 500-byte record
- report.ts: surface crypto-income warning on legacy warnings[] too
- profile.ts: validate stored year is an integer, not just finite
- tests: use Decimal instead of parseFloat for ECB rate assertions;
  cover the modelo720 overflow guard
- remove local agent tooling (.claude/, .tbd/, AGENTS.md) wrongly
  committed; gitignore them
- CLAUDE.md: interest income casilla 0033 -> 0027
@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 98.96907% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.60%. Comparing base (9ad66ba) to head (93dafa0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/engine/ecb.ts 95.23% 1 Missing ⚠️
src/parsers/trade-republic.ts 96.15% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #183      +/-   ##
==========================================
+ Coverage   97.57%   97.60%   +0.03%     
==========================================
  Files          39       40       +1     
  Lines        8408     8519     +111     
  Branches     1719     1750      +31     
==========================================
+ Hits         8204     8315     +111     
  Misses        204      204              
Files with missing lines Coverage Δ
src/engine/double-taxation.ts 100.00% <100.00%> (ø)
src/engine/fifo.ts 88.67% <100.00%> (-0.03%) ⬇️
src/engine/fx-fifo.ts 97.68% <100.00%> (+0.01%) ⬆️
src/engine/tax-brackets.ts 100.00% <100.00%> (ø)
src/engine/wash-sale.ts 95.00% <100.00%> (+1.00%) ⬆️
src/generators/modelo720.ts 99.27% <100.00%> (+0.01%) ⬆️
src/generators/report.ts 100.00% <100.00%> (ø)
src/i18n/locales/ca.ts 100.00% <100.00%> (ø)
src/i18n/locales/en.ts 100.00% <100.00%> (ø)
src/i18n/locales/es.ts 100.00% <100.00%> (ø)
... and 9 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@GeiserX GeiserX merged commit 7c3a7c7 into main May 29, 2026
5 checks passed
@GeiserX GeiserX deleted the fix/review-findings branch May 29, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant