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)