diff --git a/changelog.d/bump-us-pin-1.653.3.changed.md b/changelog.d/bump-us-pin-1.653.3.changed.md new file mode 100644 index 00000000..45f6458e --- /dev/null +++ b/changelog.d/bump-us-pin-1.653.3.changed.md @@ -0,0 +1,9 @@ +Bump the bundled US release manifest to `policyengine-us==1.653.3` (from +1.647.0) to unblock downstream projects that want to pin the latest +working model version through `policyengine.py`. The dataset stays at +`policyengine-us-data==1.73.0` (the latest US data release tagged on +Hugging Face); certification is now +`matching_data_build_fingerprint` with `built_with_model_version` +recording the 1.647.0 that actually produced the data. Both bundled +manifests (`us.json`, `uk.json`) update `policyengine_version` and +`bundle_id` to 3.5.0 to match the current package version. diff --git a/changelog.d/range-specifiers.added.md b/changelog.d/range-specifiers.added.md new file mode 100644 index 00000000..a52f4ba1 --- /dev/null +++ b/changelog.d/range-specifiers.added.md @@ -0,0 +1,7 @@ +`certify_data_release_compatibility` now accepts full PEP 440 version +specifiers (`>=1.637.0,<2.0.0`, `~=1.637`, etc.) in a data release +manifest's `compatible_model_packages`, not only `==X.Y.Z`. This lets +the US data package declare a range of compatible `policyengine-us` +versions when the `data_build_fingerprint` is known to be stable +across them, avoiding the need to regenerate the dataset for every +model patch release. Adds `packaging>=23.0` as a direct dependency. diff --git a/pyproject.toml b/pyproject.toml index 7430d80d..a6d7c091 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "plotly>=5.0.0", "requests>=2.31.0", "psutil>=5.9.0", + "packaging>=23.0", ] [project.scripts] @@ -39,7 +40,7 @@ uk = [ ] us = [ "policyengine_core>=3.25.0", - "policyengine-us==1.647.0", + "policyengine-us==1.653.3", ] dev = [ "pytest", @@ -54,7 +55,7 @@ dev = [ "ruff>=0.9.0", "policyengine_core>=3.25.0", "policyengine-uk==2.88.0", - "policyengine-us==1.647.0", + "policyengine-us==1.653.3", "towncrier>=24.8.0", "mypy>=1.11.0", "pytest-cov>=5.0.0", diff --git a/src/policyengine/core/release_manifest.py b/src/policyengine/core/release_manifest.py index a1ab2fd0..f4929554 100644 --- a/src/policyengine/core/release_manifest.py +++ b/src/policyengine/core/release_manifest.py @@ -218,9 +218,14 @@ def get_data_release_manifest(country_id: str) -> DataReleaseManifest: def _specifier_matches(version: str, specifier: str) -> bool: - if specifier.startswith("=="): - return version == specifier[2:] - return False + """Match a PEP 440 version specifier (``==``, ``>=``, ``<``, ``,``-joined).""" + from packaging.specifiers import InvalidSpecifier, SpecifierSet + from packaging.version import InvalidVersion, Version + + try: + return Version(version) in SpecifierSet(specifier) + except (InvalidSpecifier, InvalidVersion): + return False def certify_data_release_compatibility( diff --git a/src/policyengine/data/release_manifests/uk.json b/src/policyengine/data/release_manifests/uk.json index 8f437212..de8fa505 100644 --- a/src/policyengine/data/release_manifests/uk.json +++ b/src/policyengine/data/release_manifests/uk.json @@ -1,8 +1,8 @@ { "schema_version": 1, - "bundle_id": "uk-3.4.0", + "bundle_id": "uk-3.5.0", "country_id": "uk", - "policyengine_version": "3.4.0", + "policyengine_version": "3.5.0", "model_package": { "name": "policyengine-uk", "version": "2.88.0", diff --git a/src/policyengine/data/release_manifests/us.json b/src/policyengine/data/release_manifests/us.json index 4eb945f0..b005eda9 100644 --- a/src/policyengine/data/release_manifests/us.json +++ b/src/policyengine/data/release_manifests/us.json @@ -1,13 +1,13 @@ { "schema_version": 1, - "bundle_id": "us-3.4.0", + "bundle_id": "us-3.5.0", "country_id": "us", - "policyengine_version": "3.4.0", + "policyengine_version": "3.5.0", "model_package": { "name": "policyengine-us", - "version": "1.647.0", - "sha256": "50e64bf910772b224cdc2b5af5a3414f976f68a9e1748107da7e1de6e325425c", - "wheel_url": "https://files.pythonhosted.org/packages/2a/96/4814f2630395350915d819452d7684f232c9b8df1d9ba5c279f3b6d02c17/policyengine_us-1.647.0-py3-none-any.whl" + "version": "1.653.3", + "sha256": "67a49b98d85c060b24d547a569e91a6703c0fc9c41299c1c67f4ecfac75c67c6", + "wheel_url": "https://files.pythonhosted.org/packages/02/07/25f39a2bfa1ff210cd8e78826c47c03b9040a98a83f4eed59c434c1ed862/policyengine_us-1.653.3-py3-none-any.whl" }, "data_package": { "name": "policyengine-us-data", @@ -25,10 +25,10 @@ "sha256": "18cdc668d05311c32ae37364abcea89b0221c27154559667e951c7b19f5b5cbd" }, "certification": { - "compatibility_basis": "exact_build_model_version", + "compatibility_basis": "matching_data_build_fingerprint", "data_build_id": "policyengine-us-data-1.73.0", "built_with_model_version": "1.647.0", - "certified_for_model_version": "1.647.0", + "certified_for_model_version": "1.653.3", "certified_by": "policyengine.py bundled manifest" }, "default_dataset": "enhanced_cps_2024", diff --git a/tests/test_models.py b/tests/test_models.py index fbc613d2..a9023c56 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -113,7 +113,7 @@ def test_has_release_manifest_metadata(self): assert us_latest.release_manifest is not None assert us_latest.release_manifest.country_id == "us" assert us_latest.model_package.name == "policyengine-us" - assert us_latest.model_package.version == "1.647.0" + assert us_latest.model_package.version == "1.653.3" assert us_latest.data_package.name == "policyengine-us-data" assert us_latest.data_package.version == "1.73.0" assert ( diff --git a/tests/test_release_manifests.py b/tests/test_release_manifests.py index c2370546..18d6eed3 100644 --- a/tests/test_release_manifests.py +++ b/tests/test_release_manifests.py @@ -45,11 +45,11 @@ def test__given_us_manifest__then_has_pinned_model_and_data_packages(self): manifest = get_release_manifest("us") assert manifest.schema_version == 1 - assert manifest.bundle_id == "us-3.4.0" + assert manifest.bundle_id == "us-3.5.0" assert manifest.country_id == "us" - assert manifest.policyengine_version == "3.4.0" + assert manifest.policyengine_version == "3.5.0" assert manifest.model_package.name == "policyengine-us" - assert manifest.model_package.version == "1.647.0" + assert manifest.model_package.version == "1.653.3" assert manifest.data_package.name == "policyengine-us-data" assert manifest.data_package.version == "1.73.0" assert manifest.data_package.repo_id == "policyengine/policyengine-us-data" @@ -61,15 +61,15 @@ def test__given_us_manifest__then_has_pinned_model_and_data_packages(self): assert manifest.certification is not None assert manifest.certification.data_build_id == "policyengine-us-data-1.73.0" assert manifest.certification.built_with_model_version == "1.647.0" - assert manifest.certification.certified_for_model_version == "1.647.0" + assert manifest.certification.certified_for_model_version == "1.653.3" def test__given_uk_manifest__then_has_pinned_model_and_data_packages(self): manifest = get_release_manifest("uk") assert manifest.schema_version == 1 - assert manifest.bundle_id == "uk-3.4.0" + assert manifest.bundle_id == "uk-3.5.0" assert manifest.country_id == "uk" - assert manifest.policyengine_version == "3.4.0" + assert manifest.policyengine_version == "3.5.0" assert manifest.model_package.name == "policyengine-uk" assert manifest.model_package.version == "2.88.0" assert manifest.data_package.name == "policyengine-uk-data" @@ -214,6 +214,46 @@ def test__given_missing_data_release_manifest__then_fetch_raises_unavailable(sel else: raise AssertionError("Expected missing manifest to be reported") + def test__given_range_specifier__then_certification_accepts_compatible_version( + self, + ): + get_data_release_manifest.cache_clear() + payload = { + "schema_version": 1, + "data_package": { + "name": "policyengine-us-data", + "version": "1.73.0", + }, + "build": { + "build_id": "policyengine-us-data-1.73.0", + "built_with_model_package": { + "name": "policyengine-us", + "version": "1.637.0", + "data_build_fingerprint": "sha256:stable", + }, + }, + "compatible_model_packages": [ + { + "name": "policyengine-us", + "specifier": ">=1.637.0,<2.0.0", + } + ], + "default_datasets": {"national": "enhanced_cps_2024"}, + "artifacts": {}, + } + + with patch( + "policyengine.core.release_manifest.requests.get", + return_value=_response_with_json(payload), + ): + certification = certify_data_release_compatibility( + "us", + runtime_model_version="1.653.3", + ) + + assert certification.compatibility_basis == "legacy_compatible_model_package" + assert certification.certified_for_model_version == "1.653.3" + def test__given_matching_fingerprint__then_certification_allows_reuse(self): get_data_release_manifest.cache_clear() payload = { @@ -262,7 +302,7 @@ def test__given_private_manifest_unavailable__then_bundled_certification_is_used ): certification = certify_data_release_compatibility( "us", - runtime_model_version="1.647.0", + runtime_model_version="1.653.3", ) assert certification == get_release_manifest("us").certification @@ -368,7 +408,7 @@ def test__given_manifest_certification__then_release_bundle_exposes_it(self): bundle = model_version.release_bundle - assert bundle["bundle_id"] == "uk-3.4.0" + assert bundle["bundle_id"] == "uk-3.5.0" assert bundle["default_dataset"] == "enhanced_frs_2023_24" assert bundle["default_dataset_uri"] == manifest.default_dataset_uri assert bundle["certified_data_build_id"] == "policyengine-uk-data-1.40.4" @@ -415,7 +455,7 @@ def test__given_us_managed_microsimulation__then_passes_certified_dataset_and_bu dataset = mock_microsimulation.call_args.kwargs["dataset"] assert dataset == microsim.policyengine_bundle["runtime_dataset_source"] - assert microsim.policyengine_bundle["policyengine_version"] == "3.4.0" + assert microsim.policyengine_bundle["policyengine_version"] == "3.5.0" assert microsim.policyengine_bundle["runtime_dataset"] == "enhanced_cps_2024" assert ( microsim.policyengine_bundle["runtime_dataset_uri"] @@ -453,7 +493,7 @@ def test__given_uk_managed_dataset_name__then_resolves_within_bundle(self): "hf://policyengine/policyengine-uk-data-private/" "enhanced_frs_2023_24.h5@1.40.4" ) - assert microsim.policyengine_bundle["policyengine_version"] == "3.4.0" + assert microsim.policyengine_bundle["policyengine_version"] == "3.5.0" assert microsim.policyengine_bundle["runtime_dataset"] == "enhanced_frs_2023_24" assert microsim.policyengine_bundle["runtime_dataset_uri"] == ( "hf://policyengine/policyengine-uk-data-private/enhanced_frs_2023_24.h5@1.40.4" diff --git a/tests/test_trace_tro.py b/tests/test_trace_tro.py index 90bb10eb..f78b4f33 100644 --- a/tests/test_trace_tro.py +++ b/tests/test_trace_tro.py @@ -42,7 +42,7 @@ FAKE_WHEEL_SHA = "a" * 64 FAKE_WHEEL_URL = ( "https://files.pythonhosted.org/packages/ab/cd/" - "policyengine_us-1.647.0-py3-none-any.whl" + "policyengine_us-1.637.0-py3-none-any.whl" ) @@ -66,7 +66,7 @@ def _us_data_release_manifest( "built_at": "2026-04-10T12:00:00Z", "built_with_model_package": { "name": "policyengine-us", - "version": "1.647.0", + "version": "1.637.0", "git_sha": "deadbeef", "data_build_fingerprint": data_build_fingerprint, },