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/utils/release_manifest.py b/policyengine_us_data/utils/release_manifest.py index d4d46b9fd..c62c3b46b 100644 --- a/policyengine_us_data/utils/release_manifest.py +++ b/policyengine_us_data/utils/release_manifest.py @@ -43,6 +43,7 @@ def _base_manifest( run_context: Mapping[str, str] | None, build_id: str, created_at: str, + additional_compatible_specifiers: Sequence[str] | None = None, ) -> Dict: manifest = { "schema_version": RELEASE_MANIFEST_SCHEMA_VERSION, @@ -79,6 +80,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 @@ -111,6 +116,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, @@ -131,6 +137,7 @@ def build_release_manifest( run_context=run_context, build_id=resolved_build_id, created_at=manifest_timestamp, + additional_compatible_specifiers=additional_compatible_specifiers, ) else: manifest["schema_version"] = RELEASE_MANIFEST_SCHEMA_VERSION @@ -149,15 +156,20 @@ def build_release_manifest( "git_sha": model_package_git_sha, "data_build_fingerprint": model_package_data_build_fingerprint, } + compat = [] if run_context: manifest["build"]["run"] = dict(run_context) 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) diff --git a/tests/unit/test_release_manifest.py b/tests/unit/test_release_manifest.py index 3a4a3bd0b..54b824979 100644 --- a/tests/unit/test_release_manifest.py +++ b/tests/unit/test_release_manifest.py @@ -107,6 +107,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)