fix(bookkeeping): classify template audit evidence (#1594)

This commit is contained in:
Mattsson
2026-08-13 18:58:09 +02:00
committed by GitHub
parent d02fd82191
commit e494662530
4 changed files with 489 additions and 98 deletions
+64 -20
View File
@@ -48,11 +48,13 @@ anything.
`gnubok_correct_entry`. Never `gnubok_reverse_journal_entry` alone: the
business event remains valid, only its classification is wrong.
- Do not write directly to `journal_entries` or `journal_entry_lines`.
- Any `effective_lock_status` other than `open` is a hard stop. The correction
path never bypasses a lock. Unlocking or reopening requires a separately
reviewed and explicitly approved workflow for the exact company and period.
If a declaration or closing has relied on the period, establish the required
omprövning or closing consequences before requesting that approval.
- Any `effective_lock_status` other than `open` is a hard stop for the current
`gnubok_correct_entry` path. Never unlock, reopen or edit the original merely
to make a correction possible. A locked-period correction requires a
separately reviewed and tested path that records a storno and replacement in
an open period, references the original, and preserves who corrected it and
when. If a declaration or closing relied on the locked period, establish the
required omprövning or closing consequences before requesting approval.
- Never run a production correction without explicit approval for the exact
company, vouchers and replacement lines.
- No automated bulk mutation. Ever.
@@ -63,16 +65,41 @@ Run [`scripts/audit-template-misbookings.sql`](../scripts/audit-template-misbook
against the intended database. It is read-only, creates nothing, and returns
only posted entries.
**Output is diagnostic, never a verdict.** There is no provenance link from a
posted entry back to the template that produced it (`template_id` lives on
`mapping_rules`, not on journal entries), so candidates are matched by account
signature, and both signatures have legitimate shapes:
The audit now separates the broad signature from the evidence classification.
There is no direct template id on a journal entry, but `source_type`, exact line
shape, transaction context and linked underlag can rule candidates in or out.
Both signatures still have legitimate shapes:
- `5820` is the **correct** account for actual car hire.
- Representation at 25% is lawful only when the supply is actually subject to
25% VAT and the supplier invoice is correct.
Read `review_priority` as an ordering aid only:
Read `evidence_classification` as the remediation queue:
| Classification | Meaning |
|---|---|
| `confirmed_correction` | Exact defective shape plus corroborating evidence. Review the underlag, then stage `gnubok_correct_entry` and request explicit approval for the exact voucher and replacement lines. |
| `insufficient_evidence` | The shape matches, but the underlag or provenance is not decisive. Obtain evidence before proposing any write. |
| `false_positive` | Not attributable to either defective live template. This is not a general verdict that every aspect of the voucher is correct. |
`source_type` values `import` and `correction` are false positives for this
template-remediation audit because neither live template generated them. Keep
any independent accounting concern outside this remediation batch.
Linked underlag is resolved from direct journal-entry and journal-line links,
transactions and supplier invoices. Document identifiers and extracted VAT
signals are returned, but storage paths and file names are not. A decisive 25%
extraction on a representation voucher rules out the defective 12% restaurant
template signature. It does not by itself prove that the representation cap,
participant record or business purpose was handled correctly.
Every representation row therefore carries
`independent_representation_review = participants_purpose_and_300_sek_vat_base_cap_required`.
`observed_6072_cost_exceeds_300_sek` is a triage signal only: the statutory cap
is per person and occasion, so the number of participants and the underlag are
still required.
Read `review_priority` as a supporting ordering aid:
| Priority | Meaning |
|---|---|
@@ -82,14 +109,25 @@ Read `review_priority` as an ordering aid only:
| `manual_review_5820_no_corroborating_signal` | A `5820` entry with no hotel-shaped text or 12% VAT ratio. Most likely legitimate car hire. |
| `manual_review_6072_with_vat` | A `6072` entry with `2641` VAT whose aggregate ratio is not exactly 25%. It may be correct or may be a mixed voucher masking the defective representation leg. |
The classifier is verified against read-only probes: a hotel booked to `5820`
ranks high, a genuine car hire on `5820` falls to manual review, and an exact
25% representation signature ranks high. Correct 12% representation and mixed
vouchers whose aggregate ratio is not 25% remain visible for manual review,
because unrelated `2641` lines can otherwise hide a defective representation
component. The representation probes include multiple `6072` and `2641` lines;
the audit aggregates them per entry and returns one candidate instead of a
many-to-many set of line pairs.
The classifier is verified against read-only probes for confirmed, insufficient
and false-positive outcomes. The audit aggregates multiple `6072` and `2641`
lines per entry and returns one candidate instead of a many-to-many set of line
pairs. Mixed representation vouchers without decisive underlag remain
`insufficient_evidence`; they are never cleared merely because their aggregate
VAT ratio differs from the exact defective 25% signature.
Treat lock and VAT-return impact as overlays, not evidence classes:
- `effective_lock_status` identifies candidates that the current correction
service cannot handle. Never unlock or reopen merely to permit a correction.
- `vat_return_impact = potential_if_confirmed` means a representation candidate
could affect ruta 48. Determine the filing status before requesting approval.
- `vat_reporting_period`, `vat_deadline_statuses` and `vat_filing_status`
surface the in-app filing record. `not_proven_by_in_app_records` is unknown,
not proof that no declaration was filed externally.
- Keep exact company and voucher identifiers in an access-controlled approval
manifest. Public issue updates should use aggregate batches unless disclosure
has been explicitly approved.
## Review each candidate
@@ -107,8 +145,8 @@ many-to-many set of line pairs.
filed. If so, an omprövning is in scope and the ledger correction alone is
not sufficient.
6. Decide materiality. A 40 kr VAT difference on one lunch two years ago in a
closed period is unlikely to warrant reopening anything; a systematic error
across a year is different. Record the decision either way.
closed period may not warrant a separate correction in an open period; a
systematic error across a year is different. Record the decision either way.
Keep the reviewed set, the evidence, the proposed replacement lines and the
reviewer identity together as the correction record.
@@ -121,6 +159,12 @@ retain `original_lines`, copy every original line, change only what is wrong,
verify the replacement balances, stage
`gnubok_correct_entry`, and approve only with explicit authorisation.
`gnubok_correct_entry` routes through `correctEntry()` in
`lib/core/bookkeeping/storno-service.ts`. It retains the original, creates a
posted storno with `reverses_id`, creates the replacement with
`correction_of_id`, and records the acting user and timestamps. Never replace
this audited envelope with direct journal-table writes.
For the hotel defect, the only change is `account_number` on the cost line,
`5820` to `5830`.