From fa49587192625485a3bceb2f4b84da932974eb2b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 18 Apr 2026 12:21:47 -0400 Subject: [PATCH] Accept additional compatible model specifiers on release manifests `build_release_manifest` now takes `additional_compatible_specifiers`: a list of PEP 440 specifier strings that extend the `compatible_model_packages` list beyond the single `==` entry it emits by default. Motivation: when the `data_build_fingerprint` is stable across a range of `policyengine-us` versions, the data package can declare compatibility without forcing downstream consumers (policyengine.py, policybench) to rebuild the dataset for every model patch release. Without this, the published release manifest lists only the exact build-time model version, which in practice is sometimes a broken pin (1.637.0 fails parameter validation against current policyengine-core). The policyengine.py side that consumes these specifiers was extended in PolicyEngine/policyengine.py#284 to handle full PEP 440 specifiers via packaging.specifiers.SpecifierSet. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../additional-compatible-specifiers.added.md | 7 ++++++ .../tests/test_release_manifest.py | 22 +++++++++++++++++++ .../utils/release_manifest.py | 16 ++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 changelog.d/additional-compatible-specifiers.added.md diff --git a/changelog.d/additional-compatible-specifiers.added.md b/changelog.d/additional-compatible-specifiers.added.md new file mode 100644 index 000000000..1a32e0762 --- /dev/null +++ b/changelog.d/additional-compatible-specifiers.added.md @@ -0,0 +1,7 @@ +`build_release_manifest` now accepts an +`additional_compatible_specifiers` parameter that extends the +`compatible_model_packages` list with arbitrary PEP 440 specifiers +(e.g. `">=1.637.0,<2.0.0"`). Use this when the data build fingerprint +is known to be stable across a range of `policyengine-us` versions so +downstream consumers do not have to regenerate the dataset for every +model patch release. diff --git a/policyengine_us_data/tests/test_release_manifest.py b/policyengine_us_data/tests/test_release_manifest.py index f6c1ba8ab..48bc2a9a5 100644 --- a/policyengine_us_data/tests/test_release_manifest.py +++ b/policyengine_us_data/tests/test_release_manifest.py @@ -86,6 +86,28 @@ def test_build_release_manifest_tracks_uploaded_artifacts(tmp_path): } +def test_build_release_manifest_adds_additional_compatible_specifiers(tmp_path): + national_path = _write_file( + tmp_path / "enhanced_cps_2024.h5", + b"national-dataset", + ) + + manifest = build_release_manifest( + files_with_repo_paths=[(national_path, "enhanced_cps_2024.h5")], + version="1.83.3", + repo_id="policyengine/policyengine-us-data", + model_package_version="1.637.0", + model_package_data_build_fingerprint="sha256:stable", + additional_compatible_specifiers=(">=1.637.0,<2.0.0",), + created_at="2026-04-18T12:00:00Z", + ) + + assert manifest["compatible_model_packages"] == [ + {"name": "policyengine-us", "specifier": "==1.637.0"}, + {"name": "policyengine-us", "specifier": ">=1.637.0,<2.0.0"}, + ] + + def test_build_release_manifest_merges_existing_release_same_version(tmp_path): district_bytes = b"district-dataset" district_path = _write_file(tmp_path / "NC-01.h5", district_bytes) diff --git a/policyengine_us_data/utils/release_manifest.py b/policyengine_us_data/utils/release_manifest.py index d85f8e8fb..82a60c145 100644 --- a/policyengine_us_data/utils/release_manifest.py +++ b/policyengine_us_data/utils/release_manifest.py @@ -42,6 +42,7 @@ def _base_manifest( model_package_data_build_fingerprint: str | None, build_id: str, created_at: str, + additional_compatible_specifiers: Sequence[str] | None = None, ) -> Dict: manifest = { "schema_version": RELEASE_MANIFEST_SCHEMA_VERSION, @@ -76,6 +77,10 @@ def _base_manifest( "specifier": f"=={model_package_version}", } ) + for specifier in additional_compatible_specifiers or (): + manifest["compatible_model_packages"].append( + {"name": model_package_name, "specifier": specifier} + ) return manifest @@ -107,6 +112,7 @@ def build_release_manifest( existing_manifest: Mapping | None = None, default_datasets: Optional[Mapping[str, str]] = None, created_at: str | None = None, + additional_compatible_specifiers: Sequence[str] | None = None, ) -> Dict: manifest = _normalize_existing_manifest( existing_manifest, @@ -126,6 +132,7 @@ def build_release_manifest( model_package_data_build_fingerprint=model_package_data_build_fingerprint, build_id=resolved_build_id, created_at=manifest_timestamp, + additional_compatible_specifiers=additional_compatible_specifiers, ) else: manifest["schema_version"] = RELEASE_MANIFEST_SCHEMA_VERSION @@ -144,13 +151,18 @@ def build_release_manifest( "git_sha": model_package_git_sha, "data_build_fingerprint": model_package_data_build_fingerprint, } + compat = [] if model_package_version: - manifest["compatible_model_packages"] = [ + compat.append( { "name": model_package_name, "specifier": f"=={model_package_version}", } - ] + ) + for specifier in additional_compatible_specifiers or (): + compat.append({"name": model_package_name, "specifier": specifier}) + if compat: + manifest["compatible_model_packages"] = compat if default_datasets: manifest.setdefault("default_datasets", {}).update(default_datasets)