Skip to content

Add IRD wage and salary distribution for TY2026 (chronicle#210 I2) - #328

Merged
MaxGhenis merged 6 commits into
mainfrom
nzhub/b3i2
Oct 10, 2026
Merged

MaxGhenis merged 6 commits into
mainfrom
nzhub/b3i2

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Part of #210, family I2. It follows the merged I1 package, ird/taxable_income_distribution_2025.

What this adds

packages/ird/wage_salary_distribution_2026 holds 510 administrative person observations from IRD's "Wage and salary distributions for individuals" workbook (August 2026 release) for the 2026 tax year (1 April 2025 to 31 March 2026):

  • 472 band facts: 235 income bands, each with a count and $M income, plus the separately published total count and total income;
  • 19 decile facts and 19 top-percentile facts: upper-boundary cut-points and $M income.

Notes:

  • Wage and salary income is gross PAYE earnings, per the workbook's explanatory notes. It excludes NZ Super, taxable benefits, student allowances, earnings-related ACC and shareholder-employee salaries.
  • Only the 2026 ("New") columns are emitted. The revised 2023–2025 columns, including TY2024, are preserved as source cells but not emitted. If Add IRD taxable-income and wage statistics for New Zealand tax year 2024 #210 wants TY2024 aligned with I1, that is a small follow-up.
  • The workbook's drawing note says 2022–2025 were revised, while its row-2 flags mark 2023–2025. Both are kept verbatim and not reconciled.

Source

  • Stable publisher URL (no ?modified= query): https://www.ird.govt.nz/-/media/project/ir/home/documents/about-us/tax-statistics---current/revenue-and-refunds/wage-and-salary/wage-and-salary-statistics/wage-and-salary-distributions-for-individuals.xlsx.
  • 198,542 bytes, SHA-256 d0526b20…cd9e750, fetched 2026-10-10.
  • Licence CC BY 4.0, per IRD's Crown copyright conditions, checked 2026-10-10.
  • The raw workbook is published to raw/nz/ird/ird-wage-salary-distribution-2026/…. The hub verified it by streaming it back (SHA-256 matches).

Invariants and how they're checked

These are scoped to this package's 510 facts:

  • One cell per fact. Every fact reads exactly one publisher cell. All 510 selected coordinates are audited against the unchanged workbook bytes.
  • Totals stay separate. The published totals (BB242 count 2,840,300; BC242 $199,691.5M) are their own facts, never sums of bands.
  • Cut-points. Decile and percentile upper boundaries are NZD cut-points (quantile aggregation), not shares. Decile 10 and percentile 100 publish no boundary, so none is emitted or imputed; their income facts stay.
  • Tax year. TY2026 is the ending year.
  • Precision. $M values keep the cached publisher precision, with value scale 1,000,000 and no rounding.
  • Labels. Open-ended bands keep their literal labels. Every dimension and value is labelled (Dimension labels are only checked for the UK suite: 68 of 79 other packages fail the consumer_fact.v2 rules #265).

Each guard has a regression test that fails on a mutated YAML or source cell, and passes on the accepted one:

  • 273 publisher header guards;
  • 9 YAML mutations: total kind, cut-point aggregation, blank boundary, tax year, scale, rounding, divisor, open-band label, dimension label.

Tests

  • tests/test_chronicle_nz_wage_salary.py: 300 passed. The build lane ran it, and the hub re-ran it on the final tree.
  • Independent coordinate map. The test declares all 510 expected (sheet, cell, unit, scale, aggregation) tuples from literal row ranges and a fixed column table, not from the package YAML, and requires the package to select exactly that set. Four in-memory mutants fail it: a count pointed at the income column, a boundary pointed at an income cell, the total pointed at a band row, and a percentile in the wrong column.
  • validate-package and build-suite pass: 510 facts, lineage coverage 1.0.
  • The full-bundle node is left to CI.

Bundle pins

Built by a GPT-6.1 Sol lane from the NZ hub's brief. The hub verified the test runs, artifact hashes and R2 object.

Chronicle governance

  • Agent role: ledger-source-ingestor. As in the other package PRs, the per-package files outside its allowed_paths are the alias entry in chronicle/source_package.py, db/data/**, docs/pe-nz-source-checklist.md, the changelog fragment and the bundle pins. There are no logic changes outside packages/.
  • Deterministic checks:
    • source-package validation: validate-package passes, with 0 errors and 0 warnings;
    • source-cell preservation: the complete workbook bytes and 20,631 cells are kept;
    • consumer-contract and raw-facts boundary validation: the package's build-suite acceptance gates pass inside the test file.
  • Judge questions. Neither independent review used the judge names as a formal verdict line, so this is the hub's reading of what they established:
    • ledger-source-fidelity: round 1 re-read all 510 selected cells against the workbook and found no wrong selection. Round 2, at 19c6c65c, states that all 510 values trace to publisher cells.
    • ledger-boundary: round 2 states that no reconciliation, aging, imputation, model binding or solver construction is introduced.
    • ledger-contract: no schema or consumer-contract change.
  • Receipts. Both reviews note that the hub's fetch and R2 read-back are attested here, not shown by a receipt kept in the repo. A follow-up will add retained receipts for the three new NZ packages.

Review

An independent GPT-6.1 Sol review at ba0485d6 returned APPROVE_WITH_NITS:

  • It re-read all 510 selected cells against the workbook and found no wrong selection.
  • All 295 tests passed, and each failed under mutation.
  • Nit 1, a test gap: a YAML override pointing a count at the income column passed every test, because expected coordinates came from the same YAML. The independent map above closes it.
  • Nit 2, a stale "R2 pending" line in the changelog: fixed.

A delta review at 19c6c65c returned APPROVE_WITH_NITS with no blocking defect:

  • 300 tests passed.
  • The round-1 mutant now fails, and so do three further mutants of the reviewer's own: swapped band rows, a changed unit and a changed scale.
  • The pins match main plus this package's delta.
  • Its nits are the governance metadata, now recorded above, and retained receipts, a follow-up.

🤖 Generated with Claude Code

MaxGhenis and others added 6 commits October 10, 2026 06:43
Register ird-wage-salary-distribution-2026, following the merged I1 package.
Preserve the complete 198,542-byte staged workbook unchanged and pin the hub
receipt: fetched 2026-10-10T08:31:07Z, SHA-256
d0526b2092f9fa2bec44e11984c0ef17fda788a742c1c7054d416f6a0cd9e750.
Use the stable publisher URL, CC BY 4.0 licence, and Chronicle build_r2_key.
R2 upload/remote checksum verification remain for the hub before merge.

Emit 510 administrative person observations for the ending 2026 tax year:
472 band facts (235 bands plus separate total, count and income), 19 decile
facts and 19 top-percentile facts. Preserve all 20,631 worksheet cells and
verbatim explanatory drawing notes and population footnotes. Historical and
revised columns remain source cells only. Rename checklist row 62 from
ird/wage_salary_distribution_2025 to ird/wage_salary_distribution_2026;
tick artifact/package boxes and leave R2 unchecked. Update the stale I1
sentence and add the changelog and source evidence note.

Invariants and checks:

- Every numeric fact reads one publisher cell. Audit all 510 unique selected
  coordinates against the unchanged workbook; direct-cell lineage/unit-scale
  regression and build-suite acceptance pass. All facts are administrative
  observations for persons, without PolicyEngine-computed values.
- Separately published totals remain independent BB242/BC242 facts, never sums
  of bands. Exact-cell and no-range-selector tests pass; total-kind YAML
  mutation fails its regression before the accepted YAML passes.
- Upper boundaries are NZD cut-points with aggregation quantile, never shares.
  Exact BB255/BB278 anchors and all boundary aggregation tests pass; changing
  quantile to sum fails its regression.
- Decile 10 BB264 and percentile 100 BB279 have no upper boundary. Both raw
  blanks remain source cells, both boundary facts stay absent, and their
  income facts remain present. Adding the blank percentile-100 boundary to
  scratch YAML fails the regression; no value is imputed.
- TY2026 means 1 April 2025 to 31 March 2026, the ending year. Header guards,
  coverage-date tests and build at bundle year 2023 retain tax_year:2026;
  changing the package period to 2025 fails its regression.
- $M cells retain exact cached publisher precision with scale 1000000 and no
  rounding; boundaries have scale 1. All 510 values equal the core's existing
  normalization of their cached source cells. Scale, rounding and divisor
  mutations each fail the regression. Source XML numeric lexemes remain in
  unchanged workbook bytes; no means, shares or uprated values are derived.
- Open bands retain literal publisher labels and categorical equality
  constraints; no endpoints are closed and no I1 bands are merged. Changing
  the top band label in scratch YAML fails the regression.
- Every dimension and value carries the explicit authored label. Complete
  consumer-contract/label tests pass; removing a dimension declaration fails.
- The drawing sentence naming revisions to 2022–2025 and row-2 R flags naming
  2023–2025 are both retained verbatim without reconciliation. Raw revision
  and explanatory-note tests pass; only New TY2026 facts are selected.
- Every required publisher guard has a regression: 273 independent coordinates
  cover all row/measure headers, years, New flag and population footnotes.
  Guard removal yields 274 expected failures; all 273 guards individually
  reject mutated source cells in the final green run.

Validation: validate-package CLI PASS (5 sets, 274 rows, 6 measures, 510 records,
5 regions, 0 errors/warnings). Targeted test file: 295 passed, 12 dependency
warnings; 10 scratch YAML red cases: 283 expected failures. Build-suite PASS,
all 12 acceptance gates true, 510 facts/records, 20631 cells, 510 constraints,
lineage coverage 1.0. Axiom validation was not run; acceptance records the
standard concept_alignment_validation_skipped warning. Ruff check/format
and git diff --check PASS. Local ledger-source-fidelity and ledger-boundary
reviews PASS. Full bundle/full-suite tests were not run; CI owns them.

Measured bundle delta (single-source build_bundle, year=2023):

- facts: +510; by_source ird: +510
- geographies: +0; new geography keys: []; by_geography country:NZ: +510
- publisher sources: +0 (ird already present)
- packages: +1 (ird-wage-salary-distribution-2026)
- tables: +1; by_source_table
  ird:Wage and salary distributions for individuals 2026: +510
- warnings: +0; errors: +0; skipped sources: +0
- period count: +0 (tax_year:2026 already present); by_period tax_year:2026: +510
- entity count: +0 (person already present); by_entity person: +510
- aggregate duplicate keys: +0; semantic duplicate keys: +0

Single-source standalone counts: 510 facts, 1 geography country:NZ, 1 source ird,
1 package, 1 table, 1 period tax_year:2026, 1 entity person, 0 warnings/duplicates.
Pins use baseline 36c0358 plus this measured delta: 1001258 facts, 319 packages,
314 tables, ird 4362, person 495562, country:NZ 4581; geography 20547, source 56,
period 511, entity 12, warning 117 counts unchanged. Hub can re-pin if parallel
packages land first. The 46,758,982-byte bundle output and pytest temp directory
were deleted; remaining scratch contains only small scripts and red evidence.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
The hub published the manifest-declared objects with chronicle publish-raw and
verified each by streaming it back from ledger-raw (SHA-256 matches) on
2026-10-10. Tick the checklist's raw/nz column and replace the lane's
upload-pending sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 (nit 2): the fragment still said the upload was pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dditively

Stack chronicle#328 on #330 so both land without a second two-hour bundle
run. tests/test_chronicle_bundle.py pins are main 36c0358 plus both measured
single-source deltas: facts 1000748 + 510 (IRD I2) + 112 (Treasury) = 1001370;
packages 318 + 2 = 320; tables 313 + 2 = 315; country:NZ 4071 + 622 = 4693;
ird 4362, treasury 139, person 495562, government 3562. Git merged the
identical 318->319 and 313->314 edits silently; corrected to 320 and 315.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Declare an independent 510-observation map from the committed workbook's
literal row ranges and fixed BB/BC columns. Check exact record IDs, sheet,
cell address, unit, scale and aggregation, plus emitted fact metadata.
Use the independent coordinates and scales for every value/lineage check.

Add four in-memory row-override regressions: count to income, boundary to
income, total to band, and wrong-year percentile column. All four mutants
fail the new positive test in its body, with no setup errors. Tracked YAML
and all 510 fact IDs, value types and exact values remain unchanged.

Validation: 300 targeted tests pass; Ruff check and format pass; git diff
--check passes. Only the wage/salary test file is changed. No full suite or
full-bundle node ran. Leave changes uncommitted for the NZ hub.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
MaxGhenis added a commit that referenced this pull request Oct 10, 2026
…tively

Stack chronicle#329 on #328 (which contains main at 3dce46a, including the
Budget 2026 Estimates package) so both land without another two-hour bundle
run each. tests/test_chronicle_bundle.py pins are that branch's plus this
package's measured single-source delta: facts 1001370 + 1413 = 1002783;
packages 321; tables 316; stats_nz 1498; country:NZ 4693 + 1413 = 6106;
household 154636; person 495562 + 473 = 496035; fiscal_year:2023 + 1413.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit e731fef into main Oct 10, 2026
3 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged by the NZ hub (session local_e407a527) at 19c6c65 with --merge. Gates: gh pr checks exit 0; MERGEABLE; not draft; no CHANGES_REQUESTED review. Reviewed head: 19c6c65. Independent GPT-6.1 Sol reviews: round 1 APPROVE_WITH_NITS at ba0485d (all 510 cells re-read against the workbook), round 2 APPROVE_WITH_NITS at 19c6c65 (300 tests; the round-1 mutant and three more now fail; pins = main + this package's delta). The reviewers marked 'Needs Max: yes' because the PR adds publisher observations to a public repo. The hub is not holding on that: these are official statistics republished unchanged under CC BY 4.0 with attribution, the round-1 reviews of the same content judged it as not needing Max, and it matches the NZ source packages already merged under the same gates (#209, #211, #318-#321, #330). No model output, estimate or claim of ours is published. chronicle#329 is stacked on this branch; the branch is kept until it lands.

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