policyengine 3.4.3__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 (147) hide show
  1. {policyengine-3.4.3 → policyengine-3.4.4}/.github/workflows/push.yaml +1 -3
  2. {policyengine-3.4.3 → policyengine-3.4.4}/CHANGELOG.md +7 -0
  3. {policyengine-3.4.3 → policyengine-3.4.4}/Makefile +9 -3
  4. {policyengine-3.4.3 → policyengine-3.4.4}/PKG-INFO +3 -2
  5. {policyengine-3.4.3 → policyengine-3.4.4}/README.md +2 -1
  6. {policyengine-3.4.3 → policyengine-3.4.4}/docs/dev.md +2 -1
  7. {policyengine-3.4.3 → policyengine-3.4.4}/docs/release-bundles.md +49 -0
  8. {policyengine-3.4.3 → policyengine-3.4.4}/pyproject.toml +1 -1
  9. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/__init__.py +7 -0
  10. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/tax_benefit_model_version.py +23 -1
  11. policyengine-3.4.4/src/policyengine/core/trace_tro.py +259 -0
  12. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine.egg-info/PKG-INFO +3 -2
  13. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine.egg-info/SOURCES.txt +1 -0
  14. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_release_manifests.py +185 -0
  15. {policyengine-3.4.3 → policyengine-3.4.4}/.claude/policyengine-guide.md +0 -0
  16. {policyengine-3.4.3 → policyengine-3.4.4}/.claude/quick-reference.md +0 -0
  17. {policyengine-3.4.3 → policyengine-3.4.4}/.github/CONTRIBUTING.md +0 -0
  18. {policyengine-3.4.3 → policyengine-3.4.4}/.github/bump_version.py +0 -0
  19. {policyengine-3.4.3 → policyengine-3.4.4}/.github/changelog_template.md +0 -0
  20. {policyengine-3.4.3 → policyengine-3.4.4}/.github/check-changelog.sh +0 -0
  21. {policyengine-3.4.3 → policyengine-3.4.4}/.github/fetch_version.py +0 -0
  22. {policyengine-3.4.3 → policyengine-3.4.4}/.github/get-changelog-diff.sh +0 -0
  23. {policyengine-3.4.3 → policyengine-3.4.4}/.github/has-functional-changes.sh +0 -0
  24. {policyengine-3.4.3 → policyengine-3.4.4}/.github/is-version-number-acceptable.sh +0 -0
  25. {policyengine-3.4.3 → policyengine-3.4.4}/.github/publish-git-tag.sh +0 -0
  26. {policyengine-3.4.3 → policyengine-3.4.4}/.github/workflows/pr_code_changes.yaml +0 -0
  27. {policyengine-3.4.3 → policyengine-3.4.4}/.github/workflows/pr_docs_changes.yaml +0 -0
  28. {policyengine-3.4.3 → policyengine-3.4.4}/.gitignore +0 -0
  29. {policyengine-3.4.3 → policyengine-3.4.4}/.python-version +0 -0
  30. {policyengine-3.4.3 → policyengine-3.4.4}/LICENSE +0 -0
  31. {policyengine-3.4.3 → policyengine-3.4.4}/changelog.d/.gitkeep +0 -0
  32. {policyengine-3.4.3 → policyengine-3.4.4}/docs/.gitignore +0 -0
  33. {policyengine-3.4.3 → policyengine-3.4.4}/docs/advanced-outputs.md +0 -0
  34. {policyengine-3.4.3 → policyengine-3.4.4}/docs/core-concepts.md +0 -0
  35. {policyengine-3.4.3 → policyengine-3.4.4}/docs/country-models-uk.md +0 -0
  36. {policyengine-3.4.3 → policyengine-3.4.4}/docs/country-models-us.md +0 -0
  37. {policyengine-3.4.3 → policyengine-3.4.4}/docs/economic-impact-analysis.md +0 -0
  38. {policyengine-3.4.3 → policyengine-3.4.4}/docs/examples.md +0 -0
  39. {policyengine-3.4.3 → policyengine-3.4.4}/docs/index.md +0 -0
  40. {policyengine-3.4.3 → policyengine-3.4.4}/docs/myst.yml +0 -0
  41. {policyengine-3.4.3 → policyengine-3.4.4}/docs/regions-and-scoping.md +0 -0
  42. {policyengine-3.4.3 → policyengine-3.4.4}/docs/visualisation.md +0 -0
  43. {policyengine-3.4.3 → policyengine-3.4.4}/examples/employment_income_variation_uk.py +0 -0
  44. {policyengine-3.4.3 → policyengine-3.4.4}/examples/employment_income_variation_us.py +0 -0
  45. {policyengine-3.4.3 → policyengine-3.4.4}/examples/household_impact_example.py +0 -0
  46. {policyengine-3.4.3 → policyengine-3.4.4}/examples/income_bands_uk.py +0 -0
  47. {policyengine-3.4.3 → policyengine-3.4.4}/examples/income_distribution_us.py +0 -0
  48. {policyengine-3.4.3 → policyengine-3.4.4}/examples/paper_repro_uk.py +0 -0
  49. {policyengine-3.4.3 → policyengine-3.4.4}/examples/policy_change_uk.py +0 -0
  50. {policyengine-3.4.3 → policyengine-3.4.4}/examples/speedtest_us_simulation.py +0 -0
  51. {policyengine-3.4.3 → policyengine-3.4.4}/examples/us_budgetary_impact.py +0 -0
  52. {policyengine-3.4.3 → policyengine-3.4.4}/setup.cfg +0 -0
  53. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/__init__.py +0 -0
  54. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/cache.py +0 -0
  55. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/dataset.py +0 -0
  56. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/dataset_version.py +0 -0
  57. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/dynamic.py +0 -0
  58. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/output.py +0 -0
  59. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/parameter.py +0 -0
  60. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/parameter_node.py +0 -0
  61. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/parameter_value.py +0 -0
  62. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/policy.py +0 -0
  63. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/region.py +0 -0
  64. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/release_manifest.py +0 -0
  65. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/scoping_strategy.py +0 -0
  66. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/simulation.py +0 -0
  67. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/tax_benefit_model.py +0 -0
  68. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/core/variable.py +0 -0
  69. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/__init__.py +0 -0
  70. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/uk/__init__.py +0 -0
  71. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/uk/regions.py +0 -0
  72. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/us/__init__.py +0 -0
  73. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/us/data/__init__.py +0 -0
  74. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/us/data/districts.py +0 -0
  75. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/us/data/places.py +0 -0
  76. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/us/data/states.py +0 -0
  77. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/countries/us/regions.py +0 -0
  78. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/data/release_manifests/uk.json +0 -0
  79. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/data/release_manifests/us.json +0 -0
  80. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/__init__.py +0 -0
  81. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/aggregate.py +0 -0
  82. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/change_aggregate.py +0 -0
  83. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/congressional_district_impact.py +0 -0
  84. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/constituency_impact.py +0 -0
  85. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/decile_impact.py +0 -0
  86. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/inequality.py +0 -0
  87. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/intra_decile_impact.py +0 -0
  88. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/local_authority_impact.py +0 -0
  89. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/outputs/poverty.py +0 -0
  90. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/__init__.py +0 -0
  91. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/analysis.py +0 -0
  92. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/datasets.py +0 -0
  93. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/model.py +0 -0
  94. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk/outputs.py +0 -0
  95. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/uk.py +0 -0
  96. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/__init__.py +0 -0
  97. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/analysis.py +0 -0
  98. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/datasets.py +0 -0
  99. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/model.py +0 -0
  100. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us/outputs.py +0 -0
  101. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/tax_benefit_models/us.py +0 -0
  102. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/utils/__init__.py +0 -0
  103. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/utils/dates.py +0 -0
  104. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/utils/entity_utils.py +0 -0
  105. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/utils/parameter_labels.py +0 -0
  106. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/utils/parametric_reforms.py +0 -0
  107. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine/utils/plotting.py +0 -0
  108. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine.egg-info/dependency_links.txt +0 -0
  109. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine.egg-info/requires.txt +0 -0
  110. {policyengine-3.4.3 → policyengine-3.4.4}/src/policyengine.egg-info/top_level.txt +0 -0
  111. {policyengine-3.4.3 → policyengine-3.4.4}/tests/__init__.py +0 -0
  112. {policyengine-3.4.3 → policyengine-3.4.4}/tests/conftest.py +0 -0
  113. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/__init__.py +0 -0
  114. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/filtering_fixtures.py +0 -0
  115. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/parameter_labels_fixtures.py +0 -0
  116. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/parametric_reforms_fixtures.py +0 -0
  117. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/poverty_by_demographics_fixtures.py +0 -0
  118. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/region_fixtures.py +0 -0
  119. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/us_reform_fixtures.py +0 -0
  120. {policyengine-3.4.3 → policyengine-3.4.4}/tests/fixtures/variable_label_fixtures.py +0 -0
  121. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_aggregate.py +0 -0
  122. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_bump_version.py +0 -0
  123. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_cache.py +0 -0
  124. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_change_aggregate.py +0 -0
  125. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_congressional_district_impact.py +0 -0
  126. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_constituency_impact.py +0 -0
  127. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_entity_mapping.py +0 -0
  128. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_entity_utils.py +0 -0
  129. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_filtering.py +0 -0
  130. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_household_impact.py +0 -0
  131. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_inequality.py +0 -0
  132. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_intra_decile_impact.py +0 -0
  133. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_local_authority_impact.py +0 -0
  134. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_models.py +0 -0
  135. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_pandas3_compatibility.py +0 -0
  136. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_parameter_labels.py +0 -0
  137. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_parametric_reforms.py +0 -0
  138. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_poverty.py +0 -0
  139. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_poverty_by_demographics.py +0 -0
  140. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_poverty_run.py +0 -0
  141. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_region.py +0 -0
  142. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_scoping_strategy.py +0 -0
  143. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_uk_regions.py +0 -0
  144. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_us_reform_application.py +0 -0
  145. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_us_regions.py +0 -0
  146. {policyengine-3.4.3 → policyengine-3.4.4}/tests/test_variable_labels.py +0 -0
  147. {policyengine-3.4.3 → policyengine-3.4.4}/uv.lock +0 -0
@@ -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:
@@ -1,3 +1,10 @@
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
+
1
8
  ## [3.4.3] - 2026-04-13
2
9
 
3
10
  ### Fixed
@@ -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.3
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
@@ -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
 
@@ -23,7 +23,8 @@ dependencies used in CI (pytest, ruff, mypy, towncrier).
23
23
  ```bash
24
24
  make format # ruff format
25
25
  make test # pytest with coverage
26
- make docs # run the MyST docs build used in CI via npx
26
+ make docs # build static MyST/Jupyter Book 2 HTML docs
27
+ make docs-serve # preview the docs locally
27
28
  make clean # remove caches, build artifacts, .h5 files
28
29
  ```
29
30
 
@@ -186,6 +186,55 @@ Notes:
186
186
  - apps and APIs should surface this bundle, not only country package versions
187
187
  - a bundle may reuse a previously staged data artifact if compatibility is explicitly certified
188
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
+
189
238
  ## Compatibility rule
190
239
 
191
240
  The architecture should avoid forcing a new data build for every harmless country model release.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "3.4.3"
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 = [
@@ -36,6 +36,13 @@ from .tax_benefit_model import TaxBenefitModel as TaxBenefitModel
36
36
  from .tax_benefit_model_version import (
37
37
  TaxBenefitModelVersion as TaxBenefitModelVersion,
38
38
  )
39
+ from .trace_tro import (
40
+ build_trace_tro_from_release_bundle as build_trace_tro_from_release_bundle,
41
+ )
42
+ from .trace_tro import (
43
+ compute_trace_composition_fingerprint as compute_trace_composition_fingerprint,
44
+ )
45
+ from .trace_tro import serialize_trace_tro as serialize_trace_tro
39
46
  from .variable import Variable as Variable
40
47
 
41
48
  # Rebuild models to resolve forward references
@@ -4,8 +4,14 @@ from uuid import uuid4
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
- from .release_manifest import CountryReleaseManifest, DataCertification, PackageVersion
7
+ from .release_manifest import (
8
+ CountryReleaseManifest,
9
+ DataCertification,
10
+ PackageVersion,
11
+ get_data_release_manifest,
12
+ )
8
13
  from .tax_benefit_model import TaxBenefitModel
14
+ from .trace_tro import build_trace_tro_from_release_bundle
9
15
 
10
16
  if TYPE_CHECKING:
11
17
  from .parameter import Parameter
@@ -201,6 +207,22 @@ class TaxBenefitModelVersion(BaseModel):
201
207
  ),
202
208
  }
203
209
 
210
+ @property
211
+ def trace_tro(self) -> dict:
212
+ if self.release_manifest is None:
213
+ raise ValueError(
214
+ "TRACE TRO export requires a bundled country release manifest."
215
+ )
216
+
217
+ data_release_manifest = get_data_release_manifest(
218
+ self.release_manifest.country_id
219
+ )
220
+ return build_trace_tro_from_release_bundle(
221
+ self.release_manifest,
222
+ data_release_manifest,
223
+ certification=self.data_certification,
224
+ )
225
+
204
226
  def __repr__(self) -> str:
205
227
  # Give the id and version, and the number of variables, parameters, parameter nodes, parameter values
206
228
  return f"<TaxBenefitModelVersion id={self.id} variables={len(self.variables)} parameters={len(self.parameters)} parameter_nodes={len(self.parameter_nodes)} parameter_values={len(self.parameter_values)}>"
@@ -0,0 +1,259 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ from collections.abc import Iterable, Mapping
6
+
7
+ from .release_manifest import (
8
+ CountryReleaseManifest,
9
+ DataCertification,
10
+ DataReleaseManifest,
11
+ )
12
+
13
+ TRACE_TROV_VERSION = "0.1"
14
+ TRACE_CONTEXT = [
15
+ {
16
+ "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
17
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
18
+ "trov": "https://w3id.org/trace/trov/0.1#",
19
+ "schema": "https://schema.org/",
20
+ }
21
+ ]
22
+
23
+
24
+ def _hash_object(value: str) -> dict[str, str]:
25
+ return {
26
+ "trov:hashAlgorithm": "sha256",
27
+ "trov:hashValue": value,
28
+ }
29
+
30
+
31
+ def _artifact_mime_type(path_or_uri: str) -> str | None:
32
+ suffix = path_or_uri.rsplit(".", 1)[-1].lower() if "." in path_or_uri else ""
33
+ return {
34
+ "h5": "application/x-hdf5",
35
+ "json": "application/json",
36
+ "jsonld": "application/ld+json",
37
+ }.get(suffix)
38
+
39
+
40
+ def _canonical_json_bytes(value: Mapping) -> bytes:
41
+ return (json.dumps(value, indent=2, sort_keys=True) + "\n").encode("utf-8")
42
+
43
+
44
+ def compute_trace_composition_fingerprint(
45
+ artifact_hashes: Iterable[str],
46
+ ) -> str:
47
+ digest = hashlib.sha256()
48
+ digest.update("".join(sorted(artifact_hashes)).encode("utf-8"))
49
+ return digest.hexdigest()
50
+
51
+
52
+ def build_trace_tro_from_release_bundle(
53
+ country_manifest: CountryReleaseManifest,
54
+ data_release_manifest: DataReleaseManifest,
55
+ *,
56
+ certification: DataCertification | None = None,
57
+ bundle_manifest_path: str | None = None,
58
+ data_release_manifest_path: str | None = None,
59
+ ) -> dict:
60
+ certified_artifact = country_manifest.certified_data_artifact
61
+ if certified_artifact is None:
62
+ raise ValueError(
63
+ "Country release manifest does not define a certified artifact."
64
+ )
65
+
66
+ dataset_artifact = data_release_manifest.artifacts.get(certified_artifact.dataset)
67
+ if dataset_artifact is None:
68
+ raise ValueError(
69
+ "Data release manifest does not include the certified dataset "
70
+ f"'{certified_artifact.dataset}'."
71
+ )
72
+ if dataset_artifact.sha256 is None:
73
+ raise ValueError(
74
+ "Data release manifest does not include a SHA256 for the certified dataset "
75
+ f"'{certified_artifact.dataset}'."
76
+ )
77
+
78
+ effective_certification = certification or country_manifest.certification
79
+ bundle_manifest_location = (
80
+ bundle_manifest_path
81
+ or f"data/release_manifests/{country_manifest.country_id}.json"
82
+ )
83
+ data_manifest_location = data_release_manifest_path or (
84
+ "https://huggingface.co/"
85
+ f"{country_manifest.data_package.repo_id}/resolve/"
86
+ f"{country_manifest.data_package.version}/"
87
+ f"{country_manifest.data_package.release_manifest_path}"
88
+ )
89
+
90
+ bundle_manifest_payload = country_manifest.model_dump(mode="json")
91
+ data_release_payload = data_release_manifest.model_dump(mode="json")
92
+ bundle_manifest_hash = hashlib.sha256(
93
+ _canonical_json_bytes(bundle_manifest_payload)
94
+ ).hexdigest()
95
+ data_release_manifest_hash = hashlib.sha256(
96
+ _canonical_json_bytes(data_release_payload)
97
+ ).hexdigest()
98
+
99
+ artifact_specs = [
100
+ {
101
+ "hash": bundle_manifest_hash,
102
+ "location": bundle_manifest_location,
103
+ "mime_type": "application/json",
104
+ },
105
+ {
106
+ "hash": data_release_manifest_hash,
107
+ "location": data_manifest_location,
108
+ "mime_type": "application/json",
109
+ },
110
+ {
111
+ "hash": dataset_artifact.sha256,
112
+ "location": certified_artifact.uri,
113
+ "mime_type": _artifact_mime_type(certified_artifact.uri),
114
+ },
115
+ ]
116
+
117
+ composition_artifacts = []
118
+ arrangement_locations = []
119
+ artifact_hashes = []
120
+
121
+ for index, artifact in enumerate(artifact_specs):
122
+ artifact_id = f"composition/1/artifact/{index}"
123
+ artifact_hashes.append(artifact["hash"])
124
+ artifact_entry = {
125
+ "@id": artifact_id,
126
+ "@type": "trov:ResearchArtifact",
127
+ "trov:hash": _hash_object(artifact["hash"]),
128
+ }
129
+ if artifact["mime_type"] is not None:
130
+ artifact_entry["trov:mimeType"] = artifact["mime_type"]
131
+ composition_artifacts.append(artifact_entry)
132
+ arrangement_locations.append(
133
+ {
134
+ "@id": f"arrangement/0/location/{index}",
135
+ "@type": "trov:ArtifactLocation",
136
+ "trov:artifact": {"@id": artifact_id},
137
+ "trov:path": artifact["location"],
138
+ }
139
+ )
140
+
141
+ certification_description = ""
142
+ if effective_certification is not None:
143
+ certification_description = (
144
+ f" Certified for runtime model version "
145
+ f"{effective_certification.certified_for_model_version} via "
146
+ f"{effective_certification.compatibility_basis}."
147
+ )
148
+ if effective_certification.built_with_model_version is not None:
149
+ certification_description += (
150
+ f" Built with {country_manifest.model_package.name} "
151
+ f"{effective_certification.built_with_model_version}."
152
+ )
153
+ if effective_certification.data_build_fingerprint is not None:
154
+ certification_description += (
155
+ f" Data-build fingerprint: "
156
+ f"{effective_certification.data_build_fingerprint}."
157
+ )
158
+
159
+ created_at = country_manifest.published_at or (
160
+ data_release_manifest.build.built_at
161
+ if data_release_manifest.build is not None
162
+ else None
163
+ )
164
+ build_id = (
165
+ effective_certification.data_build_id
166
+ if effective_certification is not None
167
+ else (
168
+ certified_artifact.build_id
169
+ or f"{country_manifest.data_package.name}-{country_manifest.data_package.version}"
170
+ )
171
+ )
172
+
173
+ return {
174
+ "@context": TRACE_CONTEXT,
175
+ "@graph": [
176
+ {
177
+ "@id": "tro",
178
+ "@type": ["trov:TransparentResearchObject", "schema:CreativeWork"],
179
+ "trov:vocabularyVersion": TRACE_TROV_VERSION,
180
+ "schema:creator": country_manifest.policyengine_version,
181
+ "schema:name": (
182
+ f"policyengine {country_manifest.country_id} certified bundle TRO"
183
+ ),
184
+ "schema:description": (
185
+ f"TRACE TRO for certified runtime bundle "
186
+ f"{country_manifest.bundle_id or country_manifest.country_id} "
187
+ f"covering the bundled country release manifest, the country data "
188
+ f"release manifest, and the certified dataset artifact."
189
+ f"{certification_description}"
190
+ ),
191
+ "schema:dateCreated": created_at,
192
+ "trov:wasAssembledBy": {
193
+ "@id": "trs",
194
+ "@type": ["trov:TrustedResearchSystem", "schema:Organization"],
195
+ "schema:name": "PolicyEngine certified release bundle pipeline",
196
+ "schema:description": (
197
+ "PolicyEngine certification workflow for runtime bundles that "
198
+ "pin a country model version, a country data release, and a "
199
+ "specific dataset artifact."
200
+ ),
201
+ },
202
+ "trov:createdWith": {
203
+ "@type": "schema:SoftwareApplication",
204
+ "schema:name": "policyengine",
205
+ "schema:softwareVersion": country_manifest.policyengine_version,
206
+ },
207
+ "trov:hasComposition": {
208
+ "@id": "composition/1",
209
+ "@type": "trov:ArtifactComposition",
210
+ "trov:hasFingerprint": {
211
+ "@id": "fingerprint",
212
+ "@type": "trov:CompositionFingerprint",
213
+ "trov:hash": _hash_object(
214
+ compute_trace_composition_fingerprint(artifact_hashes)
215
+ ),
216
+ },
217
+ "trov:hasArtifact": composition_artifacts,
218
+ },
219
+ "trov:hasArrangement": [
220
+ {
221
+ "@id": "arrangement/0",
222
+ "@type": "trov:ArtifactArrangement",
223
+ "rdfs:comment": (
224
+ f"Certified arrangement for bundle "
225
+ f"{country_manifest.bundle_id or country_manifest.country_id}."
226
+ ),
227
+ "trov:hasArtifactLocation": arrangement_locations,
228
+ }
229
+ ],
230
+ "trov:hasPerformance": [
231
+ {
232
+ "@id": "trp/0",
233
+ "@type": "trov:TrustedResearchPerformance",
234
+ "rdfs:comment": (
235
+ f"Certification of build {build_id} for "
236
+ f"{country_manifest.model_package.name} "
237
+ f"{country_manifest.model_package.version}."
238
+ ),
239
+ "trov:wasConductedBy": {"@id": "trs"},
240
+ "trov:startedAtTime": (
241
+ data_release_manifest.build.built_at
242
+ if data_release_manifest.build is not None
243
+ else created_at
244
+ ),
245
+ "trov:endedAtTime": created_at,
246
+ "trov:contributedToArrangement": {
247
+ "@id": "trp/0/binding/0",
248
+ "@type": "trov:ArrangementBinding",
249
+ "trov:arrangement": {"@id": "arrangement/0"},
250
+ },
251
+ }
252
+ ],
253
+ }
254
+ ],
255
+ }
256
+
257
+
258
+ def serialize_trace_tro(tro: Mapping) -> bytes:
259
+ return (json.dumps(tro, indent=2, sort_keys=True) + "\n").encode("utf-8")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.4.3
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
@@ -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
 
@@ -65,6 +65,7 @@ src/policyengine/core/scoping_strategy.py
65
65
  src/policyengine/core/simulation.py
66
66
  src/policyengine/core/tax_benefit_model.py
67
67
  src/policyengine/core/tax_benefit_model_version.py
68
+ src/policyengine/core/trace_tro.py
68
69
  src/policyengine/core/variable.py
69
70
  src/policyengine/countries/__init__.py
70
71
  src/policyengine/countries/uk/__init__.py