614b7e60b9
* fix(salary): apply percent brackets for monthly incomes above 80 000 kr
Skatteverket's monthly tax tables switch from fixed krona amounts to
percent-of-income rows above 80 000 kr/month. The lookup only loaded the
krona ("30B") rows and clamped higher incomes to the last bracket,
under-withholding every salary above 80 000 kr (e.g. 100 000 kr, tabell
31 kolumn 1: 25 294 kr instead of 35 000 kr).
- fetch both 30B and 30% sections from the Skatteverket API; treat a
missing section as API failure so the bundled fallback wins over
incomplete data
- TaxTableRate is a discriminated union; percent brackets withhold
percent of the whole monthly income, ore dropped per SFF 2011:1261
22 kap. 1 (oretal bortfaller)
- fallback generator parses %-rows too; regenerated with 1 232 percent
rows and a guard that every table carries both sections
- keep the old clamp only as a warn-logging last resort
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(salary): fail loudly on incomplete tax table data (review findings)
Address CodeRabbit and Swedish accounting review findings on #1510:
- lookupTaxAmount throws TaxTableUnavailableError when loaded brackets
contain a gap instead of silently withholding 0
- a failed or empty pagination page fails the whole API fetch so the
bundled fallback serves complete data
- kolumn values are parsed strictly (decimal-aware, comma accepted);
malformed values fail the fetch instead of becoming 0 kr / 0 %
- importer rejects malformed column values instead of emitting 0
(regenerated fallback is byte-identical)
- close the bracket gap in the calculation-engine test fixture
- clarify the ore-truncation citation and use an absolute date in
DECISIONS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(salary): validate income boundaries in tax table parsers
Round-2 CodeRabbit finding on #1510: income boundaries were still parsed
with parseInt, which accepts "100abc" and turns garbage into 0 or an
open-ended bracket. Both the importer and the API loader now require
digits-only boundaries; an empty upper bound is legal only on percent
rows (the open-ended top row). Malformed API data fails the fetch so the
bundled fallback runs; malformed TXT data fails the import. Regenerated
fallback is byte-identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>