policyengine 3.4.2__tar.gz → 3.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. policyengine-3.4.4/.github/bump_version.py +135 -0
  2. {policyengine-3.4.2 → policyengine-3.4.4}/.github/workflows/push.yaml +4 -3
  3. {policyengine-3.4.2 → policyengine-3.4.4}/CHANGELOG.md +21 -0
  4. {policyengine-3.4.2 → policyengine-3.4.4}/Makefile +9 -3
  5. {policyengine-3.4.2 → policyengine-3.4.4}/PKG-INFO +5 -4
  6. {policyengine-3.4.2 → policyengine-3.4.4}/README.md +2 -1
  7. {policyengine-3.4.2 → policyengine-3.4.4}/docs/dev.md +8 -4
  8. {policyengine-3.4.2 → policyengine-3.4.4}/docs/index.md +1 -0
  9. {policyengine-3.4.2 → policyengine-3.4.4}/docs/myst.yml +1 -0
  10. policyengine-3.4.4/docs/release-bundles.md +329 -0
  11. {policyengine-3.4.2 → policyengine-3.4.4}/pyproject.toml +3 -3
  12. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/__init__.py +13 -0
  13. policyengine-3.4.4/src/policyengine/core/release_manifest.py +380 -0
  14. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/tax_benefit_model_version.py +81 -4
  15. policyengine-3.4.4/src/policyengine/core/trace_tro.py +259 -0
  16. policyengine-3.4.4/src/policyengine/data/release_manifests/uk.json +45 -0
  17. policyengine-3.4.4/src/policyengine/data/release_manifests/us.json +48 -0
  18. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/model.py +25 -33
  19. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/model.py +25 -21
  20. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine.egg-info/PKG-INFO +5 -4
  21. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine.egg-info/SOURCES.txt +3 -0
  22. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine.egg-info/requires.txt +2 -2
  23. policyengine-3.4.4/tests/test_bump_version.py +56 -0
  24. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_models.py +5 -5
  25. policyengine-3.4.4/tests/test_release_manifests.py +583 -0
  26. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_uk_regions.py +1 -1
  27. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_us_regions.py +3 -3
  28. policyengine-3.4.2/.github/bump_version.py +0 -77
  29. policyengine-3.4.2/src/policyengine/core/release_manifest.py +0 -178
  30. policyengine-3.4.2/src/policyengine/data/release_manifests/uk.json +0 -27
  31. policyengine-3.4.2/src/policyengine/data/release_manifests/us.json +0 -30
  32. policyengine-3.4.2/tests/test_release_manifests.py +0 -114
  33. {policyengine-3.4.2 → policyengine-3.4.4}/.claude/policyengine-guide.md +0 -0
  34. {policyengine-3.4.2 → policyengine-3.4.4}/.claude/quick-reference.md +0 -0
  35. {policyengine-3.4.2 → policyengine-3.4.4}/.github/CONTRIBUTING.md +0 -0
  36. {policyengine-3.4.2 → policyengine-3.4.4}/.github/changelog_template.md +0 -0
  37. {policyengine-3.4.2 → policyengine-3.4.4}/.github/check-changelog.sh +0 -0
  38. {policyengine-3.4.2 → policyengine-3.4.4}/.github/fetch_version.py +0 -0
  39. {policyengine-3.4.2 → policyengine-3.4.4}/.github/get-changelog-diff.sh +0 -0
  40. {policyengine-3.4.2 → policyengine-3.4.4}/.github/has-functional-changes.sh +0 -0
  41. {policyengine-3.4.2 → policyengine-3.4.4}/.github/is-version-number-acceptable.sh +0 -0
  42. {policyengine-3.4.2 → policyengine-3.4.4}/.github/publish-git-tag.sh +0 -0
  43. {policyengine-3.4.2 → policyengine-3.4.4}/.github/workflows/pr_code_changes.yaml +0 -0
  44. {policyengine-3.4.2 → policyengine-3.4.4}/.github/workflows/pr_docs_changes.yaml +0 -0
  45. {policyengine-3.4.2 → policyengine-3.4.4}/.gitignore +0 -0
  46. {policyengine-3.4.2 → policyengine-3.4.4}/.python-version +0 -0
  47. {policyengine-3.4.2 → policyengine-3.4.4}/LICENSE +0 -0
  48. {policyengine-3.4.2 → policyengine-3.4.4}/changelog.d/.gitkeep +0 -0
  49. {policyengine-3.4.2 → policyengine-3.4.4}/docs/.gitignore +0 -0
  50. {policyengine-3.4.2 → policyengine-3.4.4}/docs/advanced-outputs.md +0 -0
  51. {policyengine-3.4.2 → policyengine-3.4.4}/docs/core-concepts.md +0 -0
  52. {policyengine-3.4.2 → policyengine-3.4.4}/docs/country-models-uk.md +0 -0
  53. {policyengine-3.4.2 → policyengine-3.4.4}/docs/country-models-us.md +0 -0
  54. {policyengine-3.4.2 → policyengine-3.4.4}/docs/economic-impact-analysis.md +0 -0
  55. {policyengine-3.4.2 → policyengine-3.4.4}/docs/examples.md +0 -0
  56. {policyengine-3.4.2 → policyengine-3.4.4}/docs/regions-and-scoping.md +0 -0
  57. {policyengine-3.4.2 → policyengine-3.4.4}/docs/visualisation.md +0 -0
  58. {policyengine-3.4.2 → policyengine-3.4.4}/examples/employment_income_variation_uk.py +0 -0
  59. {policyengine-3.4.2 → policyengine-3.4.4}/examples/employment_income_variation_us.py +0 -0
  60. {policyengine-3.4.2 → policyengine-3.4.4}/examples/household_impact_example.py +0 -0
  61. {policyengine-3.4.2 → policyengine-3.4.4}/examples/income_bands_uk.py +0 -0
  62. {policyengine-3.4.2 → policyengine-3.4.4}/examples/income_distribution_us.py +0 -0
  63. {policyengine-3.4.2 → policyengine-3.4.4}/examples/paper_repro_uk.py +0 -0
  64. {policyengine-3.4.2 → policyengine-3.4.4}/examples/policy_change_uk.py +0 -0
  65. {policyengine-3.4.2 → policyengine-3.4.4}/examples/speedtest_us_simulation.py +0 -0
  66. {policyengine-3.4.2 → policyengine-3.4.4}/examples/us_budgetary_impact.py +0 -0
  67. {policyengine-3.4.2 → policyengine-3.4.4}/setup.cfg +0 -0
  68. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/__init__.py +0 -0
  69. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/cache.py +0 -0
  70. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/dataset.py +0 -0
  71. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/dataset_version.py +0 -0
  72. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/dynamic.py +0 -0
  73. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/output.py +0 -0
  74. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/parameter.py +0 -0
  75. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/parameter_node.py +0 -0
  76. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/parameter_value.py +0 -0
  77. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/policy.py +0 -0
  78. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/region.py +0 -0
  79. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/scoping_strategy.py +0 -0
  80. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/simulation.py +0 -0
  81. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/tax_benefit_model.py +0 -0
  82. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/core/variable.py +0 -0
  83. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/__init__.py +0 -0
  84. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/uk/__init__.py +0 -0
  85. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/uk/regions.py +0 -0
  86. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/us/__init__.py +0 -0
  87. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/us/data/__init__.py +0 -0
  88. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/us/data/districts.py +0 -0
  89. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/us/data/places.py +0 -0
  90. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/us/data/states.py +0 -0
  91. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/countries/us/regions.py +0 -0
  92. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/__init__.py +0 -0
  93. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/aggregate.py +0 -0
  94. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/change_aggregate.py +0 -0
  95. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/congressional_district_impact.py +0 -0
  96. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/constituency_impact.py +0 -0
  97. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/decile_impact.py +0 -0
  98. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/inequality.py +0 -0
  99. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/intra_decile_impact.py +0 -0
  100. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/local_authority_impact.py +0 -0
  101. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/outputs/poverty.py +0 -0
  102. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/__init__.py +0 -0
  103. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/analysis.py +0 -0
  104. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/datasets.py +0 -0
  105. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/outputs.py +0 -0
  106. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk.py +0 -0
  107. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/__init__.py +0 -0
  108. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/analysis.py +0 -0
  109. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/datasets.py +0 -0
  110. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/outputs.py +0 -0
  111. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us.py +0 -0
  112. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/utils/__init__.py +0 -0
  113. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/utils/dates.py +0 -0
  114. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/utils/entity_utils.py +0 -0
  115. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/utils/parameter_labels.py +0 -0
  116. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/utils/parametric_reforms.py +0 -0
  117. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine/utils/plotting.py +0 -0
  118. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine.egg-info/dependency_links.txt +0 -0
  119. {policyengine-3.4.2 → policyengine-3.4.4}/src/policyengine.egg-info/top_level.txt +0 -0
  120. {policyengine-3.4.2 → policyengine-3.4.4}/tests/__init__.py +0 -0
  121. {policyengine-3.4.2 → policyengine-3.4.4}/tests/conftest.py +0 -0
  122. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/__init__.py +0 -0
  123. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/filtering_fixtures.py +0 -0
  124. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/parameter_labels_fixtures.py +0 -0
  125. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/parametric_reforms_fixtures.py +0 -0
  126. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/poverty_by_demographics_fixtures.py +0 -0
  127. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/region_fixtures.py +0 -0
  128. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/us_reform_fixtures.py +0 -0
  129. {policyengine-3.4.2 → policyengine-3.4.4}/tests/fixtures/variable_label_fixtures.py +0 -0
  130. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_aggregate.py +0 -0
  131. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_cache.py +0 -0
  132. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_change_aggregate.py +0 -0
  133. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_congressional_district_impact.py +0 -0
  134. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_constituency_impact.py +0 -0
  135. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_entity_mapping.py +0 -0
  136. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_entity_utils.py +0 -0
  137. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_filtering.py +0 -0
  138. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_household_impact.py +0 -0
  139. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_inequality.py +0 -0
  140. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_intra_decile_impact.py +0 -0
  141. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_local_authority_impact.py +0 -0
  142. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_pandas3_compatibility.py +0 -0
  143. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_parameter_labels.py +0 -0
  144. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_parametric_reforms.py +0 -0
  145. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_poverty.py +0 -0
  146. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_poverty_by_demographics.py +0 -0
  147. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_poverty_run.py +0 -0
  148. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_region.py +0 -0
  149. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_scoping_strategy.py +0 -0
  150. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_us_reform_application.py +0 -0
  151. {policyengine-3.4.2 → policyengine-3.4.4}/tests/test_variable_labels.py +0 -0
  152. {policyengine-3.4.2 → policyengine-3.4.4}/uv.lock +0 -0
@@ -0,0 +1,135 @@
1
+ """Infer semver bump from towncrier fragment types and update version."""
2
+
3
+ import re
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ SEMVER_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
9
+
10
+
11
+ def parse_version(version: str) -> tuple[int, int, int]:
12
+ match = SEMVER_PATTERN.match(version)
13
+ if not match:
14
+ raise ValueError(f"Invalid semver: {version}")
15
+ return tuple(int(part) for part in match.groups())
16
+
17
+
18
+ def get_pyproject_version(pyproject_path: Path) -> str:
19
+ text = pyproject_path.read_text()
20
+ match = re.search(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE)
21
+ if not match:
22
+ print(
23
+ "Could not find version in pyproject.toml",
24
+ file=sys.stderr,
25
+ )
26
+ sys.exit(1)
27
+ return match.group(1)
28
+
29
+
30
+ def get_changelog_versions(changelog_path: Path) -> list[str]:
31
+ if not changelog_path.exists():
32
+ return []
33
+ return re.findall(
34
+ r"^## \[(\d+\.\d+\.\d+)\]", changelog_path.read_text(), re.MULTILINE
35
+ )
36
+
37
+
38
+ def get_git_tag_versions(repo_root: Path) -> list[str]:
39
+ try:
40
+ result = subprocess.run(
41
+ ["git", "tag"],
42
+ cwd=repo_root,
43
+ capture_output=True,
44
+ text=True,
45
+ check=True,
46
+ )
47
+ except (FileNotFoundError, subprocess.CalledProcessError):
48
+ return []
49
+
50
+ versions = []
51
+ for tag in result.stdout.splitlines():
52
+ normalized = tag.removeprefix("v").strip()
53
+ if SEMVER_PATTERN.match(normalized):
54
+ versions.append(normalized)
55
+ return versions
56
+
57
+
58
+ def get_current_version(
59
+ pyproject_path: Path,
60
+ changelog_path: Path,
61
+ repo_root: Path,
62
+ ) -> str:
63
+ candidates = [get_pyproject_version(pyproject_path)]
64
+ candidates.extend(get_changelog_versions(changelog_path))
65
+ candidates.extend(get_git_tag_versions(repo_root))
66
+ return max(candidates, key=parse_version)
67
+
68
+
69
+ def infer_bump(changelog_dir: Path) -> str:
70
+ fragments = [
71
+ f for f in changelog_dir.iterdir() if f.is_file() and f.name != ".gitkeep"
72
+ ]
73
+ if not fragments:
74
+ print("No changelog fragments found", file=sys.stderr)
75
+ sys.exit(1)
76
+
77
+ categories = {f.suffix.lstrip(".") for f in fragments}
78
+ for f in fragments:
79
+ parts = f.stem.split(".")
80
+ if len(parts) >= 2:
81
+ categories.add(parts[-1])
82
+
83
+ if "breaking" in categories:
84
+ return "major"
85
+ if "added" in categories or "removed" in categories:
86
+ return "minor"
87
+ return "patch"
88
+
89
+
90
+ def bump_version(version: str, bump: str) -> str:
91
+ major, minor, patch = (int(x) for x in version.split("."))
92
+ if bump == "major":
93
+ return f"{major + 1}.0.0"
94
+ elif bump == "minor":
95
+ return f"{major}.{minor + 1}.0"
96
+ else:
97
+ return f"{major}.{minor}.{patch + 1}"
98
+
99
+
100
+ def update_file(path: Path, new_version: str):
101
+ text = path.read_text()
102
+ updated, replacements = re.subn(
103
+ r'(^version\s*=\s*")(\d+\.\d+\.\d+)(")',
104
+ rf"\g<1>{new_version}\g<3>",
105
+ text,
106
+ flags=re.MULTILINE,
107
+ )
108
+ if replacements == 0:
109
+ print(
110
+ f"Could not update version in {path}",
111
+ file=sys.stderr,
112
+ )
113
+ sys.exit(1)
114
+ if updated != text:
115
+ path.write_text(updated)
116
+ print(f" Updated {path}")
117
+
118
+
119
+ def main():
120
+ root = Path(__file__).resolve().parent.parent
121
+ pyproject = root / "pyproject.toml"
122
+ changelog = root / "CHANGELOG.md"
123
+ changelog_dir = root / "changelog.d"
124
+
125
+ current = get_current_version(pyproject, changelog, root)
126
+ bump = infer_bump(changelog_dir)
127
+ new = bump_version(current, bump)
128
+
129
+ print(f"Version: {current} -> {new} ({bump})")
130
+
131
+ update_file(pyproject, new)
132
+
133
+
134
+ if __name__ == "__main__":
135
+ main()
@@ -81,10 +81,8 @@ jobs:
81
81
  - uses: actions/setup-node@v4
82
82
  with:
83
83
  node-version: 18.x
84
- - name: Install MyST
85
- run: npm install -g mystmd
86
84
  - name: Build HTML Assets
87
- run: cd docs && myst build --html
85
+ run: make docs
88
86
  - name: Upload artifact
89
87
  uses: actions/upload-pages-artifact@v3
90
88
  with:
@@ -108,6 +106,9 @@ jobs:
108
106
  uses: actions/checkout@v4
109
107
  with:
110
108
  token: ${{ steps.app-token.outputs.token }}
109
+ fetch-depth: 0
110
+ - name: Fetch tags
111
+ run: git fetch --tags --force
111
112
  - name: Setup Python
112
113
  uses: actions/setup-python@v5
113
114
  with:
@@ -1,3 +1,24 @@
1
+ ## [3.4.4] - 2026-04-13
2
+
3
+ ### Changed
4
+
5
+ - Add TRACE TRO export helpers for certified runtime bundles and expose them through `policyengine.core`.
6
+
7
+
8
+ ## [3.4.3] - 2026-04-13
9
+
10
+ ### Fixed
11
+
12
+ - Fix the release versioning workflow so it bumps from the highest known released version instead of regressing to a stale version from `pyproject.toml`.
13
+
14
+
15
+ ## [3.4.1] - 2026-04-13
16
+
17
+ ### Changed
18
+
19
+ - Add certified bundle metadata that records runtime model pins alongside build-time data artifact provenance and compatibility fingerprints.
20
+
21
+
1
22
  ## [3.4.2] - 2026-04-12
2
23
 
3
24
  ### Changed
@@ -1,9 +1,15 @@
1
- .PHONY: docs
1
+ .PHONY: docs docs-serve
2
+
3
+ MYSTMD_VERSION ?= 1.8.3
4
+ MYST_CMD = npx --yes mystmd@$(MYSTMD_VERSION)
2
5
 
3
6
  all: build-package
4
7
 
5
8
  docs:
6
- cd docs && jupyter book start
9
+ cd docs && $(MYST_CMD) build --html
10
+
11
+ docs-serve:
12
+ cd docs && $(MYST_CMD) start
7
13
 
8
14
  install:
9
15
  uv pip install -e .[dev]
@@ -27,4 +33,4 @@ build-package:
27
33
  python -m build
28
34
 
29
35
  test:
30
- pytest tests --cov=policyengine --cov-report=term-missing
36
+ pytest tests --cov=policyengine --cov-report=term-missing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.4.2
3
+ Version: 3.4.4
4
4
  Summary: A package to conduct policy analysis using PolicyEngine tax-benefit models.
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -678,7 +678,7 @@ Requires-Dist: requests>=2.31.0
678
678
  Requires-Dist: psutil>=5.9.0
679
679
  Provides-Extra: uk
680
680
  Requires-Dist: policyengine_core>=3.23.6; extra == "uk"
681
- Requires-Dist: policyengine-uk==2.78.0; extra == "uk"
681
+ Requires-Dist: policyengine-uk==2.74.0; extra == "uk"
682
682
  Provides-Extra: us
683
683
  Requires-Dist: policyengine_core>=3.23.6; extra == "us"
684
684
  Requires-Dist: policyengine-us==1.602.0; extra == "us"
@@ -693,7 +693,7 @@ Requires-Dist: build; extra == "dev"
693
693
  Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
694
694
  Requires-Dist: ruff>=0.9.0; extra == "dev"
695
695
  Requires-Dist: policyengine_core>=3.23.6; extra == "dev"
696
- Requires-Dist: policyengine-uk==2.78.0; extra == "dev"
696
+ Requires-Dist: policyengine-uk==2.74.0; extra == "dev"
697
697
  Requires-Dist: policyengine-us==1.602.0; extra == "dev"
698
698
  Requires-Dist: towncrier>=24.8.0; extra == "dev"
699
699
  Requires-Dist: mypy>=1.11.0; extra == "dev"
@@ -788,7 +788,8 @@ uv pip install -e .[dev] # install with dev dependencies (pytest, ruff, m
788
788
  ```bash
789
789
  make format # ruff format
790
790
  make test # pytest with coverage
791
- make docs # build Jupyter Book documentation
791
+ make docs # build static MyST/Jupyter Book 2 HTML docs
792
+ make docs-serve # preview the docs locally
792
793
  make clean # remove caches, build artifacts, .h5 files
793
794
  ```
794
795
 
@@ -86,7 +86,8 @@ uv pip install -e .[dev] # install with dev dependencies (pytest, ruff, m
86
86
  ```bash
87
87
  make format # ruff format
88
88
  make test # pytest with coverage
89
- make docs # build Jupyter Book documentation
89
+ make docs # build static MyST/Jupyter Book 2 HTML docs
90
+ make docs-serve # preview the docs locally
90
91
  make clean # remove caches, build artifacts, .h5 files
91
92
  ```
92
93
 
@@ -12,17 +12,19 @@
12
12
  ```bash
13
13
  git clone https://github.com/PolicyEngine/policyengine.py.git
14
14
  cd policyengine.py
15
- uv pip install -e .[dev]
15
+ uv pip install -e ".[dev]"
16
16
  ```
17
17
 
18
- This installs both UK and US country models plus dev dependencies (pytest, ruff, mypy, towncrier).
18
+ This installs the shared analysis layer, both country model extras, and the dev
19
+ dependencies used in CI (pytest, ruff, mypy, towncrier).
19
20
 
20
21
  ## Common commands
21
22
 
22
23
  ```bash
23
24
  make format # ruff format
24
25
  make test # pytest with coverage
25
- make docs # build documentation site
26
+ make docs # build static MyST/Jupyter Book 2 HTML docs
27
+ make docs-serve # preview the docs locally
26
28
  make clean # remove caches, build artifacts, .h5 files
27
29
  ```
28
30
 
@@ -60,7 +62,7 @@ PRs trigger the following checks:
60
62
  | Tests (Python 3.13) | Required | `make test` |
61
63
  | Tests (Python 3.14) | Required | `make test` |
62
64
  | Mypy | Informational | `mypy src/policyengine` |
63
- | Docs build | Required | MyST build |
65
+ | Docs build | Required | `make docs` |
64
66
 
65
67
  ## Versioning and releases
66
68
 
@@ -73,6 +75,8 @@ echo "Description of change" > changelog.d/my-change.added
73
75
 
74
76
  On merge, the versioning workflow bumps the version, builds the changelog, and creates a GitHub Release.
75
77
 
78
+ For the target release-bundle architecture, see [Release bundles](release-bundles.md). That document defines the split between country `*-data` build manifests and `policyengine.py` certified runtime bundles.
79
+
76
80
  ## Architecture
77
81
 
78
82
  ### Package layout
@@ -16,4 +16,5 @@ We do this by:
16
16
  - [US tax-benefit model](country-models-us.md): Entities, parameters, reform examples
17
17
  - [Examples](examples.md): Complete working scripts
18
18
  - [Visualisation](visualisation.md): Publication-ready charts with Plotly
19
+ - [Release bundles](release-bundles.md): Reproducible model-plus-data certification and provenance
19
20
  - [Development](dev.md): Setup, testing, CI, architecture
@@ -17,6 +17,7 @@ project:
17
17
  - file: country-models-us.md
18
18
  - file: examples.md
19
19
  - file: visualisation.md
20
+ - file: release-bundles.md
20
21
  - file: dev.md
21
22
 
22
23
  site:
@@ -0,0 +1,329 @@
1
+ # Release Bundles
2
+
3
+ This document defines the intended reproducibility boundary for `policyengine.py`.
4
+
5
+ The key design decision is:
6
+
7
+ - country `*-data` repos build and stage immutable data artifacts
8
+ - `policyengine.py` is the only component that certifies supported runtime bundles
9
+ - `policyengine.py` does not rebuild country data itself
10
+
11
+ This keeps country-specific data construction in the country data repos while still giving users a single top-level version to cite and pin.
12
+
13
+ ## Why this boundary exists
14
+
15
+ For countries like the UK, the data package is not model-independent. Dataset construction, imputations, and calibration steps call the country model directly. That means a published dataset artifact depends on:
16
+
17
+ - the country model version used during data construction
18
+ - the calibration targets used during data construction
19
+ - the raw input data used during data construction
20
+
21
+ If `policyengine.py` only pins a country model version and a data package version without checking that relationship, the provenance boundary is incomplete.
22
+
23
+ ## Roles
24
+
25
+ ### Country model package
26
+
27
+ Examples: `policyengine-uk`, `policyengine-us`
28
+
29
+ The country model package owns:
30
+
31
+ - policy logic
32
+ - variables and parameters
33
+ - reforms
34
+ - a `data_build_fingerprint` for the subset of model logic that affects data construction
35
+
36
+ It does not own final runtime bundle certification.
37
+
38
+ ### Country data package
39
+
40
+ Examples: `policyengine-uk-data`, `policyengine-us-data`
41
+
42
+ The country data package owns:
43
+
44
+ - data build pipelines
45
+ - raw input acquisition
46
+ - calibration target snapshots
47
+ - expensive dataset construction
48
+ - staging immutable build artifacts with provenance
49
+
50
+ It does not define the final supported runtime bundle exposed to users.
51
+
52
+ ### `policyengine.py`
53
+
54
+ `policyengine.py` owns:
55
+
56
+ - runtime bundle certification
57
+ - user-facing reproducibility boundaries
58
+ - the supported mapping from `policyengine.py` version to country model version and certified data artifact
59
+
60
+ It does not rebuild microdata artifacts.
61
+
62
+ ## Two manifest layers
63
+
64
+ The architecture has two manifest layers with different responsibilities.
65
+
66
+ ### 1. Data build manifest
67
+
68
+ Published by the country `*-data` repo.
69
+
70
+ This answers:
71
+
72
+ - what bytes were produced
73
+ - how they were produced
74
+ - which exact model and targets produced them
75
+
76
+ Suggested schema:
77
+
78
+ ```json
79
+ {
80
+ "schema_version": 1,
81
+ "country_id": "uk",
82
+ "data_package": {
83
+ "name": "policyengine-uk-data",
84
+ "version": "1.41.0"
85
+ },
86
+ "build": {
87
+ "build_id": "uk-data-2026-04-12T12-30-00Z",
88
+ "git_sha": "abc123",
89
+ "built_at": "2026-04-12T12:30:00Z",
90
+ "built_with_model_package": {
91
+ "name": "policyengine-uk",
92
+ "version": "2.81.0",
93
+ "git_sha": "def456",
94
+ "data_build_fingerprint": "sha256:..."
95
+ },
96
+ "calibration_targets": {
97
+ "snapshot_id": "2026-04-10",
98
+ "sha256": "sha256:..."
99
+ },
100
+ "raw_inputs": [
101
+ {
102
+ "name": "frs_2023_24",
103
+ "sha256": "sha256:..."
104
+ }
105
+ ],
106
+ "build_environment": {
107
+ "python_version": "3.13.3",
108
+ "lockfile_sha256": "sha256:..."
109
+ }
110
+ },
111
+ "default_datasets": {
112
+ "national": "enhanced_frs_2023_24",
113
+ "baseline": "frs_2023_24"
114
+ },
115
+ "artifacts": {
116
+ "enhanced_frs_2023_24": {
117
+ "kind": "microdata",
118
+ "repo_id": "policyengine/policyengine-uk-data-private",
119
+ "path": "builds/uk-data-2026-04-12T12-30-00Z/enhanced_frs_2023_24.h5",
120
+ "revision": "uk-data-2026-04-12T12-30-00Z",
121
+ "sha256": "sha256:...",
122
+ "size_bytes": 123456789
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ Notes:
129
+
130
+ - `build_id` must be immutable.
131
+ - build artifacts should be staged under a build-specific path or revision, not a floating release tag.
132
+ - the build manifest is the authoritative provenance record for the artifact bytes.
133
+
134
+ ### 2. Certified runtime bundle manifest
135
+
136
+ Published by `policyengine.py`.
137
+
138
+ This answers:
139
+
140
+ - which model and data artifact are supported together at runtime
141
+ - which exact dataset should be used by default
142
+ - which artifact checksum and provenance should be surfaced to users
143
+
144
+ Suggested schema:
145
+
146
+ ```json
147
+ {
148
+ "schema_version": 1,
149
+ "policyengine_version": "3.5.0",
150
+ "bundle_id": "uk-3.5.0",
151
+ "published_at": "2026-04-12T13:00:00Z",
152
+ "country_id": "uk",
153
+ "model_package": {
154
+ "name": "policyengine-uk",
155
+ "version": "2.81.1"
156
+ },
157
+ "certified_data_artifact": {
158
+ "data_package": {
159
+ "name": "policyengine-uk-data",
160
+ "version": "1.41.0"
161
+ },
162
+ "build_id": "uk-data-2026-04-12T12-30-00Z",
163
+ "dataset": "enhanced_frs_2023_24",
164
+ "uri": "hf://policyengine/policyengine-uk-data-private/builds/uk-data-2026-04-12T12-30-00Z/enhanced_frs_2023_24.h5@uk-data-2026-04-12T12-30-00Z",
165
+ "sha256": "sha256:..."
166
+ },
167
+ "certification": {
168
+ "compatibility_basis": "matching_data_build_fingerprint",
169
+ "built_with_model_version": "2.81.0",
170
+ "certified_for_model_version": "2.81.1",
171
+ "data_build_fingerprint": "sha256:...",
172
+ "certified_by": "policyengine.py release workflow"
173
+ },
174
+ "default_dataset": "enhanced_frs_2023_24",
175
+ "region_artifacts": {
176
+ "national": {
177
+ "dataset": "enhanced_frs_2023_24"
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ Notes:
184
+
185
+ - this is the user-facing reproducibility boundary
186
+ - apps and APIs should surface this bundle, not only country package versions
187
+ - a bundle may reuse a previously staged data artifact if compatibility is explicitly certified
188
+
189
+ ## TRACE export
190
+
191
+ The internal build manifest and certified runtime bundle remain the operational source of
192
+ truth.
193
+
194
+ TRACE sits on top of those manifests as a standards-based export layer.
195
+
196
+ ### What gets exported
197
+
198
+ Country `*-data` repos should emit a `trace.tro.jsonld` file for each published data
199
+ release. That TRO should cover:
200
+
201
+ - the release manifest itself
202
+ - each published artifact hash listed in the release manifest
203
+ - the build-time model provenance recorded in the release manifest
204
+
205
+ `policyengine.py` should emit a separate certified-bundle TRO. That TRO should cover:
206
+
207
+ - the bundled country release manifest shipped in `policyengine.py`
208
+ - the country data release manifest resolved for the certified data package version
209
+ - the certified dataset artifact hash
210
+ - the certification basis used to allow runtime reuse
211
+
212
+ ### What TRACE does not replace
213
+
214
+ TRACE is not the source of truth for compatibility policy.
215
+
216
+ In particular, TRACE does not decide:
217
+
218
+ - whether a new model version can safely reuse an existing data artifact
219
+ - how `data_build_fingerprint` is computed
220
+ - which staged artifact becomes a supported runtime default
221
+
222
+ Those decisions still belong to the country data build manifest and the
223
+ `policyengine.py` certified runtime bundle.
224
+
225
+ ### Why we still want it
226
+
227
+ TRACE adds three things our internal manifests do not provide by themselves:
228
+
229
+ - a standard declaration format for provenance exchange
230
+ - a composition fingerprint over the exact artifacts in scope
231
+ - a better external surface for papers, audits, and reproducibility reviews
232
+
233
+ That is why the recommended design is:
234
+
235
+ - internal manifests for build/certification control
236
+ - generated TRACE TROs for standards-based export
237
+
238
+ ## Compatibility rule
239
+
240
+ The architecture should avoid forcing a new data build for every harmless country model release.
241
+
242
+ To do that safely, compatibility must be explicit.
243
+
244
+ ### Data build fingerprint
245
+
246
+ Each country model package should expose a `data_build_fingerprint` that covers the subset of logic that affects dataset construction or calibration.
247
+
248
+ Examples of inputs to the fingerprint:
249
+
250
+ - variables used in imputations
251
+ - variables used in calibration loss matrices
252
+ - parameters referenced during data construction
253
+ - uprating or target-computation logic used during the build
254
+
255
+ Things that should usually not affect the fingerprint:
256
+
257
+ - runtime-only outputs that are not used in data construction
258
+ - UI-oriented metadata
259
+ - code paths unrelated to data construction
260
+
261
+ ### Certification rules
262
+
263
+ `policyengine.py` may certify a staged data artifact for a model version only if one of the following is true:
264
+
265
+ 1. the model version exactly matches the `built_with_model_package.version`
266
+ 2. the model version has the same `data_build_fingerprint` as the build-time model version
267
+
268
+ If neither is true, the bundle release must fail and a new data build is required.
269
+
270
+ This should be a hard failure, not a warning.
271
+
272
+ ## Artifact states
273
+
274
+ Artifacts should move through explicit states:
275
+
276
+ - `staged`: built by the country data repo and available for inspection or later certification
277
+ - `certified`: referenced by a released `policyengine.py` runtime bundle
278
+ - `deprecated`: no longer recommended for new use, but still reproducible
279
+
280
+ The key point is that `staged` and `certified` are different states. A staged artifact is not automatically part of a supported runtime release.
281
+
282
+ ## UK release workflow
283
+
284
+ ### Case 1: model-only release
285
+
286
+ 1. Cut UK model release candidate `M`.
287
+ 2. Compute `data_build_fingerprint(M)`.
288
+ 3. Compare it to the fingerprint recorded in the previously certified data build manifest.
289
+ 4. If the fingerprint matches, skip the expensive UK data rebuild.
290
+ 5. Release `policyengine.py` with a new certified runtime bundle that points to the existing staged UK artifact.
291
+
292
+ ### Case 2: data-affecting release
293
+
294
+ 1. Cut UK model release candidate `M`.
295
+ 2. Compute `data_build_fingerprint(M)`.
296
+ 3. If the fingerprint changed, build a new UK data artifact in `policyengine-uk-data` against:
297
+ - exact `policyengine-uk==M`
298
+ - exact target snapshot
299
+ - exact raw input hashes
300
+ 4. Stage the new artifact under a build-specific immutable path or revision.
301
+ 5. Publish the UK data build manifest.
302
+ 6. Release `policyengine.py` with a certified runtime bundle that points to the new staged artifact.
303
+
304
+ ## Implementation guidance
305
+
306
+ The current `release_manifest.json` mechanism in country data repos is a good starting point, but it is not yet enough on its own. The target implementation should add:
307
+
308
+ - `built_with_model_package.version`
309
+ - `built_with_model_package.git_sha`
310
+ - `built_with_model_package.data_build_fingerprint`
311
+ - calibration target snapshot metadata
312
+ - immutable staged artifact paths or revisions
313
+
314
+ The target implementation in `policyengine.py` should add:
315
+
316
+ - hard validation of bundle certification rules
317
+ - explicit runtime bundle metadata on simulations, APIs, and app responses
318
+ - checksum-backed dataset resolution from the certified bundle manifest
319
+
320
+ ## Why not let `policyengine.py` build all country data directly?
321
+
322
+ Because that would centralise the wrong concerns:
323
+
324
+ - country-specific private data handling would move into the generic orchestration layer
325
+ - country-specific build logic would move into the generic orchestration layer
326
+ - expensive build failures would block the top-level runtime package more often
327
+ - provenance would still originate in the country data pipeline, so `policyengine.py` would not actually eliminate the need for the country build manifest
328
+
329
+ `policyengine.py` should be the certification boundary, not the country data build system.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "3.4.2"
7
+ version = "3.4.4"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -28,7 +28,7 @@ dependencies = [
28
28
  [project.optional-dependencies]
29
29
  uk = [
30
30
  "policyengine_core>=3.23.6",
31
- "policyengine-uk==2.78.0",
31
+ "policyengine-uk==2.74.0",
32
32
  ]
33
33
  us = [
34
34
  "policyengine_core>=3.23.6",
@@ -45,7 +45,7 @@ dev = [
45
45
  "pytest-asyncio>=0.26.0",
46
46
  "ruff>=0.9.0",
47
47
  "policyengine_core>=3.23.6",
48
- "policyengine-uk==2.78.0",
48
+ "policyengine-uk==2.74.0",
49
49
  "policyengine-us==1.602.0",
50
50
  "towncrier>=24.8.0",
51
51
  "mypy>=1.11.0",