Skip to content

feat: audit.py computes I1, I5 (narrow), B1, B4, B5 and F5 from a real workbook - #113

Merged
svedbg merged 1 commit into
mainfrom
feat/audit-py-b-f-i-checks
Sep 6, 2026
Merged

feat: audit.py computes I1, I5 (narrow), B1, B4, B5 and F5 from a real workbook#113
svedbg merged 1 commit into
mainfrom
feat/audit-py-b-f-i-checks

Conversation

@svedbg

@svedbg svedbg commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

Point 1 of the trz-expert evaluation: the model rewrites all 86 checks as prose every audit, measured weakest on the multi-step arithmetic in groups B, F and I. This covers the subset that's mechanical and safe for a generic tool — the same discipline k_checker.py already applies to K5/K6.

  • scripts/rates.py — every rate read fresh from references/stavki.md + references/stavki/*.md at call time, never hardcoded. Period lookups match a table row's own date range, so a period the reference has no row for returns None rather than reusing the nearest one.
  • scripts/audit.py — I1 (vertical net reconciliation), I5 narrow (sick pay with zero sick days), B1/B5 (minimum wage), B4 (maximum insurable, both over-cap and copied-from-wrong-half-year), F5 (ТЗПБ extracted algebraically, compared against the mapping's declared rate).

Left to the model, same reasoning as k_checker.py's K1/K3/K4/K7: B2/B3/B6 need a company-specific number mapping.yaml doesn't carry; F1/F2/F3/F4/F6/F7/F9's composition method needs a judgment call not safe yet; F8/F10 need information one workbook can't hold.

Real defects found by testing against generate_wide.py at scale (not just a hand fixture)

  1. A pre-existing preflight.py vocabulary bug: "НЕТО преди удръжки"/"НЕТО за изплащане" and "Вноски работодател ДОО+ТЗПБ"/"Вноски работодател общо" each collapsed into one concept, raising the blocking DUPLICATE_CONCEPT signal on every realistic fixture — stopping every check in this file cold before it could even run. Split now.
  2. B1/B5 false positive on part-time work: test/vedomost_05_2026.xlsx's own documented part-time row (4h vs 8h, explicitly "must produce no finding" in expected_findings.md) was flagged as a violation.
  3. B1/B5 false positive on partial-month attendance: fixing chore(actions): bump actions/setup-python from 5 to 7 #2 exposed the same gap for leave/sick days — 159 false positives at 200 seeds. Neither script has a public-holiday calendar, so both gates use the highest hours/days count on the sheet as the least-wrong full-time/full-month proxy — the mode was tried first and rejected (a small office where nobody has full attendance that month makes the mode itself partial).

Test plan

  • I1 106/106, B4 70/70 (both shapes), F5 94/94 across 200 seeds — exact match, zero false positives
  • B1/B5 zero false positives across 300 generate_wide.py seeds
  • Exact match against expected_findings.md's suite-1 fixture, including the part-time row staying silent
  • Proved the test has teeth: broke TOL to 50.0, watched 24 checks fail, restored it
  • Wired into CI (validate at 300 seeds, deep-suites at 3000), pre-commit hook, CLAUDE.md
  • Full pre-commit hook (including the new step) passes clean on this commit

🤖 Generated with Claude Code

https://claude.ai/code/session_01XvbzEbQwxwxxHAtXfPMrno

…l workbook

Point 1 of the trz-expert evaluation: the model rewrites all 86 checks as prose
every audit, and its measured accuracy is worst on the multi-step arithmetic in
groups B, F and I - exactly the checks this script covers, mechanically, the
same way k_checker.py already covers K5/K6.

Two new files:

- scripts/rates.py reads every rate audit.py needs fresh from
  references/stavki.md + references/stavki/*.md at call time, never hardcoded -
  the same "no rate from memory" rule applied to a script instead of the
  model's prose. Period-scoped lookups (minimum wage, maximum insurable income)
  match a table row's own date range rather than assuming a specific year, so a
  period the reference has no row for returns None instead of reusing the
  nearest one.
- scripts/audit.py: I1 (БРУТО minus contributions minus tax minus deductions
  chains to НЕТО), I5 narrow (sick pay accrued with zero sick days - the fuller
  "day columns sum to the month's calendar norm" needs a public-holiday
  calendar this script doesn't have), B1/B5 (main wage / insurable income
  against the minimum wage), B4 (insurable income against the maximum, both
  over the correct cap and copied from the neighbouring half-year's), F5 (ТЗПБ
  extracted algebraically from the employer's total contributions, compared
  against the mapping's declared rate).

Left to the model, for the same reason K1/K3/K4/K7 are left to k_checker.py:
B2/B3/B6 need a company-specific number mapping.yaml doesn't carry;
F1/F2/F3/F4/F6/F7/F9's composition/relief method needs a judgment call this
script isn't positioned to make safely yet; F8/F10 need information one
workbook doesn't hold; K1/K3/K4/K7/K8 are k_checker.py's own closed-vocabulary
risk, unchanged.

Testing this against test/generate_wide.py at scale, not only a hand-built
fixture, found three real defects before anyone relied on any of them:

1. preflight.py's CONCEPTS already collapsed "НЕТО преди удръжки" with "НЕТО за
   изплащане", and "Вноски работодател ДОО+ТЗПБ" with "Вноски работодател
   общо" - raising the blocking DUPLICATE_CONCEPT signal on every realistic
   generated fixture and stopping every check in this file cold. Split now,
   the same way "Клас %"/"Клас сума" already were.
2. B1/B5 compared against the full minimum wage with no notion of part-time
   work - test/vedomost_05_2026.xlsx's own documented part-time row (Стефка
   Ангелова, 4 hours against 8) caught this directly, flagged as a violation
   when expected_findings.md says explicitly it must raise nothing.
3. Fixing that exposed the same gap for partial-month attendance: a row that
   took leave or sick days has a legitimately prorated "основна", and
   generate_wide.py's own random leave/sick days produced 159 false B1
   findings at 200 seeds once checked at scale. Neither script has a
   public-holiday calendar to compute the true month norm, so both gates use
   the highest hours/days count declared on the sheet as the least-wrong
   stand-in for full-time/full-month - tried the modal (most common) value
   first and rejected it: in a small office where nobody that month has full
   attendance, the mode is itself a partial value.

Nine new preflight.py CONCEPTS entries from the previous commit made this
testable at all - the composition vocabulary they were added for is not yet
used by anything in this file.

Wired into CI (both the `validate` job at 300 seeds and `deep-suites` at
3000), the pre-commit hook (which now also runs it whenever
skills/trz-expert/scripts/ changes), and CLAUDE.md.

Verified: I1 106/106, B4 70/70 (both shapes), F5 94/94 across 200 seeds, zero
false positives on B1/B5 across 300 seeds, exact match against
expected_findings.md's suite-1 fixture including the part-time row. Confirmed
the test has teeth by breaking TOL to 50.0 and watching 24 checks fail before
restoring it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XvbzEbQwxwxxHAtXfPMrno
@svedbg
svedbg merged commit 5938e1b into main Sep 6, 2026
12 checks passed
@svedbg
svedbg deleted the feat/audit-py-b-f-i-checks branch September 6, 2026 09:46
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