From e48af5a0327458d2c0d84e95d766ca6a0e4430f3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 4 Nov 2026 18:12:23 -0500 Subject: [PATCH] Tighten NZ's ageing and declarations tests after #1488 and #1507 Cloud review 220's nit 1 on #1507: test_nz_reference_aging.py's test_p1_rounds_once_and_the_truncated_numerator_refuses used p1_ty2026_to_ty2027_central. Its truncated numerator differs from the declared one, but all three intermediate roundings match it, so the test couldn't tell one rounding from rounding N or the projected quarters to cents, or q to six digits. A new helper, _p1_numerators, computes each P1 profile's numerator under one half-even rounding and under truncation and those three roundings. The parametrized refusal test sets each differing alternative as the declared numerator and expects the resolver to refuse it. A feed-free test checks that the three profiles between them separate one rounding from every alternative. Review 216's nits 2 and 3 on #1488: - The docstring of test_every_calibration_node_has_its_own_standin_as_the_only_writer said composition checks the measurement proof on the skeleton alone. Since #1488, composition also runs _check_measurement_proofs on the extended graph, where the compiled graph has to prove each nz.v..calibrate producer. - add_measurement inserted a compile-only producer "until G declares one". NZ's transport graph and all eight calibration scenarios now declare the measurement through their stand-ins, so the helper returned at once on every call. It and its two calls go. #1507 left one assert unformatted in each of test_nz_reference_aging.py and test_nz_money_normalization.py; ruff format now formats both files, and neither syntax tree changes. Co-Authored-By: Claude Opus 5.5 --- .../shared/test_nz_money_normalization.py | 12 ++- .../shared/test_nz_reference_aging.py | 89 ++++++++++++++++--- .../shared/test_nz_transport_declarations.py | 42 ++------- 3 files changed, 92 insertions(+), 51 deletions(-) diff --git a/packages/microcosm-build/tests/engine_free/shared/test_nz_money_normalization.py b/packages/microcosm-build/tests/engine_free/shared/test_nz_money_normalization.py index 31708a6666..5a457382d9 100644 --- a/packages/microcosm-build/tests/engine_free/shared/test_nz_money_normalization.py +++ b/packages/microcosm-build/tests/engine_free/shared/test_nz_money_normalization.py @@ -480,10 +480,14 @@ def test_the_three_donor_factors_are_exact_and_only_money_may_apply_them(): assert receipt["target_schedule"] == ird["target_schedule"] for bound in ("lower", "upper"): root = Fraction(**receipt["root_bounds"][bound]) - assert Fraction(**receipt["numerator_mean_bounds"][bound]) == ( - Fraction("1700.68") - + Fraction("1714.86") * sum(root**index for index in range(3)) - ) / 4 + assert ( + Fraction(**receipt["numerator_mean_bounds"][bound]) + == ( + Fraction("1700.68") + + Fraction("1714.86") * sum(root**index for index in range(3)) + ) + / 4 + ) assert entry["declaration"]["purpose"] == "money_normalization_input" diff --git a/packages/microcosm-build/tests/engine_free/shared/test_nz_reference_aging.py b/packages/microcosm-build/tests/engine_free/shared/test_nz_reference_aging.py index fc3dec363e..b9d946f2e1 100644 --- a/packages/microcosm-build/tests/engine_free/shared/test_nz_reference_aging.py +++ b/packages/microcosm-build/tests/engine_free/shared/test_nz_reference_aging.py @@ -6,7 +6,7 @@ import json import os from datetime import date -from decimal import Decimal, localcontext +from decimal import ROUND_FLOOR, ROUND_HALF_EVEN, Decimal, localcontext from fractions import Fraction from pathlib import Path @@ -222,18 +222,78 @@ def test_treasury_fy2027_source_column_uses_chronicle_2026_and_2027_refuses( ) -def test_p1_rounds_once_and_the_truncated_numerator_refuses(operand_facts): - # p1_ty2026_to_ty2027_central's unrounded value truncates to a different numerator. - declaration = _entry("p1_ty2026_to_ty2027_central")["declaration"] +def _p1_numerators(declaration): + """A P1 factor's numerator under one half-even rounding, and what truncation + or an intermediate rounding would declare instead, all in millionths.""" + value = {period: Decimal(text) for _, period, _, text in _OPERANDS} + sources = [ + row["selector"]["period_value"] for row in declaration["source_schedule"] + ] + cent = Decimal("0.01") + with localcontext() as context: + context.prec = 60 + growth = Decimal("2.7") + Decimal(declaration["growth"]["scenario_delta"]) + q = (1 + growth / 100).sqrt().sqrt() + q6 = q.quantize(Decimal("0.000001")) + june, september = value["2026-Q2"], value["2026-Q3"] + denominator = sum(value[period] for period in sources) / 4 + + def mean(projected): + return (june + september + sum(projected)) / 4 + + def millionths(numerator, rounding=ROUND_HALF_EVEN): + ratio = numerator / denominator * 1000000 + return int(ratio.to_integral_value(rounding=rounding)) + + exact = mean([september * q, september * q * q]) + return millionths(exact), { + "truncated": millionths(exact, ROUND_FLOOR), + "numerator rounded to cents": millionths(exact.quantize(cent)), + "projected quarters rounded to cents": millionths( + mean([(september * q**power).quantize(cent) for power in (1, 2)]) + ), + "q rounded to six digits": millionths( + mean([september * q6, september * q6 * q6]) + ), + } + + +@pytest.mark.parametrize("profile", ["low", "central", "high"]) +def test_p1_rounds_once_and_refuses_truncated_or_intermediate_numerators( + profile, operand_facts +): + factor_id = f"p1_ty2026_to_ty2027_{profile}" + declaration = _entry(factor_id)["declaration"] assert declaration["source_period"] == 2026 assert [ row["selector"]["period_value"] for row in declaration["source_schedule"] ] == ["2025-Q2", "2025-Q3", "2025-Q4", "2026-Q1"] - declaration["expected_operational_factor"]["numerator"] = 1029852 - with pytest.raises(ValueError, match="expected factor mismatch"): - resolve_reference_factor( - declaration, operand_facts, _document()["feed_identity"] - ) + once, alternatives = _p1_numerators(declaration) + assert once == _EXPECTED[factor_id] + for numerator in sorted(set(alternatives.values()) - {once}): + declaration["expected_operational_factor"]["numerator"] = numerator + with pytest.raises(ValueError, match="expected factor mismatch"): + resolve_reference_factor( + declaration, operand_facts, _document()["feed_identity"] + ) + + +def test_p1_profiles_separate_one_rounding_from_each_alternative(): + """Each alternative misses some profile's declared numerator, so the + refusal test above exercises every alternative.""" + missed = set() + for profile in ("low", "central", "high"): + declaration = _entry(f"p1_ty2026_to_ty2027_{profile}")["declaration"] + once, alternatives = _p1_numerators(declaration) + missed |= { + name for name, numerator in alternatives.items() if numerator != once + } + assert missed == { + "truncated", + "numerator rounded to cents", + "projected quarters rounded to cents", + "q rounded to six digits", + } def test_root_enclosure_certifies_no_rounded_quarters_and_irrational_root(): @@ -247,9 +307,14 @@ def test_root_enclosure_certifies_no_rounded_quarters_and_irrational_root(): assert lower**4 < radicand < upper**4 assert receipt["root_bounds"]["exact"] is False denominator = _fraction(receipt["denominator_mean"]) - assert _fraction(receipt["numerator_mean_bounds"]["lower"]) == ( - Fraction("1700.68") + Fraction("1714.86") * sum(lower**i for i in range(3)) - ) / 4 + assert ( + _fraction(receipt["numerator_mean_bounds"]["lower"]) + == ( + Fraction("1700.68") + + Fraction("1714.86") * sum(lower**i for i in range(3)) + ) + / 4 + ) for bound in ("lower", "upper"): value = _fraction(receipt["unrounded_factor_bounds"][bound]) assert ( diff --git a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py index ac7a82d644..75d4dd6c2e 100644 --- a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py +++ b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py @@ -728,39 +728,10 @@ def add_quantiles(rows): ) rows[at:at] = synthetic - def add_measurement(rows): - """A compile-only producer of the stand-in columns, until G declares one.""" - if any( - row.get("params", {}).get("measurement_id") == _MEASUREMENT for row in rows - ): - return - prepare = next(row for row in rows if row["id"].endswith(".targets.prepare")) - rows.insert( - rows.index(prepare), - { - "id": prepare["id"].removesuffix(".targets.prepare") - + ".synthetic_measurement", - "kernel": "measure.combine@1", - "population": prepare["population"], - "inputs": [{"entity": "person", "columns": ["age"]}], - "outputs": [ - {"entity": "person", "column": column, "dtype": dtype} - for column, dtype in ( - ("ird_taxable_income_proxy_cents", "Int64"), - ("ird_taxable_income_proxy_nzd", "float64"), - ("ird_taxable_income_proxy_universe_status", "string"), - ) - ], - "params": {"measurement_id": _MEASUREMENT}, - }, - ) - add_quantiles(resources["transport_graph"]["nodes"]) - add_measurement(resources["transport_graph"]["nodes"]) for scenario in resources["scenarios"]["scenarios"]: if scenario["tier"] == "calibration": add_quantiles(scenario["nodes"]) - add_measurement(scenario["nodes"]) rows = _instantiated_nz_rows(spec) produced = { (output["entity"], output["column"]) @@ -891,12 +862,13 @@ def test_nz_packaged_graph_compiles_with_only_pins_and_money_producers(): def test_every_calibration_node_has_its_own_standin_as_the_only_writer(): """R2C-15 on every calibration node, the variant branches' included. - Composition checks the measurement proof on the skeleton alone: - ``_check_measurement_proofs`` runs before the scenario extensions append - their branches, so it never sees ``nz.v..calibrate``. For each - calibration node of the composed graph, exactly one compiled ancestor - declares the measurement, it is that branch's stand-in, and it is the - only ancestor whose outputs or EXPAND cells hold each proved column. + Composition runs ``_check_measurement_proofs`` on the skeleton and again + on the extended graph, where the declaration names no producer for + ``nz.v..calibrate`` and the compiled graph has to prove one. This test + checks NZ's graph directly: for each calibration node of the composed + graph, exactly one compiled ancestor declares the measurement, it is that + branch's stand-in, and it is the only ancestor whose outputs or EXPAND + cells hold each proved column. """ spec, _, graph, compiled = _nz_composed() proofs, refusals = compose.measurement_proofs(spec)