Repository navigation
Replace NZ digest pins with legacy and selected-factor differentials - #1427
Merged
Merged
Conversation
The #1398 review found that two tests pinned digests of outputs that NZ resources feed, so any change to those inputs forced a re-pin unrelated to what the tests guard. They now compare against oracles instead. - Calibration columns: a frozen copy of 4f333f2's prepare_calibration_columns serves as the oracle. Tests compare the live code with it on the NZ fixture, on generated NZ-domain tables (valid and defective, empty, boxed NumPy identities and relabelled indexes) and on generated interval inputs. Each call gets its own copy of every argument, and the comparison includes what each call left in its copies. On today's bindings the frozen code reproduces the retired pin, 1b22f4e1. - NZ factor receipts: a receipt must match the receipt resolved from only its selected factor, for each aging profile, and must not change when the factor purpose vocabulary drops money_normalization_input. A Hypothesis property drops and reorders the unread factors. - The 1.654 scan reads JSON values. It finds the exact rate as a JSON number, a numerator/denominator object, or a whole numeric token in text (signs and exponents included), and ignores other numbers such as 1.6541 or 1.654e1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
nzhub/nz-digest-differentials
branch
from
October 30, 2026 03:00
08a2cd8 to
09dc3fb
Compare
MaxGhenis
added a commit
that referenced
this pull request
Oct 30, 2026
The round-2 review of #1427 approved with three nits: - The input snapshot used to_dict, which turns object-dtype pd.NA into None, so a write that swapped them went unseen. _lossless now encodes each scalar with its type and repr, and the index with its class, dtype, name and labels. Two examples put a missing amount on an excluded row. - A rate token could touch a letter, so v1.654, a1654e-3b and 1.654e+ matched. Tokens now need a non-word boundary on both sides; the matcher test covers those forms. - ruff format wanted a second blank line after the frozen fixture's header. The six frozen functions still match 4f333f2 byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 30, 2026
The round-2 and round-3 reviews of #1427 approved with nits: - The input snapshot used to_dict, which turns object-dtype pd.NA into None, and then str(dtype), which drops a categorical's categories and order flag and a string column's storage. _typed now encodes each scalar with its type and repr, each dtype by its repr, both axes by class, dtype, names and labels, and each frame's attrs and duplicate-label flag. Generated tables now give tenure a categorical dtype half the time, in any category order, ordered or not. Two examples put a missing amount on an excluded row. - A rate token could touch a letter, or start right after a sign that did, so v1.654, v-1.654, a1654e-3b and 1.654e+ matched. A token now needs a non-word boundary on both sides and can't follow a sign; the matcher test covers those forms. - ruff format wanted a second blank line after the frozen fixture's header. The six frozen functions still match 4f333f2 byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
nzhub/nz-digest-differentials
branch
from
October 30, 2026 03:58
2f82930 to
3d8610a
Compare
MaxGhenis
added a commit
that referenced
this pull request
Oct 30, 2026
The round-2 to round-4 reviews of #1427 approved with nits: - The input snapshot used to_dict, which turns object-dtype pd.NA into None, and then str(dtype), which drops a categorical's categories and order flag and a string column's storage. _typed now encodes each scalar with its type and repr, each dtype by its repr, both axes by class, dtype, names and labels, and each frame's attrs and duplicate-label flag. Generated tables now give tenure a categorical dtype half the time, in any category order, ordered or not. Two examples put a missing amount on an excluded row. - A rate token could touch a letter, or start right after a sign that did, so v1.654, v-1.654, a1654e-3b and 1.654e+ matched. A token now needs a non-word boundary on both sides. A sign after a digit separates two numbers, so both ends of the range 1.650-1.654 count; after a letter, a sign leaves no number, so v-1.654 holds none. The matcher test covers those forms. - ruff format wanted a second blank line after the frozen fixture's header. The six frozen functions still match 4f333f2 byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
nzhub/nz-digest-differentials
branch
from
October 30, 2026 04:13
3d8610a to
e0517e1
Compare
MaxGhenis
added a commit
that referenced
this pull request
Oct 30, 2026
The round-2 to round-4 reviews of #1427 approved with nits: - The input snapshot used to_dict, which turns object-dtype pd.NA into None, and then str(dtype), which drops a categorical's categories and order flag and a string column's storage. _typed now encodes each scalar with its type and repr, each dtype by its repr, both axes by class, dtype, names and labels, and each frame's attrs and duplicate-label flag. Generated tables now give tenure a categorical dtype half the time, in any category order, ordered or not. Two examples put a missing amount on an excluded row. - A rate token could touch a letter, or start right after a sign that did, so v1.654, v-1.654, a1654e-3b and 1.654e+ matched. A token now needs a non-word boundary on both sides. A sign after a digit separates two numbers, so both ends of the range 1.650-1.654 count; after a letter, a sign leaves no number, so v-1.654 holds none. The matcher test covers those forms. - ruff format wanted a second blank line after the frozen fixture's header. The six frozen functions still match 4f333f2 byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
nzhub/nz-digest-differentials
branch
from
October 30, 2026 04:26
e0517e1 to
583d9e2
Compare
The round-2 to round-4 reviews of #1427 approved with nits: - The input snapshot used to_dict, which turns object-dtype pd.NA into None, and then str(dtype), which drops a categorical's categories and order flag and a string column's storage. _typed now encodes each scalar with its type and repr, each dtype by its repr, both axes by class, dtype, names and labels, and each frame's attrs and duplicate-label flag. Generated tables now give tenure a categorical dtype half the time, in any category order, ordered or not. Two examples put a missing amount on an excluded row. - A rate token could touch a letter, or start right after a sign that did, so v1.654, v-1.654, a1654e-3b and 1.654e+ matched. A token now needs a non-word boundary on both sides. A sign after a digit separates two numbers, so both ends of the range 1.650-1.654 count; after a letter, a sign leaves no number, so v-1.654 holds none. The matcher test covers those forms. - ruff format wanted a second blank line after the frozen fixture's header. The six frozen functions still match 4f333f2 byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
nzhub/nz-digest-differentials
branch
from
October 30, 2026 04:42
583d9e2 to
010beac
Compare
MaxGhenis
marked this pull request as ready for review
October 30, 2026 04:47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Merged by the NZ hub (session local_e407a527) at 5f86383 with --merge. Gates: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status: Sol's check of 010beac approves with Needs Max: no. The head, 5f86383, merges main into that commit and changes none of this PR's lines; waiting on CI.
Follow-up to the #1398 merge review (nits N1 and N2). Two tests pinned SHA-256 digests of outputs that NZ resources feed, so any change to those inputs forced a re-pin that said nothing about what the tests guard. A third test searched raw text for
1.654. This PR replaces all three with differential or value-level checks. It changes tests only; no source or data file moves.Changes
test_calibration_columns.py::test_prepare_matches_pinned_legacy_output_digest(pin1b22f4e1…)tests/fixtures/calibration_columns_legacy.py.txtfreezes the six pure functions ofcalibration_columns.pyat 4f333f2, before 2f220f7 moved the predicates intocalibration_predicates. Three tests compare today'sprepare_calibration_columnsagainst it: on the NZ fixture (frame-equal and byte-equal), on generated NZ-domain tables (one run for valid tables and one per defect class), and on generated interval inputs.test_reference_profiles.py::test_nz_factor_receipts_are_byte_identical_after_additive_factors(three pins)money_normalization_input, as it stood before #1398. A Hypothesis property drops and reorders the unread factors."1.654" in path.read_text()scan intest_the_rate_has_one_source_and_the_bridge_points_to_it1.650-1.654count; after a letter, a sign leaves no number, sov1.654andv-1.654hold none. It must find exactlymoney_normalization.json→rate.text. A parametrized test pins the matcher on 38 cases.The legacy loader follows the
reference_selection_legacy.py.txtpattern. It execs the fixture with an explicit scope (math,Mapping,Integral,Real,Any,np,pd, andinterval_indicatorsandvalidate_interval_documentfromreference_intervals).reference_intervals.pyhasn't changed since 4f333f2 (git diff 4f333f2c origin/main -- …/reference_intervals.pyprints nothing). Each of the six frozen functions matches its 4f333f2 source segment byte for byte; the reviewer checked this independently._outcomedeep-copies every argument for each call, including theintervalsoption. It returns the call's result and a snapshot of what the call left in its own copies, both in a typed encoding:pd.NA,Noneandnp.int64(7)differ;orderedflag count, as does a string column's storage;attrsand duplicate-label flag.So neither implementation can change what the other reads, and a write to any of those parts of an input shows as a difference even when outputs agree. The encoding doesn't claim more: state that a repr hides, such as a MultiIndex level's dtype, stays out of view. The generated tables give
tenurea categorical dtype half the time, in any category order, ordered or not.Invariants
prepare_calibration_columnsand the frozen 4f333f2 version give the same outcome: the same typed snapshot of the prepared tables (values, dtypes, both axes,attrs) or the sameValueErrormessage, and the same final state of their inputs. The same holds for interval outputs on either entity. The equivalence covers valid bindings; malformed binding schemas already refuse differently (ValueErroragainstAttributeError) and stay outside it.nz/**/*.json, atmoney_normalization.json→rate.text, as a JSON number, a numerator/denominator object, or a whole numeric token in text.Evidence
1b22f4e11e6e6c901866010d709acadaaad638e0774e80751e0ff23257a2bde7. Today's three receipts also reproduce their retired hashes (b0f8648d…,8989fa75…,e246f615…).packages/*/srcplus the repo root), one file at a time with-n 0:test_calibration_columns.py: 104 passed (Hypothesis seeds 1427 and 1428);test_reference_profiles.py: 129 passed;test_nz_money_normalization.py: 55 passed.ruff check .passes, as doruff format --checkon the four changed files (including the fixture, which ruff reads as Python when named) andtools/ci_test_plan.py verify. A directory-wideruff format --check packages/microcosm-build/testsfails on seven files this PR doesn't touch; each is byte-identical to its merge-base version.ge→gtincalibration_predicatesnegative_ageclass)lt→leintervalsargument (Sol's case)currency_bridge.jsonrestates "Rate 1.654."currency_bridge.jsonrestates "Rate 1654e-3."currency_bridge.jsonadds{"numerator": 827, "denominator": 500}pd.NAforNonein its inputs before returning (round-2 case)currency_bridge.jsonmentions 1.6541 and 1.654e1currency_bridge.jsonmentions "Dependency version v1.654." (round-2 case)orderedflag before returningattrsentry on its input frames before returningcurrency_bridge.jsonmentions "Dependency version v-1.654.",a-1.654andv-1654e-3(round-3 cases)currency_bridge.jsonrestates "Rate (+1.654)."currency_bridge.jsongives the range "1.650-1.654" (round-4 case)currency_bridge.jsongives the range "1.700-1.654" (round-4 case)currency_bridge.jsongives the sum "0+1.654" (round-4 case)currency_bridge.jsonmentions the dates 2026-04-01 to 2026-11-03In the first full run, the negative-age mutant survived: 60 examples spread over 11 defect classes didn't always draw it. Each defect class now gets its own 15-example run, and each run asserts that the live code refuses (or, for valid tables, prepares) before comparing it with the oracle.
git grepfinds no remaining reference to the retired test names or the four retired digests.Review
An independent GPT-6.1 Sol review at 08a2cd8 requested changes:
intervalsto both calls uncopied, so a live call that rewrote it also changed the oracle's input.1654e-3, and matched1.654e1and-1.654inside longer numbers.Round 2 (09dc3fb) fixed all four; the mutant table includes Sol's interval case and both purpose-leak spellings.
Sol's round 2 at 09dc3fb found the four fixed and approved with three nits; round 3 fixes all three:
to_dictturned object-dtypepd.NAintoNone, so the input snapshot missed an NA-to-None write. The snapshot now encodes each value with its type, and two@examples put a missing amount on an excluded row.v1.654,a1654e-3band1.654e+matched. Tokens now need a non-word boundary on both sides, and the matcher test covers those forms.ruff format --checkwanted a second blank line after the fixture header. The six frozen functions still match 4f333f2 byte for byte.Sol's round 3 at 2f82930 found those three fixed and approved with three more nits; round 4 fixes them:
v-1.654,a-1.654andv-1654e-3matched as positive numbers. A token now can't follow a sign either.str(dtype)dropped a categorical's categories andorderedflag and a string column's storage, and the column axis lost its name. The snapshot now keeps each dtype's repr, both axes' names and each frame'sattrsand flag, and the body states what it covers.Sol's round 4 at 3d8610a found those three fixed, answered Needs Max: no, and approved with one nit, which round 5 fixes. The sign rule also hid the second end of a range such as
1.650-1.654or1.700-1.654, and the1.654in the sum0+1.654. A sign that follows a digit now separates two numbers, and the matcher test covers both ranges, the sum, a date range and+-1.654.Sol's round 5 at e0517e1 found that fix sound, answered Needs Max: no, and approved with one wording nit in the regex comment. 583d9e2 rewords that comment and credits
_text_numbers, not the regex, with skipping2.1.654; comments alone change, and the file's AST and non-comment tokens match e0517e1's. Sol's check of 583d9e2 agreed and flagged the same "X included" wording in two docstrings. The head, 010beac, rewords both; with docstrings blanked, both files' ASTs match 583d9e2's.Out of scope
N3 from the same review stays open: CI never sets
NZ_CHRONICLE_ARTIFACT, so the generator-reproduction tests skip there.CI
Run 41087535556 at 5f86383 is pending. Earlier runs failed one engine-free (3.13) test:
test_spec_source_citations.py::test_a_long_string_is_scanned_in_linear_time, a 2 s CPU limit on the citation scanners. This PR doesn't touch that test or those scanners, and #1421 dropped the test from main, so this head no longer runs it.🤖 Generated with Claude Code