Files
accounted/components/reports
Mattsson 799fa1246a fix(vat): downgrade per-voucher RC basis gaps only under per-rate evidence (#1464)
* fix(vat): downgrade per-voucher RC basis gaps only under per-rate evidence

Per-voucher RC basis gap findings (findRcBasisGaps) blocked "Skicka till
Skatteverket" as ERROR even when the flagged vouchers were legitimate
moms-only rattelseverifikat whose basbelopp lives in another (often
reversed) verifikat. In that state no arrangement of vouchers satisfies
both the per-voucher scan and the aggregate basis/moms identity, so the
block was unfixable: every correction voucher joined the blocklist it
was meant to clear (Orto Engineering 3DJake support case, 2026-08).

The gap finding now downgrades to a non-blocking WARNING only when ALL
of the following hold, otherwise the blocking ERROR stays exactly as
before:

- the 44xx/45xx RC basis accounts, grouped per momssats
  (RC_BASIS_ACCOUNTS_BY_RATE), match ruta 30/31/32 two-sided within a
  0.5 kr ore epsilon per rate;
- no moms box (ruta 30/31/32) is negative;
- the aggregate RC_OUTPUT_MISSING check has not fired;
- the caller supplied the evidence at all (older wire payloads and
  totals-less contexts keep the blocking behavior).

A first cross-rate-sum predicate was refuted by adversarial review: a
wrong-rate fiktiv moms voucher (12% moms "covered" by a 25% basis)
reached parity and unblocked a 7 800 kr under-declaration, and a
net-negative rate box made the summed comparison vacuous (textbook
FK004 state filing). Rutor 20-24 are partitioned by purchase type, not
rate, so the certificate must come from account totals; both
counterexamples plus the tolerance-hole case (shortfall inside the
aggregate 0.5% tolerance still blocks) are locked in as regression
tests.

The evidence travels as rcBasisByRate on the declaration payload
(rcBasisTotalsByRate projection), consumed by the web view and the MCP
completeness checks; rc-basis-gaps.ts derives its flat account set from
the same rate-grouped single source so scan and evidence cannot drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(vat): refuse gap downgrade on non-finite evidence; pin the ore epsilon

Review findings, one pass:

- CodeRabbit (major): rcBasisByRate arrives as unvalidated JSON in the
  web view; a missing or non-numeric field made every per-rate
  comparison evaluate against NaN, which compares false and PASSED the
  predicate, relaxing the filing gate in the unsafe direction. The
  predicate now refuses the downgrade outright on any non-finite basis
  or moms figure, covering both the web and MCP callers.
- CodeRabbit (nit): added a 0.51 kr drift case so a future widening of
  the 0.5 kr epsilon fails a test instead of slipping through green.

Declined with reasons (recorded in the PR summary): requiring textual
voucher-to-voucher references before downgrading (belongs to the
rattelse documentation flow, and would reintroduce the unfixable block
this PR removes); epsilon stacking across rates (max 1.5 kr, immaterial
at whole-krona filing and below the aggregate tolerance); explicit
negative-basis guard (all negative-basis paths already block via the
two-sided mismatch or the negative-moms guard, now plus the finite
guard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 14:45:22 +02:00
..
2026-07-21 23:00:15 +02:00