Skip to content

Add SPF Finances municipal PIT surcharge rates, income year 2025 - #387

Merged
MaxGhenis merged 4 commits into
mainfrom
be/b2b-commune-surcharge
Oct 24, 2026
Merged

MaxGhenis merged 4 commits into
mainfrom
be/b2b-commune-surcharge

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Belgian transport port (ruling d048), source package B2b: municipal personal-income-tax surcharge rates by commune.

What it adds

packages/spf_finances/municipal_tax_rate_ay2026 (alias spf-finances-municipal-tax-rate-ay2026): FPS Finance's five-page PDF "Municipal tax rates (%) for tax assessment year 2026".

  • 565 facts, one per printed commune rate. Values are the printed percentages with decimal commas turned into points (7,5 → 7.5), no rounding. Zero is kept (Knokke-Heist 0,0).
  • unit: percent, aggregation: rate, entity: government, provenance_class: administrative.
  • Geography: the 565 Statbel NIS 2025 communes from statbel/population_structure_commune_2025.
    • Matching: 558 labels match a Statbel Dutch or French name exactly; 7 match after case folding, accent and punctuation removal. Both tiers are listed in the README and in commune_mapping_audit.csv.
    • Keys: all 565 geography keys already exist in the bundle (statbel-fiscal-income-2023-nis-2025), so the package adds no new geography keys.
    • Names: each fact carries SPF's printed name, as the harness requires of every publisher (chronicle#266). Nine differ from Statbel's: Bruxelles/Brussels, Waimes/Weismes and seven spelling or punctuation variants. The bundle reports these as conflicting_geography_name_across_packages warnings (chronicle/bundle.py). The audit keeps Statbel's name beside each.
    • Identity: the NIS code identifies the commune; the printed spelling is not a filter or constraint.
  • Locators: PDF page, column block, and start, end and rate text lines for every cell. CSV guard cells pin each selector to its NIS code, publisher label and locator.

Period

The PDF's assessment year 2026 taxes income of 2025. The facts follow the existing Belgian tax packages (statbel/fiscal_income_distribution_2023, spf_finances/pit_2023, statbel/fiscal_income_2023_nis_2025):

  • tax_year: 2025 (income year);
  • shared constraints income_year == 2025 ("Publisher income year") and assessment_year == 2026 ("Publisher assessment year"), evidenced by matching CSV columns;
  • legal vintage income_year_2025_assessment_year_2026;
  • source_period_label keeps "for tax assessment year 2026" verbatim.

No 2027 assessment-year rates are inferred for 2026 income.

Invariants

  • Bijection: 565 publisher labels map one-to-one onto the 565 NIS 2025 communes. An unresolved, ambiguous or duplicate label, or an incomplete match, stops the build.
  • Fidelity: each fact's value equals its printed token. The tests re-parse the PDF with layout extraction, independently of the generator.
  • Determinism: build_package.py regenerates the CSV, audit, manifest and source_package.yaml byte for byte.
  • Lineage: every fact has source rows and cells; build-suite lineage coverage is 1.0 with 0 acceptance errors.
  • Identity: respelling a commune never changes its fact keys. The test respells all 565 facts' names, filters and constraints as Statbel's name and casefolded, uppercased and accent-stripped forms. Moving a fact to another NIS code always changes its keys. The test fails on the 875dea6 shape. Hypothesis is not a Chronicle dependency, so this is an exhaustive check over a fixed set of respellings.

Checks

Check Result
chronicle validate-package … --year 2025 valid, 0 warnings
chronicle build-suite … --year 2025 valid; 565 facts, 1,130 constraints (the two year constraints per fact), 0 acceptance errors, lineage 1.0
ruff check / ruff format --check on new files pass
R2 ledger-raw: CSV 84fd7521…, PDF 051211d8… uploaded, SHA-256 readback verified 2026-10-24
Focused tests (tests/test_chronicle_spf_finances_municipal_tax_rate.py) all 11 test functions pass when called directly at 650620b (unchanged by the rebase); the pytest run waits on the host reservation, and CI's Chronicle surface job runs the file
Bundle pins in tests/test_chronicle_bundle.py (9323e9e) +565 facts, one source, one source table and one built package; tax_year:2025 +565; government +565; commune:11002 9 → 10; nine commune geography-name warnings (warning_count 123 → 132); warning-id initials 2, 5, 6. Measured with Chronicle's bundle functions and confirmed against #387's CI bundle on the old base. The full bundle test, replayed against #385's CI bundle plus #386's skipped packages and this package, passes; main's unchanged test fails against it. CI runs the full bundle test on this head
Independent review (GPT-6.1 Sol) 875dea6: REQUEST CHANGES (geography names; spelling in fact identity), fixed in 863e8ec. It found 565/565 rates exact and all mappings correct. Re-review of 863e8ec: APPROVE, no findings. 650620b adds the identity test; a delta review will cover it with the bundle pins

Rebase

Rebased from 200bf6d onto main ba2509a, after #385 and #386. git range-diff shows all three commits unchanged, and the package, data, changelog and package test are byte-identical to 650620b. The bundle pins are the only new commit.

Shared code

Only the SOURCE_PACKAGE_ALIASES registration line in chronicle/source_package.py.

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 24, 2026 04:16
Preserve all 565 commune rates printed in FPS Finance's assessment-year 2026
PDF, with exact page, column-block and text-line locators and a bijective
mapping to Statbel's NIS 2025 communes (558 exact, 7 orthographic).

Key the facts as tax_year 2025, the income year these rates tax, with
income_year == 2025 and assessment_year == 2026 constraints evidenced by
source-row columns and the legal vintage income_year_2025_assessment_year_2026,
as in statbel/fiscal_income_distribution_2023. The printed assessment-year
label is kept verbatim. The CSV and PDF are in R2 ledger-raw, verified by
SHA-256 readback.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address the review of 875dea6:
- Each fact's geography name is now the commune name SPF prints, as the
  harness requires of every publisher (chronicle#266). Nine differ from
  Statbel's (Bruxelles/Brussels, Waimes/Weismes and seven spelling or
  punctuation variants); the mapping audit keeps Statbel's name beside
  each, and the bundle reports them as cross-package name warnings.
- Drop the per-row publisher_commune filter and constraint and its
  dimension label. The NIS code identifies the commune, so a spelling no
  longer changes aggregate or semantic fact keys. The printed label stays
  in the CSV, the source-row id and a guard cell.

The regenerated CSV is in R2 ledger-raw under its new hash, verified by
SHA-256 readback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For all 565 facts, respell the commune wherever the fact carries it
(geography name, filter and constraint values) as Statbel's name and
casefolded, uppercased and accent- and punctuation-stripped forms; both
aggregate and semantic keys must not change. Moving a fact to another
NIS code must change both. The respelling fails on the 875dea6 shape,
where the printed name was a filter and constraint.

Hypothesis is not a Chronicle dependency, and adding one is a shared
dependency change, so the property is checked exhaustively over the
package's facts and a fixed set of respellings instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The package adds 565 government facts at tax_year:2025, one per existing
commune key, so geography_count is unchanged and commune:11002 goes from
9 to 10. It adds one source, one source table and one built package.
SPF spells nine communes unlike Statbel, and the package keeps SPF's
wording, so nine commune geography-name warnings are added (123 -> 132).

The deltas were measured with Chronicle's own bundle functions. A replay
of the full bundle test against #385's CI bundle, with #386's seven
skipped county packages and this package added, passes, and main's
unchanged test fails against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the be/b2b-commune-surcharge branch from 650620b to 9323e9e Compare October 24, 2026 08:17
MaxGhenis added a commit that referenced this pull request Oct 24, 2026
…ages

The two packages add 34,881 person facts at country:BE: 34,629 quarterly
daily-wage-class cells from 2023 Q1 to 2026 Q1, and 252 annual key
figures for 2020-2025. They add two sources, two source tables and two
built packages. They add no new geography, period or entity key and no
warning.

This stacks on #387's pins. The deltas were measured with Chronicle's
own bundle functions and confirmed against #388's CI bundle on the old
base. A replay of the full bundle test against #385's CI bundle, with
#386's skipped packages, #387's package and these two packages added,
passes, and #387's test fails against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 24, 2026
The package adds 35 calendar-2025 facts: 19 person, 2 benefit_unit
premium payments and 14 government expenditure. It adds one
statistical_scope geography, the German-speaking Community, plus one
source, one source table and one built package. It adds no warning.

This stacks on #388's pins. The deltas were measured with Chronicle's
own bundle functions and confirmed against #389's CI bundle on the old
base. A replay of the full bundle test against #385's CI bundle, with
#386's skipped packages and #387's, #388's and this package added,
passes, and #388's test fails against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 24, 2026 08:25
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Delta review at 9323e9e (GPT-6.1 Sol, Subfleet run 20261024-041803-chronicle387-review-r3): APPROVE, no findings. ledger-source-fidelity: PASS. ledger-boundary: PASS.

  • Rebase onto main ba2509a: git range-diff shows all three commits unchanged, and every package, data, changelog and test file has an identical blob to 650620b.
  • 650620b's identity test: non-vacuous. Restoring 875dea6's printed-name filter and constraint makes it fail.
  • Bundle pins (9323e9e): derived independently with Chronicle's bundle functions, and every changed pin matches. +565 facts, tax_year:2025 +565, government +565, commune:11002 9 → 10, nine commune geography-name warnings (123 → 132), and no geography-count change.
  • Earlier reviews: 875dea6 REQUEST CHANGES, fixed in 863e8ec; 863e8ec APPROVE.

This merges as a merge commit once CI passes at 9323e9e. #388 and #389 are stacked on it and merge after it, each at its own tested head.

@MaxGhenis
MaxGhenis merged commit 8f0cf21 into main Oct 24, 2026
3 checks passed
@MaxGhenis
MaxGhenis deleted the be/b2b-commune-surcharge branch October 24, 2026 11:20
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