From 2e234c568ca0bf11006558c38919405196cebe5b Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Mon, 1 Jun 2026 18:44:41 +0200 Subject: [PATCH 1/2] Update US bundle to policyengine-us 1.715.2 --- changelog.d/us-bundle-17152.changed.md | 1 + pyproject.toml | 4 +- .../data/release_manifests/us.json | 14 ++-- .../release_manifests/us.trace.tro.jsonld | 27 +++--- src/policyengine/provenance/bundle.py | 84 +++++++++++++++++-- src/policyengine/provenance/manifest.py | 2 + tests/test_bundle_refresh.py | 67 +++++++++++++++ tests/test_release_manifests.py | 42 +++++++++- uv.lock | 12 +-- 9 files changed, 211 insertions(+), 42 deletions(-) create mode 100644 changelog.d/us-bundle-17152.changed.md diff --git a/changelog.d/us-bundle-17152.changed.md b/changelog.d/us-bundle-17152.changed.md new file mode 100644 index 00000000..64e7f1ed --- /dev/null +++ b/changelog.d/us-bundle-17152.changed.md @@ -0,0 +1 @@ +Refresh the bundled US release manifest to policyengine-us 1.715.2 and require explicit data-release compatibility assertions when refreshing bundles across model versions. diff --git a/pyproject.toml b/pyproject.toml index e16ac3c0..ae4a634a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ uk = [ ] us = [ "policyengine_core==3.26.1", - "policyengine-us==1.700.0", + "policyengine-us==1.715.2", ] dev = [ "pytest", @@ -64,7 +64,7 @@ dev = [ "ruff>=0.9.0", "policyengine_core==3.26.1", "policyengine-uk==2.88.20", - "policyengine-us==1.700.0", + "policyengine-us==1.715.2", "towncrier>=24.8.0", "mypy>=1.11.0", "pytest-cov>=5.0.0", diff --git a/src/policyengine/data/release_manifests/us.json b/src/policyengine/data/release_manifests/us.json index dc6113d0..d691b4ce 100644 --- a/src/policyengine/data/release_manifests/us.json +++ b/src/policyengine/data/release_manifests/us.json @@ -5,16 +5,16 @@ "policyengine_version": "4.12.1", "model_package": { "name": "policyengine-us", - "version": "1.700.0", - "sha256": "7633d8aefcaf02d7628f841bc56750606f1d7fe409ff3ae7b0ef7e364a88e945", - "wheel_url": "https://files.pythonhosted.org/packages/49/e9/2837a0d98e99efaf4d82aade276eee6eeff419df614863f08e3512961d2d/policyengine_us-1.700.0-py3-none-any.whl" + "version": "1.715.2", + "sha256": "abf079828419762f5c4b0291a70f6e424744200f237e1ae0f06e25f10130c399", + "wheel_url": "https://files.pythonhosted.org/packages/45/a1/1d56bdbb69d7ce06bedd3892203a75ac3350a90c0b5fcea2fb50db46670f/policyengine_us-1.715.2-py3-none-any.whl" }, "data_package": { "name": "policyengine-us-data", "version": "1.115.5", "repo_id": "policyengine/policyengine-us-data", "release_manifest_path": "release_manifest.json", - "release_manifest_revision": "688f972425f5e858fc52bda2b696e0af74fea920" + "release_manifest_revision": "d47fb5475144260a75467d2f2e22b2d5d53d4d57" }, "certified_data_artifact": { "data_package": { @@ -23,14 +23,14 @@ }, "build_id": "policyengine-us-data-1.115.5", "dataset": "enhanced_cps_2024", - "uri": "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@688f972425f5e858fc52bda2b696e0af74fea920", + "uri": "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@d47fb5475144260a75467d2f2e22b2d5d53d4d57", "sha256": "0a6b961ad363a421bde99f2c8e5d8f20370bcba45fd303050537a25bdd805b14" }, "certification": { - "compatibility_basis": "exact_build_model_version", + "compatibility_basis": "legacy_compatible_model_package", "data_build_id": "policyengine-us-data-1.115.5", "built_with_model_version": "1.700.0", - "certified_for_model_version": "1.700.0", + "certified_for_model_version": "1.715.2", "certified_by": "policyengine-us-data release manifest", "data_build_fingerprint": "sha256:b0862de383ffcbe45f4ba0aa9c6aaec286cd4c6688c6ccb33f939bc176f9a8a0" }, diff --git a/src/policyengine/data/release_manifests/us.trace.tro.jsonld b/src/policyengine/data/release_manifests/us.trace.tro.jsonld index edf0928e..3a9902a2 100644 --- a/src/policyengine/data/release_manifests/us.trace.tro.jsonld +++ b/src/policyengine/data/release_manifests/us.trace.tro.jsonld @@ -45,7 +45,7 @@ "trov:hasArtifact": { "@id": "composition/1/artifact/data_release_manifest" }, - "trov:hasLocation": "https://huggingface.co/policyengine/policyengine-us-data/resolve/688f972425f5e858fc52bda2b696e0af74fea920/release_manifest.json" + "trov:hasLocation": "https://huggingface.co/policyengine/policyengine-us-data/resolve/d47fb5475144260a75467d2f2e22b2d5d53d4d57/release_manifest.json" }, { "@id": "arrangement/1/location/dataset", @@ -53,7 +53,7 @@ "trov:hasArtifact": { "@id": "composition/1/artifact/dataset" }, - "trov:hasLocation": "https://huggingface.co/policyengine/policyengine-us-data/resolve/688f972425f5e858fc52bda2b696e0af74fea920/enhanced_cps_2024.h5" + "trov:hasLocation": "https://huggingface.co/policyengine/policyengine-us-data/resolve/d47fb5475144260a75467d2f2e22b2d5d53d4d57/enhanced_cps_2024.h5" }, { "@id": "arrangement/1/location/model_wheel", @@ -61,7 +61,7 @@ "trov:hasArtifact": { "@id": "composition/1/artifact/model_wheel" }, - "trov:hasLocation": "https://files.pythonhosted.org/packages/49/e9/2837a0d98e99efaf4d82aade276eee6eeff419df614863f08e3512961d2d/policyengine_us-1.700.0-py3-none-any.whl" + "trov:hasLocation": "https://files.pythonhosted.org/packages/45/a1/1d56bdbb69d7ce06bedd3892203a75ac3350a90c0b5fcea2fb50db46670f/policyengine_us-1.715.2-py3-none-any.whl" } ] } @@ -75,14 +75,14 @@ "@type": "trov:ResearchArtifact", "schema:name": "policyengine.py bundle manifest for us", "trov:mimeType": "application/json", - "trov:sha256": "55dacfa54b091fb9c8b06245b160d2de1f8c6eb68ff4202e2a908ddd599779dd" + "trov:sha256": "88475a13e8ee438d6e7cbc8a85be39cd13e2f9a61f1fe68cf335d0452307a09b" }, { "@id": "composition/1/artifact/data_release_manifest", "@type": "trov:ResearchArtifact", "schema:name": "policyengine-us-data release manifest 1.115.5", "trov:mimeType": "application/json", - "trov:sha256": "f5387c6b5acc0507cc965087da5059f59a4c6cb43b3778f13f065355f05d900e" + "trov:sha256": "577fbf704da44f63d0432dc3e80f3686eb3a32020d13ca6b7b6cf7eb60b4742c" }, { "@id": "composition/1/artifact/dataset", @@ -94,15 +94,15 @@ { "@id": "composition/1/artifact/model_wheel", "@type": "trov:ResearchArtifact", - "schema:name": "policyengine-us==1.700.0 wheel", + "schema:name": "policyengine-us==1.715.2 wheel", "trov:mimeType": "application/zip", - "trov:sha256": "7633d8aefcaf02d7628f841bc56750606f1d7fe409ff3ae7b0ef7e364a88e945" + "trov:sha256": "abf079828419762f5c4b0291a70f6e424744200f237e1ae0f06e25f10130c399" } ], "trov:hasFingerprint": { "@id": "composition/1/fingerprint", "@type": "trov:CompositionFingerprint", - "trov:sha256": "025afe16935a7d032ffbce964f36df58048d0d9704b890ec9643329db57c7233" + "trov:sha256": "3b4fb35c76597c331ec205586266083cbc9805b8d9736f47a9c35a5471498230" } }, "trov:hasPerformance": { @@ -110,15 +110,12 @@ "@type": "trov:TransparentResearchPerformance", "pe:builtWithModelVersion": "1.700.0", "pe:certifiedBy": "policyengine-us-data release manifest", - "pe:certifiedForModelVersion": "1.700.0", - "pe:ciGitRef": "refs/heads/main", - "pe:ciGitSha": "7c3416d538f2eb90a0d6d9d9cd0e4eee9071f58a", - "pe:ciRunUrl": "https://github.com/PolicyEngine/policyengine.py/actions/runs/26668334869", - "pe:compatibilityBasis": "exact_build_model_version", + "pe:certifiedForModelVersion": "1.715.2", + "pe:compatibilityBasis": "legacy_compatible_model_package", "pe:dataBuildFingerprint": "sha256:b0862de383ffcbe45f4ba0aa9c6aaec286cd4c6688c6ccb33f939bc176f9a8a0", "pe:dataBuildId": "policyengine-us-data-1.115.5", - "pe:emittedIn": "github-actions", - "rdfs:comment": "Certification of build policyengine-us-data-1.115.5 for policyengine-us 1.700.0.", + "pe:emittedIn": "local", + "rdfs:comment": "Certification of build policyengine-us-data-1.115.5 for policyengine-us 1.715.2.", "trov:accessedArrangement": { "@id": "arrangement/1" }, diff --git a/src/policyengine/provenance/bundle.py b/src/policyengine/provenance/bundle.py index 86b5f7f3..1a22642e 100644 --- a/src/policyengine/provenance/bundle.py +++ b/src/policyengine/provenance/bundle.py @@ -42,6 +42,9 @@ from typing import Optional from urllib.request import Request, urlopen +from packaging.specifiers import InvalidSpecifier, SpecifierSet +from packaging.version import InvalidVersion, Version + from policyengine.provenance.manifest import ( CountryReleaseManifest, get_release_manifest, @@ -253,6 +256,69 @@ def _metadata_sidecar_path(path: str) -> str: return f"{path}.metadata.json" +def _specifier_matches(*, version: str, specifier: str) -> bool: + try: + return Version(version) in SpecifierSet(specifier) + except (InvalidSpecifier, InvalidVersion): + return False + + +def _release_manifest_has_compatible_model_package( + release_manifest_json: dict, + *, + package_name: str, + model_version: str, +) -> bool: + for compatible_model_package in release_manifest_json.get( + "compatible_model_packages", + [], + ): + if compatible_model_package.get("name") != package_name: + continue + if _specifier_matches( + version=model_version, + specifier=compatible_model_package.get("specifier", ""), + ): + return True + return False + + +def _release_manifest_compatibility_basis( + *, + release_manifest_json: dict, + current_manifest: CountryReleaseManifest, + package_name: str, + model_version: str, + built_with_model_version: str | None, + data_build_fingerprint: str | None, +) -> str: + if built_with_model_version == model_version: + return "exact_build_model_version" + + current_certification = current_manifest.certification + if ( + current_certification is not None + and current_certification.certified_for_model_version == model_version + and current_certification.data_build_fingerprint is not None + and current_certification.data_build_fingerprint == data_build_fingerprint + ): + return "matching_data_build_fingerprint" + + if _release_manifest_has_compatible_model_package( + release_manifest_json, + package_name=package_name, + model_version=model_version, + ): + return "legacy_compatible_model_package" + + raise ValueError( + "Data release manifest is not certified for " + f"{package_name}=={model_version}. Publish a data release manifest with " + "a matching build model, matching data-build fingerprint, or compatible " + "model-package specifier before refreshing the bundle." + ) + + def _refresh_dataset_path_references_from_data_release( manifest_json: dict, release_manifest_json: dict, @@ -566,16 +632,16 @@ def refresh_release_bundle( certification_json["data_build_fingerprint"] = data_build_fingerprint else: certification_json.pop("data_build_fingerprint", None) - if built_with_model_version == new_model: - certification_json["compatibility_basis"] = "exact_build_model_version" - elif data_build_fingerprint is not None: - certification_json["compatibility_basis"] = ( - "matching_data_build_fingerprint" - ) - else: - certification_json["compatibility_basis"] = ( - "legacy_compatible_model_package" + certification_json["compatibility_basis"] = ( + _release_manifest_compatibility_basis( + release_manifest_json=release_manifest_json, + current_manifest=current, + package_name=package_name, + model_version=new_model, + built_with_model_version=built_with_model_version, + data_build_fingerprint=data_build_fingerprint, ) + ) _refresh_dataset_path_references_from_data_release( manifest_json, release_manifest_json, diff --git a/src/policyengine/provenance/manifest.py b/src/policyengine/provenance/manifest.py index fcf6dcef..6df57414 100644 --- a/src/policyengine/provenance/manifest.py +++ b/src/policyengine/provenance/manifest.py @@ -322,6 +322,8 @@ def certify_data_release_compatibility( if ( runtime_data_build_fingerprint is not None and bundled_certification.data_build_fingerprint is not None + and bundled_certification.compatibility_basis + != "legacy_compatible_model_package" and runtime_data_build_fingerprint != bundled_certification.data_build_fingerprint ): diff --git a/tests/test_bundle_refresh.py b/tests/test_bundle_refresh.py index 3326a34b..c9cae63a 100644 --- a/tests/test_bundle_refresh.py +++ b/tests/test_bundle_refresh.py @@ -86,6 +86,7 @@ def _data_release_manifest_response( data_version: str = "1.83.4", dataset_sha256: str = "e" * 64, extra_artifacts: dict | None = None, + compatible_model_packages: list[dict] | None = None, headers: dict | None = None, ): artifacts = { @@ -114,6 +115,11 @@ def _data_release_manifest_response( "data_build_fingerprint": "sha256:fingerprint", }, }, + "compatible_model_packages": ( + compatible_model_packages + if compatible_model_packages is not None + else [{"name": "policyengine-us", "specifier": f"=={model_version}"}] + ), "artifacts": artifacts, } return _FakeHFResponse( @@ -168,6 +174,7 @@ def sandbox(tmp_path: Path) -> dict: "built_with_model_version": "1.595.0", "certified_for_model_version": "1.600.0", "certified_by": "test fixture", + "data_build_fingerprint": "sha256:fingerprint", }, "default_dataset": "enhanced_cps_2024", "datasets": {"enhanced_cps_2024": {"path": "enhanced_cps_2024.h5"}}, @@ -431,6 +438,66 @@ def fake_urlopen(request, *args, **kwargs): ) +def test__model_refresh_uses_compatible_model_package_assertion(sandbox) -> None: + def fake_urlopen(request, *args, **kwargs): + url = request.full_url + if "pypi.org" in url: + return _pypi_response("policyengine-us", "1.653.3") + if url.endswith("releases/1.70.0/release_manifest.json"): + return _data_release_manifest_response( + model_version="1.600.0", + data_version="1.70.0", + compatible_model_packages=[ + {"name": "policyengine-us", "specifier": "==1.600.0"}, + {"name": "policyengine-us", "specifier": "==1.653.3"}, + ], + ) + raise AssertionError(f"Unexpected URL fetched: {url}") + + with patch("policyengine.provenance.bundle.urlopen", side_effect=fake_urlopen): + refresh_release_bundle( + country="us", + model_version="1.653.3", + release_manifest_revision="release-manifest-commit-sha", + manifest_dir=sandbox["manifest_dir"], + pyproject_path=sandbox["pyproject_path"], + ) + + written = json.loads((sandbox["manifest_dir"] / "us.json").read_text()) + assert ( + written["certification"]["compatibility_basis"] + == "legacy_compatible_model_package" + ) + assert written["certification"]["built_with_model_version"] == "1.600.0" + assert written["certification"]["certified_for_model_version"] == "1.653.3" + + +def test__model_refresh_rejects_uncertified_data_release_manifest(sandbox) -> None: + def fake_urlopen(request, *args, **kwargs): + url = request.full_url + if "pypi.org" in url: + return _pypi_response("policyengine-us", "1.653.3") + if url.endswith("releases/1.70.0/release_manifest.json"): + return _data_release_manifest_response( + model_version="1.600.0", + data_version="1.70.0", + compatible_model_packages=[ + {"name": "policyengine-us", "specifier": "==1.600.0"}, + ], + ) + raise AssertionError(f"Unexpected URL fetched: {url}") + + with patch("policyengine.provenance.bundle.urlopen", side_effect=fake_urlopen): + with pytest.raises(ValueError, match="not certified"): + refresh_release_bundle( + country="us", + model_version="1.653.3", + release_manifest_revision="release-manifest-commit-sha", + manifest_dir=sandbox["manifest_dir"], + pyproject_path=sandbox["pyproject_path"], + ) + + def test__custom_release_manifest_refreshes_long_term_dataset_hashes( sandbox, ) -> None: diff --git a/tests/test_release_manifests.py b/tests/test_release_manifests.py index c7a9f967..63d283ea 100644 --- a/tests/test_release_manifests.py +++ b/tests/test_release_manifests.py @@ -41,10 +41,11 @@ PYPROJECT.read_text(), re.MULTILINE, ).group(1) -US_MODEL_VERSION = "1.700.0" +US_MODEL_VERSION = "1.715.2" +US_BUILT_WITH_MODEL_VERSION = "1.700.0" US_DATA_RELEASE_VERSION = "1.115.5" US_DATA_RELEASE_PATH = "release_manifest.json" -US_DATA_RELEASE_REVISION = "688f972425f5e858fc52bda2b696e0af74fea920" +US_DATA_RELEASE_REVISION = "d47fb5475144260a75467d2f2e22b2d5d53d4d57" US_CERTIFICATION_SOURCE = "policyengine-us-data release manifest" US_DEFAULT_DATASET_URI = ( "hf://policyengine/policyengine-us-data/" @@ -109,7 +110,14 @@ def test__given_us_manifest__then_has_pinned_model_and_data_packages(self): manifest.certification.data_build_id == f"policyengine-us-data-{US_DATA_RELEASE_VERSION}" ) - assert manifest.certification.built_with_model_version == US_MODEL_VERSION + assert ( + manifest.certification.compatibility_basis + == "legacy_compatible_model_package" + ) + assert ( + manifest.certification.built_with_model_version + == US_BUILT_WITH_MODEL_VERSION + ) assert manifest.certification.certified_for_model_version == US_MODEL_VERSION def test__given_uk_manifest__then_has_pinned_model_and_data_packages(self): @@ -507,6 +515,34 @@ def test__given_private_manifest_unavailable_and_fingerprint_mismatch__then_fail else: raise AssertionError("Expected fingerprint mismatch to fail") + def test__given_legacy_compatible_certification__then_offline_fingerprint_mismatch_is_allowed( + self, + ): + get_data_release_manifest.cache_clear() + bundled_certification = DataCertification( + compatibility_basis="legacy_compatible_model_package", + certified_for_model_version="1.602.0", + data_build_fingerprint="sha256:build", + ) + + with ( + patch( + "policyengine.provenance.manifest.get_data_release_manifest", + side_effect=DataReleaseManifestUnavailableError("private repo"), + ), + patch( + "policyengine.provenance.manifest.get_release_manifest", + return_value=MagicMock(certification=bundled_certification), + ), + ): + certification = certify_data_release_compatibility( + "us", + runtime_model_version="1.602.0", + runtime_data_build_fingerprint="sha256:runtime", + ) + + assert certification == bundled_certification + def test__given_manifest_fetch_failure_and_version_mismatch__then_fallback_fails( self, ): diff --git a/uv.lock b/uv.lock index dc4aacff..caa33cf4 100644 --- a/uv.lock +++ b/uv.lock @@ -2820,7 +2820,7 @@ wheels = [ [[package]] name = "policyengine" -version = "4.12.0" +version = "4.12.1" source = { editable = "." } dependencies = [ { name = "diskcache" }, @@ -2897,8 +2897,8 @@ requires-dist = [ { name = "policyengine-core", marker = "extra == 'us'", specifier = "==3.26.1" }, { name = "policyengine-uk", marker = "extra == 'dev'", specifier = "==2.88.20" }, { name = "policyengine-uk", marker = "extra == 'uk'", specifier = "==2.88.20" }, - { name = "policyengine-us", marker = "extra == 'dev'", specifier = "==1.700.0" }, - { name = "policyengine-us", marker = "extra == 'us'", specifier = "==1.700.0" }, + { name = "policyengine-us", marker = "extra == 'dev'", specifier = "==1.715.2" }, + { name = "policyengine-us", marker = "extra == 'us'", specifier = "==1.715.2" }, { name = "psutil", specifier = ">=5.9.0" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "pytest", marker = "extra == 'dev'" }, @@ -2961,7 +2961,7 @@ wheels = [ [[package]] name = "policyengine-us" -version = "1.700.0" +version = "1.715.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "microdf-python" }, @@ -2973,9 +2973,9 @@ dependencies = [ { name = "tables", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/70/767fddeeb827e96e0f9a499c25f76c642767a129be259162eaf5b5954eb1/policyengine_us-1.700.0.tar.gz", hash = "sha256:63a7b1a2b8a0c903b6d704e8095f6880024e8fa93ea405912820a42589e90add", size = 9862854, upload-time = "2026-05-20T00:07:27.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/ef/d87bb056084897932e083b0412976a386d29062834b0e697afa044642a75/policyengine_us-1.715.2.tar.gz", hash = "sha256:b3990ae9b7c694d2cbf497e6256850aca7be5a5a73ac98330682aba9edd61b61", size = 10014025, upload-time = "2026-05-29T02:48:39.527Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/e9/2837a0d98e99efaf4d82aade276eee6eeff419df614863f08e3512961d2d/policyengine_us-1.700.0-py3-none-any.whl", hash = "sha256:7633d8aefcaf02d7628f841bc56750606f1d7fe409ff3ae7b0ef7e364a88e945", size = 10614505, upload-time = "2026-05-20T00:07:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/45/a1/1d56bdbb69d7ce06bedd3892203a75ac3350a90c0b5fcea2fb50db46670f/policyengine_us-1.715.2-py3-none-any.whl", hash = "sha256:abf079828419762f5c4b0291a70f6e424744200f237e1ae0f06e25f10130c399", size = 11035379, upload-time = "2026-05-29T02:48:35.193Z" }, ] [[package]] From 1f33d1268cb13b377353ad23939fd6ca93a9a3b2 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Mon, 1 Jun 2026 20:28:35 +0200 Subject: [PATCH 2/2] Fix US bundle test fixtures --- .../tax_benefit_models/us/datasets.py | 44 ++++++++++++++++++- .../tax_benefit_models/us/model.py | 6 +++ .../us_model_surface.json | 4 +- .../us_single_adult_no_income.json | 2 +- .../us_single_parent_one_child.json | 2 +- tests/test_models.py | 4 +- tests/test_us_long_term_datasets.py | 13 +++++- 7 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/policyengine/tax_benefit_models/us/datasets.py b/src/policyengine/tax_benefit_models/us/datasets.py index 4beeca89..71bbb395 100644 --- a/src/policyengine/tax_benefit_models/us/datasets.py +++ b/src/policyengine/tax_benefit_models/us/datasets.py @@ -567,6 +567,13 @@ def _runtime_policyengine_us_metadata() -> dict[str, Any]: return result +def _runtime_policyengine_us_version() -> Optional[str]: + try: + return importlib_metadata.version("policyengine-us") + except importlib_metadata.PackageNotFoundError: + return None + + def _runtime_policyengine_us_package_file() -> Optional[Path]: spec = importlib.util.find_spec("policyengine_us") if spec is None or spec.origin is None: @@ -629,6 +636,30 @@ def _validate_runtime_policyengine_us_match( ) +def _validate_runtime_policyengine_us_version(expected_version: Optional[str]) -> None: + if expected_version is None: + return + runtime_version = _runtime_policyengine_us_version() + if runtime_version != expected_version: + raise ValueError( + "Managed long-term datasets require policyengine-us runtime " + f"version {expected_version!r}, but the installed runtime is " + f"{runtime_version!r}." + ) + + +def _managed_long_term_dataset_model_version(manifest: Any) -> Optional[str]: + certification = getattr(manifest, "certification", None) + built_with_model_version = getattr( + certification, + "built_with_model_version", + None, + ) + if built_with_model_version: + return built_with_model_version + return manifest.model_package.version + + def validate_long_term_dataset_metadata( metadata: dict, *, @@ -1006,7 +1037,14 @@ def load_managed_long_term_datasets( manifest = get_release_manifest("us") if required_policyengine_us_version is None: - required_policyengine_us_version = manifest.model_package.version + required_policyengine_us_version = _managed_long_term_dataset_model_version( + manifest + ) + runtime_policyengine_us_version = manifest.model_package.version + require_metadata_runtime_match = ( + require_runtime_policyengine_us_match + and required_policyengine_us_version == runtime_policyengine_us_version + ) result = {} for year in years: @@ -1085,8 +1123,10 @@ def load_managed_long_term_datasets( required_policyengine_us_version=required_policyengine_us_version, required_policyengine_us_git_sha=required_policyengine_us_git_sha, require_policyengine_us_clean_build=require_policyengine_us_clean_build, - require_runtime_policyengine_us_match=require_runtime_policyengine_us_match, + require_runtime_policyengine_us_match=require_metadata_runtime_match, ) + if require_runtime_policyengine_us_match: + _validate_runtime_policyengine_us_version(runtime_policyengine_us_version) result[key] = _build_long_term_dataset( path=path, year=year, diff --git a/src/policyengine/tax_benefit_models/us/model.py b/src/policyengine/tax_benefit_models/us/model.py index 9d98c9fb..29bf4bf3 100644 --- a/src/policyengine/tax_benefit_models/us/model.py +++ b/src/policyengine/tax_benefit_models/us/model.py @@ -187,6 +187,12 @@ def run(self, simulation: "Simulation") -> "Simulation": # leaves the module-level one untouched. Building populations # against the module-level system would hide reform-registered # variables like ``ctc_minimum_refundable_amount`` at calc time. + if microsim.baseline is not None: + self._build_simulation_from_dataset( + microsim.baseline, + dataset, + microsim.baseline.tax_benefit_system, + ) self._build_simulation_from_dataset( microsim, dataset, microsim.tax_benefit_system ) diff --git a/tests/fixtures/household_calculator_snapshots/us_model_surface.json b/tests/fixtures/household_calculator_snapshots/us_model_surface.json index 93d04f47..ff2f0972 100644 --- a/tests/fixtures/household_calculator_snapshots/us_model_surface.json +++ b/tests/fixtures/household_calculator_snapshots/us_model_surface.json @@ -5,7 +5,7 @@ "has_income_tax": true, "has_region_registry": true, "model_package_name": "policyengine-us", - "num_parameters_bucketed_100s": 873, - "num_variables_bucketed_100s": 49, + "num_parameters_bucketed_100s": 883, + "num_variables_bucketed_100s": 51, "region_registry_country": "us" } diff --git a/tests/fixtures/household_calculator_snapshots/us_single_adult_no_income.json b/tests/fixtures/household_calculator_snapshots/us_single_adult_no_income.json index ff9f1859..102379d2 100644 --- a/tests/fixtures/household_calculator_snapshots/us_single_adult_no_income.json +++ b/tests/fixtures/household_calculator_snapshots/us_single_adult_no_income.json @@ -20,7 +20,7 @@ "person[0].is_child": 0.0, "person[0].is_male": 1.0, "person[0].marital_unit_id": 0.0, - "person[0].medicaid": 6439.11, + "person[0].medicaid": 9236.48, "person[0].medicare_cost": 0.0, "person[0].person_id": 0.0, "person[0].person_weight": 1.0, diff --git a/tests/fixtures/household_calculator_snapshots/us_single_parent_one_child.json b/tests/fixtures/household_calculator_snapshots/us_single_parent_one_child.json index d62c91f7..57a0aee0 100644 --- a/tests/fixtures/household_calculator_snapshots/us_single_parent_one_child.json +++ b/tests/fixtures/household_calculator_snapshots/us_single_parent_one_child.json @@ -38,7 +38,7 @@ "person[1].is_child": 1.0, "person[1].is_male": 1.0, "person[1].marital_unit_id": 0.0, - "person[1].medicaid": 3258.31, + "person[1].medicaid": 7254.54, "person[1].medicare_cost": 0.0, "person[1].person_id": 1.0, "person[1].person_weight": 1.0, diff --git a/tests/test_models.py b/tests/test_models.py index 7d375458..4b220d74 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -113,12 +113,12 @@ 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.700.0" + assert us_latest.model_package.version == "1.715.2" assert us_latest.data_package.name == "policyengine-us-data" assert us_latest.data_package.version == "1.115.5" assert ( us_latest.default_dataset_uri - == "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@688f972425f5e858fc52bda2b696e0af74fea920" + == "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@d47fb5475144260a75467d2f2e22b2d5d53d4d57" ) def test_has_hundreds_of_parameters(self): diff --git a/tests/test_us_long_term_datasets.py b/tests/test_us_long_term_datasets.py index 4f0be6fa..ceaae47a 100644 --- a/tests/test_us_long_term_datasets.py +++ b/tests/test_us_long_term_datasets.py @@ -134,9 +134,11 @@ def _manifest_with_long_term_sha( sha256: str, version: str = "1.691.12", metadata_sha256=None, + certification=None, ): return SimpleNamespace( model_package=SimpleNamespace(version=version), + certification=certification, datasets={ "long_term_cps_2100": SimpleNamespace( sha256=sha256, @@ -271,9 +273,18 @@ def test__load_managed_long_term_datasets__loads_bundled_local_mirror( "get_release_manifest", lambda country_id: _manifest_with_long_term_sha( _sha256(h5_path), - version="1.700.0", + version="1.715.2", + certification=SimpleNamespace( + compatibility_basis="legacy_compatible_model_package", + built_with_model_version="1.700.0", + ), ), ) + monkeypatch.setattr( + us_datasets_module, + "_runtime_policyengine_us_version", + lambda: "1.715.2", + ) monkeypatch.setattr( us_datasets_module, "resolve_managed_dataset_reference",