Skip to content

Release test fails: test_award_never_rises_with_a_claimants_statutory_pay passes from source, fails from the installed wheel #2397

Closed

Description

@anth-volk

@vahid-ahmadi flagging this for you: it blocks every release.

What happens

Every versioning.yaml release run on main since 2026-10-12 20:46 UTC fails Release test on one test, so Publish is skipped and nothing has been released since:

FAILED policyengine_uk/tests/test_uc_statutory_pay_properties.py::test_award_never_rises_with_a_claimants_statutory_pay
AssertionError: {'adults': [{'age': 22, 'wages': 12570.0, 'profit': 0.0, 'startup': False, ...}], 'child_ages': [1, 1], ...

Runs: 2e5cf2b (first failure, the #2119 merge), 807e307, and the 07:35, 07:59 and 10:38 runs on 2026-10-13. The pending run for #2031 is expected to fail the same way.

Why PR checks didn't catch it

Reproduction (local, commit 2e5cf2b, Python 3.13)

  • From source (uv sync --frozen --extra dev, then pytest policyengine_uk/tests/test_uc_statutory_pay_properties.py::test_award_never_rises_with_a_claimants_statutory_pay): passes in 7.6 s.
  • From the wheel (build the wheel, install it with the locked dependencies into a new venv, run pytest --pyargs policyengine_uk.tests.test_uc_statutory_pay_properties -k test_award_never_rises_with_a_claimants_statutory_pay from outside the repo): fails (about 6 minutes, mostly Hypothesis shrinking).

Ruled out

  • Missing files: all 2,631 tracked files under policyengine_uk/ are in the wheel.
  • Dependencies and Python: the passing queue run and the failing release run installed the same 152 packages at the same versions, on Python 3.13.16. The only difference was the package's own version label (2.127.0 vs 2.128.0), and the local wheel was 2.127.0.
  • Random inputs: the test uses derandomize=True, and Hypothesis derives that seed from the test's source and signature, not its path, so both runs try the same households.

So for the same inputs the model gives a different result depending on whether policyengine_uk is imported from a source checkout or an installed wheel. For the failing household (age 22, wages £12,570, two children aged 1), the UC award rises as statutory pay rises in the installed package. Since the installed package is what PyPI users get, this may be a real bug rather than only a test problem. Investigation continues below.

🤖 Generated with Claude Code

Activity

  1. anth-volk commented on Oct 13, 2026

    @anth-volk
    CollaboratorAuthor

    Root cause

    Two separate things.

    1. A real model bug (in source and in the wheel)

    The failing assertion is exempt[HIGH] >= exempt[LOW] (assert 0.0 >= 1.0). The failing family: one adult, 22, wages £12,570, private pension £6,000, two children aged 1, North East, council tenant; statutory sick pay rises from £0 to £6,000.

    is_benefit_cap_exempt_earnings tests employment_income + self_employment_income - income_tax - national_insurance >= 10,152. It subtracts all of the person's income tax and NI, including the tax and NI caused by statutory pay and pension income, while counting only wages and profit. So a rise in statutory pay can end the earnings exemption. Computed directly at 2e5cf2b (identical from source and from the wheel):

    SSP £0 SSP £6,000
    employment_income 12,570 12,570
    income_tax 1,200 2,400
    national_insurance 0 480
    earnings test value 11,370 (exempt) 9,690 (not exempt)
    universal_credit 3,130.02 754.02

    2. Why PR and merge-queue checks don't see it

    Hypothesis 6.169 biases generation with literal constants harvested from local modules, and treats any path containing /site-packages/ as non-local (hypothesis/internal/constants_ast.py: is_local_module_file, used by conjecture/providers.py: _get_local_constants).

    • From the source checkout, policyengine_uk is local: Hypothesis collects 37 integers, 51 floats and 1,301 strings from it.
    • From the installed wheel (Release test), it collects none.

    The derandomize=True seed is identical in both (same function digest), and the first two examples match, but from the third example on the two environments generate different populations. Each environment is stable across runs and independent of PYTHONHASHSEED and the working directory. The release environment's examples include the failing family; the source environment's don't. So derandomize=True makes runs repeatable within one environment, but PR checks and Release test can still explore different cases.

    Possible fixes (for the owners to decide)

    • Model: make the reg. 82 earnings test consistent with statutory pay now counting as UC earned income (Count statutory sick, maternity and paternity pay as Universal Credit earned income #2365), e.g. include statutory pay in the earnings measured, or subtract only the tax and NI attributable to the counted earnings.
    • Tests: add an @example(...) for this family so every environment checks it, and/or make PR checks and Release test explore the same cases (for example by running both from the installed wheel).

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions