policyengine 6.2.3__tar.gz → 6.2.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 (319) hide show
  1. {policyengine-6.2.3 → policyengine-6.2.4}/CHANGELOG.md +7 -0
  2. {policyengine-6.2.3/src/policyengine.egg-info → policyengine-6.2.4}/PKG-INFO +1 -1
  3. {policyengine-6.2.3 → policyengine-6.2.4}/docs/microsim.md +39 -0
  4. {policyengine-6.2.3 → policyengine-6.2.4}/pyproject.toml +1 -1
  5. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/data/bundle/manifest.json +10 -10
  6. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/data/bundle/uk.trace.tro.jsonld +5 -5
  7. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/data/bundle/us.trace.tro.jsonld +5 -5
  8. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/model_version.py +15 -0
  9. policyengine-6.2.4/src/policyengine/tax_benefit_models/uk/datasets.py +404 -0
  10. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/uk/model.py +138 -8
  11. {policyengine-6.2.3 → policyengine-6.2.4/src/policyengine.egg-info}/PKG-INFO +1 -1
  12. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine.egg-info/SOURCES.txt +1 -0
  13. policyengine-6.2.4/tests/test_uk_year_file_data_year.py +624 -0
  14. {policyengine-6.2.3 → policyengine-6.2.4}/uv.lock +1 -1
  15. policyengine-6.2.3/src/policyengine/tax_benefit_models/uk/datasets.py +0 -270
  16. {policyengine-6.2.3 → policyengine-6.2.4}/.claude/policyengine-guide.md +0 -0
  17. {policyengine-6.2.3 → policyengine-6.2.4}/.claude/quick-reference.md +0 -0
  18. {policyengine-6.2.3 → policyengine-6.2.4}/.github/CONTRIBUTING.md +0 -0
  19. {policyengine-6.2.3 → policyengine-6.2.4}/.github/bump_version.py +0 -0
  20. {policyengine-6.2.3 → policyengine-6.2.4}/.github/capture-version.sh +0 -0
  21. {policyengine-6.2.3 → policyengine-6.2.4}/.github/changelog_template.md +0 -0
  22. {policyengine-6.2.3 → policyengine-6.2.4}/.github/check-changelog.sh +0 -0
  23. {policyengine-6.2.3 → policyengine-6.2.4}/.github/copilot-instructions.md +0 -0
  24. {policyengine-6.2.3 → policyengine-6.2.4}/.github/dispatch-policyengine-release.sh +0 -0
  25. {policyengine-6.2.3 → policyengine-6.2.4}/.github/fetch_version.py +0 -0
  26. {policyengine-6.2.3 → policyengine-6.2.4}/.github/get-changelog-diff.sh +0 -0
  27. {policyengine-6.2.3 → policyengine-6.2.4}/.github/has-functional-changes.sh +0 -0
  28. {policyengine-6.2.3 → policyengine-6.2.4}/.github/is-version-number-acceptable.sh +0 -0
  29. {policyengine-6.2.3 → policyengine-6.2.4}/.github/publish-git-tag.sh +0 -0
  30. {policyengine-6.2.3 → policyengine-6.2.4}/.github/wait-for-pypi.sh +0 -0
  31. {policyengine-6.2.3 → policyengine-6.2.4}/.github/workflows/draft-pdf.yml +0 -0
  32. {policyengine-6.2.3 → policyengine-6.2.4}/.github/workflows/pr_code_changes.yaml +0 -0
  33. {policyengine-6.2.3 → policyengine-6.2.4}/.github/workflows/pr_docs_changes.yaml +0 -0
  34. {policyengine-6.2.3 → policyengine-6.2.4}/.github/workflows/push.yaml +0 -0
  35. {policyengine-6.2.3 → policyengine-6.2.4}/.gitignore +0 -0
  36. {policyengine-6.2.3 → policyengine-6.2.4}/.python-version +0 -0
  37. {policyengine-6.2.3 → policyengine-6.2.4}/AGENTS.md +0 -0
  38. {policyengine-6.2.3 → policyengine-6.2.4}/CITATION.cff +0 -0
  39. {policyengine-6.2.3 → policyengine-6.2.4}/CLAUDE.md +0 -0
  40. {policyengine-6.2.3 → policyengine-6.2.4}/CODE_OF_CONDUCT.md +0 -0
  41. {policyengine-6.2.3 → policyengine-6.2.4}/CONTRIBUTING.md +0 -0
  42. {policyengine-6.2.3 → policyengine-6.2.4}/LICENSE +0 -0
  43. {policyengine-6.2.3 → policyengine-6.2.4}/Makefile +0 -0
  44. {policyengine-6.2.3 → policyengine-6.2.4}/README.md +0 -0
  45. {policyengine-6.2.3 → policyengine-6.2.4}/architecture.png +0 -0
  46. {policyengine-6.2.3 → policyengine-6.2.4}/architecture.svg +0 -0
  47. {policyengine-6.2.3 → policyengine-6.2.4}/changelog.d/.gitkeep +0 -0
  48. {policyengine-6.2.3 → policyengine-6.2.4}/docs/.gitignore +0 -0
  49. {policyengine-6.2.3 → policyengine-6.2.4}/docs/_generator/README.md +0 -0
  50. {policyengine-6.2.3 → policyengine-6.2.4}/docs/_generator/build_reference.py +0 -0
  51. {policyengine-6.2.3 → policyengine-6.2.4}/docs/_quarto.yml +0 -0
  52. {policyengine-6.2.3 → policyengine-6.2.4}/docs/bundles.md +0 -0
  53. {policyengine-6.2.3 → policyengine-6.2.4}/docs/countries.md +0 -0
  54. {policyengine-6.2.3 → policyengine-6.2.4}/docs/data-publishing-design.md +0 -0
  55. {policyengine-6.2.3 → policyengine-6.2.4}/docs/decile-impact-spec.md +0 -0
  56. {policyengine-6.2.3 → policyengine-6.2.4}/docs/dev.md +0 -0
  57. {policyengine-6.2.3 → policyengine-6.2.4}/docs/engineering/runbooks/build-m-us-populace-certification.md +0 -0
  58. {policyengine-6.2.3 → policyengine-6.2.4}/docs/engineering/runbooks/canonical-spm-development.md +0 -0
  59. {policyengine-6.2.3 → policyengine-6.2.4}/docs/engineering/skills/README.md +0 -0
  60. {policyengine-6.2.3 → policyengine-6.2.4}/docs/engineering/skills/data-certification.md +0 -0
  61. {policyengine-6.2.3 → policyengine-6.2.4}/docs/engineering/skills/github-prs.md +0 -0
  62. {policyengine-6.2.3 → policyengine-6.2.4}/docs/engineering/skills/repository-guidance.md +0 -0
  63. {policyengine-6.2.3 → policyengine-6.2.4}/docs/examples.md +0 -0
  64. {policyengine-6.2.3 → policyengine-6.2.4}/docs/getting-started.md +0 -0
  65. {policyengine-6.2.3 → policyengine-6.2.4}/docs/households.md +0 -0
  66. {policyengine-6.2.3 → policyengine-6.2.4}/docs/impact-analysis.md +0 -0
  67. {policyengine-6.2.3 → policyengine-6.2.4}/docs/index.md +0 -0
  68. {policyengine-6.2.3 → policyengine-6.2.4}/docs/methodology/index.md +0 -0
  69. {policyengine-6.2.3 → policyengine-6.2.4}/docs/methodology/model-architecture.md +0 -0
  70. {policyengine-6.2.3 → policyengine-6.2.4}/docs/methodology/us-health-costs.md +0 -0
  71. {policyengine-6.2.3 → policyengine-6.2.4}/docs/outputs.md +0 -0
  72. {policyengine-6.2.3 → policyengine-6.2.4}/docs/programs/index.md +0 -0
  73. {policyengine-6.2.3 → policyengine-6.2.4}/docs/programs/us-chip.md +0 -0
  74. {policyengine-6.2.3 → policyengine-6.2.4}/docs/reference/index.md +0 -0
  75. {policyengine-6.2.3 → policyengine-6.2.4}/docs/reforms.md +0 -0
  76. {policyengine-6.2.3 → policyengine-6.2.4}/docs/regions.md +0 -0
  77. {policyengine-6.2.3 → policyengine-6.2.4}/docs/release-bundles.md +0 -0
  78. {policyengine-6.2.3 → policyengine-6.2.4}/docs/run-records.md +0 -0
  79. {policyengine-6.2.3 → policyengine-6.2.4}/docs/trace-case-study.md +0 -0
  80. {policyengine-6.2.3 → policyengine-6.2.4}/docs/visualisation.md +0 -0
  81. {policyengine-6.2.3 → policyengine-6.2.4}/examples/belgium_axiom_pilot.py +0 -0
  82. {policyengine-6.2.3 → policyengine-6.2.4}/examples/employment_income_variation_uk.py +0 -0
  83. {policyengine-6.2.3 → policyengine-6.2.4}/examples/employment_income_variation_us.py +0 -0
  84. {policyengine-6.2.3 → policyengine-6.2.4}/examples/household_impact_example.py +0 -0
  85. {policyengine-6.2.3 → policyengine-6.2.4}/examples/income_bands_uk.py +0 -0
  86. {policyengine-6.2.3 → policyengine-6.2.4}/examples/income_distribution_us.py +0 -0
  87. {policyengine-6.2.3 → policyengine-6.2.4}/examples/paper_repro_uk.py +0 -0
  88. {policyengine-6.2.3 → policyengine-6.2.4}/examples/policy_change_uk.py +0 -0
  89. {policyengine-6.2.3 → policyengine-6.2.4}/examples/speedtest_us_simulation.py +0 -0
  90. {policyengine-6.2.3 → policyengine-6.2.4}/examples/us_budgetary_impact.py +0 -0
  91. {policyengine-6.2.3 → policyengine-6.2.4}/paper-preview.html +0 -0
  92. {policyengine-6.2.3 → policyengine-6.2.4}/paper.bib +0 -0
  93. {policyengine-6.2.3 → policyengine-6.2.4}/paper.md +0 -0
  94. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/bundle.py +0 -0
  95. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/certify_data_release.py +0 -0
  96. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/check_data_staleness.py +0 -0
  97. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/check_native_spm_loading.py +0 -0
  98. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/check_release_credentials.py +0 -0
  99. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/export_bundle_release_assets.py +0 -0
  100. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/generate_bundle_artifacts.py +0 -0
  101. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/generate_trace_tros.py +0 -0
  102. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/prepare_package_bundle_update.py +0 -0
  103. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/release_lock.py +0 -0
  104. {policyengine-6.2.3 → policyengine-6.2.4}/scripts/spm_bundle.py +0 -0
  105. {policyengine-6.2.3 → policyengine-6.2.4}/setup.cfg +0 -0
  106. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/__init__.py +0 -0
  107. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/bundle.py +0 -0
  108. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/cli.py +0 -0
  109. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/__init__.py +0 -0
  110. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/cache.py +0 -0
  111. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/dataset.py +0 -0
  112. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/dynamic.py +0 -0
  113. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/output.py +0 -0
  114. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/parameter.py +0 -0
  115. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/parameter_node.py +0 -0
  116. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/parameter_value.py +0 -0
  117. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/policy.py +0 -0
  118. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/region.py +0 -0
  119. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/run_record.py +0 -0
  120. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/scoping_strategy.py +0 -0
  121. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/simulation.py +0 -0
  122. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/spm.py +0 -0
  123. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/tax_benefit_model.py +0 -0
  124. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/tax_benefit_model_version.py +0 -0
  125. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/core/variable.py +0 -0
  126. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/__init__.py +0 -0
  127. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/uk/__init__.py +0 -0
  128. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/uk/regions.py +0 -0
  129. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/us/__init__.py +0 -0
  130. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/us/data/__init__.py +0 -0
  131. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/us/data/districts.py +0 -0
  132. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/us/data/places.py +0 -0
  133. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/us/data/states.py +0 -0
  134. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/countries/us/regions.py +0 -0
  135. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/data/__init__.py +0 -0
  136. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/data/schemas/trace_tro.schema.json +0 -0
  137. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/data/uk_geography_assets.py +0 -0
  138. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/derivations/__init__.py +0 -0
  139. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/derivations/narrate.py +0 -0
  140. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/derivations/trace.py +0 -0
  141. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/graph/__init__.py +0 -0
  142. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/graph/extractor.py +0 -0
  143. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/graph/graph.py +0 -0
  144. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/__init__.py +0 -0
  145. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/aggregate.py +0 -0
  146. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/change_aggregate.py +0 -0
  147. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/cliff_impact.py +0 -0
  148. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/congressional_district_impact.py +0 -0
  149. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/constituency_impact.py +0 -0
  150. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/decile_analysis.py +0 -0
  151. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/decile_grouping.py +0 -0
  152. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/decile_impact.py +0 -0
  153. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/extra_variables.py +0 -0
  154. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/inequality.py +0 -0
  155. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/intra_decile_impact.py +0 -0
  156. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/labor_supply_response.py +0 -0
  157. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/local_authority_impact.py +0 -0
  158. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/poverty.py +0 -0
  159. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/program_statistics.py +0 -0
  160. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/uk_geography_assets.py +0 -0
  161. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/outputs/uk_geography_impact.py +0 -0
  162. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/__init__.py +0 -0
  163. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/bundle_validation.py +0 -0
  164. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/certification.py +0 -0
  165. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/dataset_materialization.py +0 -0
  166. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/manifest.py +0 -0
  167. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/pyproject_pins.py +0 -0
  168. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/spm_configuration.py +0 -0
  169. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/trace.py +0 -0
  170. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/verify.py +0 -0
  171. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/provenance/zenodo.py +0 -0
  172. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/results/__init__.py +0 -0
  173. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/results/schema.py +0 -0
  174. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/results/trace_tro.py +0 -0
  175. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/results/tracking.py +0 -0
  176. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/be/__init__.py +0 -0
  177. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/be/datasets.py +0 -0
  178. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/be/model.py +0 -0
  179. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/__init__.py +0 -0
  180. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/axes.py +0 -0
  181. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/extra_variables.py +0 -0
  182. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/household.py +0 -0
  183. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/reform.py +0 -0
  184. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/common/result.py +0 -0
  185. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/uk/__init__.py +0 -0
  186. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/uk/analysis.py +0 -0
  187. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/uk/household.py +0 -0
  188. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/__init__.py +0 -0
  189. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/analysis.py +0 -0
  190. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/datasets.py +0 -0
  191. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/household.py +0 -0
  192. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/legacy_inputs.py +0 -0
  193. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/model.py +0 -0
  194. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/tax_benefit_models/us/spm.py +0 -0
  195. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/__init__.py +0 -0
  196. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/dates.py +0 -0
  197. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/design.py +0 -0
  198. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/entity_utils.py +0 -0
  199. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/errors.py +0 -0
  200. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/hashing.py +0 -0
  201. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/household_validation.py +0 -0
  202. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/parameter_labels.py +0 -0
  203. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/parametric_reforms.py +0 -0
  204. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine/utils/plotting.py +0 -0
  205. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine.egg-info/dependency_links.txt +0 -0
  206. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine.egg-info/entry_points.txt +0 -0
  207. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine.egg-info/requires.txt +0 -0
  208. {policyengine-6.2.3 → policyengine-6.2.4}/src/policyengine.egg-info/top_level.txt +0 -0
  209. {policyengine-6.2.3 → policyengine-6.2.4}/tests/__init__.py +0 -0
  210. {policyengine-6.2.3 → policyengine-6.2.4}/tests/conftest.py +0 -0
  211. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/__init__.py +0 -0
  212. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/filtering_fixtures.py +0 -0
  213. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/uk_couple_two_kids.json +0 -0
  214. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/uk_model_surface.json +0 -0
  215. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/uk_single_adult_employment_income.json +0 -0
  216. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/uk_single_adult_no_income.json +0 -0
  217. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/uk_single_parent_one_child.json +0 -0
  218. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/us_married_two_kids_high_income.json +0 -0
  219. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/us_model_surface.json +0 -0
  220. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/us_single_adult_employment_income.json +0 -0
  221. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/us_single_adult_no_income.json +0 -0
  222. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/household_calculator_snapshots/us_single_parent_one_child.json +0 -0
  223. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/parameter_labels_fixtures.py +0 -0
  224. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/parametric_reforms_fixtures.py +0 -0
  225. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/poverty_by_demographics_fixtures.py +0 -0
  226. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/region_fixtures.py +0 -0
  227. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/release_tros/README.md +0 -0
  228. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/release_tros/country-models.lock.toml +0 -0
  229. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/release_tros/manifest.json +0 -0
  230. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/release_tros/us-release_manifest.json +0 -0
  231. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/release_tros/us.trace.tro.jsonld +0 -0
  232. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/spm_development.py +0 -0
  233. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/us_reform_fixtures.py +0 -0
  234. {policyengine-6.2.3 → policyengine-6.2.4}/tests/fixtures/variable_label_fixtures.py +0 -0
  235. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_aggregate.py +0 -0
  236. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_be_axiom_pilot.py +0 -0
  237. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_budgetary_impact.py +0 -0
  238. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_bump_version.py +0 -0
  239. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_bundle.py +0 -0
  240. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_bundle_metadata.py +0 -0
  241. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_cache.py +0 -0
  242. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_certify_data_release.py +0 -0
  243. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_change_aggregate.py +0 -0
  244. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_cliff_impact.py +0 -0
  245. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_cliff_impact_analysis.py +0 -0
  246. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_congressional_district_impact.py +0 -0
  247. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_constituency_impact.py +0 -0
  248. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_dataset_materialization.py +0 -0
  249. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_dataset_persistence.py +0 -0
  250. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_dataset_runtime.py +0 -0
  251. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_dataset_source.py +0 -0
  252. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_decile_grouping.py +0 -0
  253. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_derivations.py +0 -0
  254. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_dict_reforms_on_simulation.py +0 -0
  255. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_entity_mapping.py +0 -0
  256. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_entity_utils.py +0 -0
  257. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_entity_utils_characterization.py +0 -0
  258. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_errors.py +0 -0
  259. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_extra_variables.py +0 -0
  260. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_filtering.py +0 -0
  261. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_generate_trace_tros.py +0 -0
  262. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_graph/__init__.py +0 -0
  263. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_graph/conftest.py +0 -0
  264. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_graph/test_extractor.py +0 -0
  265. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_household_calculator_snapshot.py +0 -0
  266. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_household_impact.py +0 -0
  267. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_inequality.py +0 -0
  268. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_intra_decile_impact.py +0 -0
  269. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_labor_supply_response.py +0 -0
  270. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_local_authority_impact.py +0 -0
  271. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_manifest_version_mismatch.py +0 -0
  272. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_models.py +0 -0
  273. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_pandas3_compatibility.py +0 -0
  274. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_parameter_labels.py +0 -0
  275. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_parameter_nodes.py +0 -0
  276. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_parameter_values.py +0 -0
  277. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_parametric_reforms.py +0 -0
  278. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_poverty.py +0 -0
  279. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_poverty_by_demographics.py +0 -0
  280. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_poverty_run.py +0 -0
  281. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_preservation_mirror.py +0 -0
  282. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_program_statistics.py +0 -0
  283. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_pyproject_pins.py +0 -0
  284. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_region.py +0 -0
  285. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_region_group_strategy.py +0 -0
  286. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_release_credentials.py +0 -0
  287. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_release_lock.py +0 -0
  288. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_release_manifests.py +0 -0
  289. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_release_tro_generation.py +0 -0
  290. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_results.py +0 -0
  291. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_run_record.py +0 -0
  292. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_scoping_strategy.py +0 -0
  293. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_small_follow_ups.py +0 -0
  294. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_bundle_bootstrap.py +0 -0
  295. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_bundle_tooling.py +0 -0
  296. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_decile.py +0 -0
  297. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_development_authentication.py +0 -0
  298. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_household.py +0 -0
  299. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_model.py +0 -0
  300. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_selection.py +0 -0
  301. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_spm_selection_schema.py +0 -0
  302. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_trace_tro.py +0 -0
  303. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_trace_tro_verify.py +0 -0
  304. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_uk_analysis.py +0 -0
  305. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_uk_geography_assets.py +0 -0
  306. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_uk_program_statistics.py +0 -0
  307. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_uk_regions.py +0 -0
  308. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_legacy_inputs.py +0 -0
  309. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_legacy_inputs_integration.py +0 -0
  310. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_long_term_datasets.py +0 -0
  311. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_microsim_structural_reforms.py +0 -0
  312. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_native_alignment.py +0 -0
  313. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_native_hdf_weights.py +0 -0
  314. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_program_statistics.py +0 -0
  315. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_reform_application.py +0 -0
  316. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_us_regions.py +0 -0
  317. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_variable_labels.py +0 -0
  318. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_variable_metadata.py +0 -0
  319. {policyengine-6.2.3 → policyengine-6.2.4}/tests/test_zenodo_mirror.py +0 -0
@@ -1,3 +1,10 @@
1
+ ## [6.2.4] - 2026-10-09
2
+
3
+ ### Fixed
4
+
5
+ - UK population runs now anchor the State Pension on the survey year their data was projected from, as policyengine-uk does, so a run of a UK year file matches policyengine-uk on the certified dataset, with or without a reform. Before, it took the projected year as the survey year: the State Pension was uprated by CPI rather than the triple lock, and State Pension rate reforms had no effect on it under policyengine-uk 2.102.3 and were understated under 2.123. Projected year files now also store the survey year's tables, which doubles their size. `ensure_datasets` regenerates year files from earlier releases and `load_datasets` refuses them; `Simulation.load()` refuses UK outputs saved by earlier releases, and `Simulation.ensure()` runs them again.
6
+
7
+
1
8
  ## [6.2.3] - 2026-10-09
2
9
 
3
10
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 6.2.3
3
+ Version: 6.2.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
@@ -231,6 +231,45 @@ not need repository-specific download logic. Authentication or authorization
231
231
  failures are reported directly and do not cause a retry against another
232
232
  repository type.
233
233
 
234
+ ### UK year files and the data year
235
+
236
+ `ensure_datasets` and `create_datasets` cut one file per requested year from
237
+ the projection policyengine-uk makes of the certified dataset. policyengine-uk
238
+ takes the first year of a dataset as observed data. Its State Pension formulas
239
+ split each person's reported State Pension against that year's legislated
240
+ rates, and scale the share to the simulated year's rates, which follow the
241
+ triple lock. The projection uprates the reported amount by CPI, so handing a
242
+ projected year's tables to policyengine-uk as observed data would make the
243
+ State Pension follow CPI instead.
244
+
245
+ Each projected year file therefore keeps its data year, `dataset.data_year`
246
+ (2024 for Enhanced FRS 2024–25), and that year's tables,
247
+ `dataset.data_year_data`. `Simulation.run()` projects the data year's tables
248
+ forward as policyengine-uk does, and uses the file's own tables for the
249
+ simulated year. A run of a year file then gives the same result, record by
250
+ record, as `policyengine_uk.Microsimulation` on the certified file, with or
251
+ without a reform. Keeping the data year's tables doubles a projected file: the
252
+ Enhanced FRS 2026 file is 226 MB, against 113 MB for its own tables. A dataset
253
+ built in memory without a data year is observed data for its own year, which
254
+ is how policyengine-uk treats a single-year dataset.
255
+
256
+ Row filtering applies to both sets of tables, matched by entity ID. Weight
257
+ replacement changes the simulated year's weights only; the data year and the
258
+ years between keep national weights. A row-filtered run is a simulation of the
259
+ region's households alone, so
260
+ variables that policyengine-uk calculates over every household in the
261
+ simulation are calculated over the region: income deciles, the relative
262
+ poverty median, and `shareholding`, which spreads corporate taxes across
263
+ households (PolicyEngine/policyengine.py#567). Variables of a person or
264
+ benefit unit that do not depend on those match the national run.
265
+
266
+ Year files written by earlier releases have no recorded data year.
267
+ `ensure_datasets` writes them again and `load_datasets` refuses them. A year
268
+ file opened directly, as `PolicyEngineUKDataset(filepath=...)`, is not
269
+ checked. A saved UK simulation output records the data year its run anchored
270
+ on; `Simulation.load()` refuses an output saved without one, and
271
+ `Simulation.ensure()` runs it again.
272
+
234
273
  ## Simulations
235
274
 
236
275
  A `Simulation` needs a dataset, a tax-benefit model version, and optionally a policy (reform):
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "6.2.3"
7
+ version = "6.2.4"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,10 +1,10 @@
1
1
  {
2
- "bundle_version": "6.2.3",
2
+ "bundle_version": "6.2.4",
3
3
  "citation": {
4
4
  "publisher": "PolicyEngine",
5
- "title": "PolicyEngine bundle 6.2.3",
5
+ "title": "PolicyEngine bundle 6.2.4",
6
6
  "type": "software-bundle",
7
- "version": "6.2.3"
7
+ "version": "6.2.4"
8
8
  },
9
9
  "countries": {
10
10
  "uk": {
@@ -17,7 +17,7 @@
17
17
  "data_releases": {
18
18
  "uk": {
19
19
  "build_id": "policyengine-uk-data-1.56.16",
20
- "bundle_id": "uk-6.2.3",
20
+ "bundle_id": "uk-6.2.4",
21
21
  "certification": {
22
22
  "built_with_model_git_sha": "12a1e028afeef08d8b2d74ee03fd9de3a78b2dd3",
23
23
  "built_with_model_version": "2.89.2",
@@ -99,7 +99,7 @@
99
99
  "version": "2.102.3",
100
100
  "wheel_url": "https://files.pythonhosted.org/packages/37/e6/e8d9069d498f1510a1380f24a78e957902d01f00b10248c146d6710d5b37/policyengine_uk-2.102.3-py3-none-any.whl"
101
101
  },
102
- "policyengine_version": "6.2.3",
102
+ "policyengine_version": "6.2.4",
103
103
  "region_datasets": {
104
104
  "national": {
105
105
  "path_template": "enhanced_frs_2024_25.h5"
@@ -112,7 +112,7 @@
112
112
  },
113
113
  "us": {
114
114
  "build_id": "populace-us-2024-spm-20260915",
115
- "bundle_id": "us-6.2.3",
115
+ "bundle_id": "us-6.2.4",
116
116
  "certification": {
117
117
  "built_with_model_version": "2.2.1",
118
118
  "certified_by": "policyengine.py bundle certification",
@@ -217,7 +217,7 @@
217
217
  "version": "2.2.1",
218
218
  "wheel_url": "https://files.pythonhosted.org/packages/5e/09/86ac3968d2a18e28fce3ba1186fc21728e9f299b96a806d6f204f1e0aaed/policyengine_us-2.2.1-py3-none-any.whl"
219
219
  },
220
- "policyengine_version": "6.2.3",
220
+ "policyengine_version": "6.2.4",
221
221
  "region_datasets": {
222
222
  "congressional_district": {
223
223
  "path_template": "populace_us_2024_acs_local.h5"
@@ -295,10 +295,10 @@
295
295
  "packages": {
296
296
  "policyengine": {
297
297
  "import_name": "policyengine",
298
- "install_requirement": "policyengine==6.2.3",
298
+ "install_requirement": "policyengine==6.2.4",
299
299
  "name": "policyengine",
300
300
  "role": "bundle_carrier",
301
- "version": "6.2.3"
301
+ "version": "6.2.4"
302
302
  },
303
303
  "policyengine-core": {
304
304
  "import_name": "policyengine_core",
@@ -334,6 +334,6 @@
334
334
  "wheel_url": "https://files.pythonhosted.org/packages/19/a0/c484f69a0ebf88a9b9fd0ac28176f8df66550f46714f1b0c5ec0b360824e/spm_calculator-1.0.0-py3-none-any.whl"
335
335
  }
336
336
  },
337
- "policyengine_version": "6.2.3",
337
+ "policyengine_version": "6.2.4",
338
338
  "schema_version": 2
339
339
  }
@@ -18,18 +18,18 @@
18
18
  "schema:url": "https://policyengine.org"
19
19
  },
20
20
  "schema:dateCreated": "2026-08-18T14:20:32.100961Z",
21
- "schema:description": "TRACE TRO for certified runtime bundle uk-6.2.3 covering the bundle manifest, the certified dataset artifact, the country model wheel, and the country data release manifest when it is available.",
21
+ "schema:description": "TRACE TRO for certified runtime bundle uk-6.2.4 covering the bundle manifest, the certified dataset artifact, the country model wheel, and the country data release manifest when it is available.",
22
22
  "schema:name": "policyengine uk certified bundle TRO",
23
23
  "trov:createdWith": {
24
24
  "@type": "schema:SoftwareApplication",
25
25
  "schema:name": "policyengine",
26
- "schema:softwareVersion": "6.2.3"
26
+ "schema:softwareVersion": "6.2.4"
27
27
  },
28
28
  "trov:hasArrangement": [
29
29
  {
30
30
  "@id": "arrangement/1",
31
31
  "@type": "trov:ArtifactArrangement",
32
- "rdfs:comment": "Certified arrangement for bundle uk-6.2.3.",
32
+ "rdfs:comment": "Certified arrangement for bundle uk-6.2.4.",
33
33
  "trov:hasArtifactLocation": [
34
34
  {
35
35
  "@id": "arrangement/1/location/bundle_manifest",
@@ -75,7 +75,7 @@
75
75
  "@type": "trov:ResearchArtifact",
76
76
  "schema:name": "policyengine.py bundle manifest for uk",
77
77
  "trov:mimeType": "application/json",
78
- "trov:sha256": "4f4d1c5802ff917e555ce9f5520150fa4e7e1584853782afc38c03818101c9c6"
78
+ "trov:sha256": "4e830a845f54782daed05f1331203c68fa138396025f2da49d34b34ba7ac3c42"
79
79
  },
80
80
  {
81
81
  "@id": "composition/1/artifact/data_release_manifest",
@@ -102,7 +102,7 @@
102
102
  "trov:hasFingerprint": {
103
103
  "@id": "composition/1/fingerprint",
104
104
  "@type": "trov:CompositionFingerprint",
105
- "trov:sha256": "23c38fee2f5fe5e6dd89137ba46b18b0add70f1a906cccbd28025f13c9061f64"
105
+ "trov:sha256": "d238f72a1fe43bb3b1c8c92c27eb3bf717f2c00527da1cab6277f65ccf382606"
106
106
  }
107
107
  },
108
108
  "trov:hasPerformance": {
@@ -17,18 +17,18 @@
17
17
  "schema:name": "PolicyEngine",
18
18
  "schema:url": "https://policyengine.org"
19
19
  },
20
- "schema:description": "TRACE TRO for certified runtime bundle us-6.2.3 covering the bundle manifest, the certified dataset artifact, the country model wheel, and the country data release manifest when it is available.",
20
+ "schema:description": "TRACE TRO for certified runtime bundle us-6.2.4 covering the bundle manifest, the certified dataset artifact, the country model wheel, and the country data release manifest when it is available.",
21
21
  "schema:name": "policyengine us certified bundle TRO",
22
22
  "trov:createdWith": {
23
23
  "@type": "schema:SoftwareApplication",
24
24
  "schema:name": "policyengine",
25
- "schema:softwareVersion": "6.2.3"
25
+ "schema:softwareVersion": "6.2.4"
26
26
  },
27
27
  "trov:hasArrangement": [
28
28
  {
29
29
  "@id": "arrangement/1",
30
30
  "@type": "trov:ArtifactArrangement",
31
- "rdfs:comment": "Certified arrangement for bundle us-6.2.3.",
31
+ "rdfs:comment": "Certified arrangement for bundle us-6.2.4.",
32
32
  "trov:hasArtifactLocation": [
33
33
  {
34
34
  "@id": "arrangement/1/location/bundle_manifest",
@@ -74,7 +74,7 @@
74
74
  "@type": "trov:ResearchArtifact",
75
75
  "schema:name": "policyengine.py bundle manifest for us",
76
76
  "trov:mimeType": "application/json",
77
- "trov:sha256": "4f4d1c5802ff917e555ce9f5520150fa4e7e1584853782afc38c03818101c9c6"
77
+ "trov:sha256": "4e830a845f54782daed05f1331203c68fa138396025f2da49d34b34ba7ac3c42"
78
78
  },
79
79
  {
80
80
  "@id": "composition/1/artifact/data_release_manifest",
@@ -101,7 +101,7 @@
101
101
  "trov:hasFingerprint": {
102
102
  "@id": "composition/1/fingerprint",
103
103
  "@type": "trov:CompositionFingerprint",
104
- "trov:sha256": "f601acd275cdd7a90632c491593474090dac2c4478f372fe65472e23352e1194"
104
+ "trov:sha256": "122720c1301231fb4ff791dd2e80120dd7c64633e34aff5583aaac56c34c10bb"
105
105
  }
106
106
  },
107
107
  "trov:hasPerformance": {
@@ -443,6 +443,21 @@ class MicrosimulationModelVersion(TaxBenefitModelVersion):
443
443
  "stored inputs (it has no record of them); run it again"
444
444
  )
445
445
 
446
+ if self.country_code == "uk":
447
+ from policyengine.tax_benefit_models.uk.datasets import (
448
+ _year_file_records_data_year,
449
+ )
450
+
451
+ # UK outputs saved before runs anchored on the observed data year
452
+ # (PolicyEngine/policyengine.py#556) took a projected year as
453
+ # observed and uprated the State Pension by CPI, so they are not
454
+ # reused. ``Simulation.ensure()`` runs such a simulation again.
455
+ if not _year_file_records_data_year(Path(filepath)):
456
+ raise ValueError(
457
+ "Saved UK simulation predates anchoring on the observed "
458
+ "data year (it records none); run it again"
459
+ )
460
+
446
461
  simulation.output_dataset = self._dataset_class(
447
462
  id=simulation.id,
448
463
  name=simulation.dataset.name,
@@ -0,0 +1,404 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ import pandas as pd
5
+ from microdf import MicroDataFrame
6
+ from pydantic import ConfigDict
7
+
8
+ from policyengine.core import Dataset, YearData
9
+ from policyengine.provenance.dataset_materialization import (
10
+ materialize_dataset,
11
+ )
12
+ from policyengine.provenance.manifest import (
13
+ dataset_logical_name,
14
+ get_release_manifest,
15
+ resolve_dataset_reference,
16
+ )
17
+
18
+ #: HDF5 key holding the data year of a UK year file: the year of the observed
19
+ #: data the file's tables were projected from (see
20
+ #: ``PolicyEngineUKDataset.data_year``). ``create_datasets`` writes it into
21
+ #: every year file. Year files written before policyengine.py kept the data
22
+ #: year have none: policyengine-uk would take their projected tables as
23
+ #: observed data, so ``ensure_datasets`` regenerates them and
24
+ #: ``load_datasets`` refuses them.
25
+ DATA_YEAR_KEY = "data_year"
26
+
27
+ #: Prefix of the HDF5 keys holding the data year's entity tables in a year
28
+ #: file whose year comes after its data year.
29
+ DATA_YEAR_TABLE_PREFIX = "data_year_"
30
+
31
+
32
+ class UKYearData(YearData):
33
+ """Entity-level data for a single year."""
34
+
35
+ model_config = ConfigDict(arbitrary_types_allowed=True)
36
+
37
+ person: MicroDataFrame
38
+ benunit: MicroDataFrame
39
+ household: MicroDataFrame
40
+
41
+ @property
42
+ def entity_data(self) -> dict[str, MicroDataFrame]:
43
+ """Return a dictionary of entity names to their data."""
44
+ return {
45
+ "person": self.person,
46
+ "benunit": self.benunit,
47
+ "household": self.household,
48
+ }
49
+
50
+
51
+ class PolicyEngineUKDataset(Dataset):
52
+ """UK dataset with multi-year entity-level data."""
53
+
54
+ data: Optional[UKYearData] = None
55
+
56
+ data_year: Optional[int] = None
57
+ """Year of the observed data that ``data`` was projected from.
58
+
59
+ policyengine-uk takes the first year of a dataset as observed data. It
60
+ splits each person's reported State Pension against that year's
61
+ legislated rates and scales the share to the simulated year's rates,
62
+ which follow the triple lock. ``create_datasets`` records the source
63
+ dataset's first year here. ``None`` means ``data`` is itself observed
64
+ data for ``year``, as it is for a dataset built in memory.
65
+ """
66
+
67
+ data_year_data: Optional[UKYearData] = None
68
+ """Entity tables of ``data_year``, when it comes before ``year``.
69
+
70
+ ``run()`` projects them forward as policyengine-uk does in a direct run,
71
+ and uses ``data`` for ``year`` itself. Without them, policyengine-uk
72
+ would take the projected ``data`` as observed data for ``year`` and the
73
+ State Pension would follow the CPI uprating of its reported amount
74
+ instead of the triple lock.
75
+ """
76
+
77
+ def model_post_init(self, __context):
78
+ """Called after Pydantic initialization.
79
+
80
+ Constructing with only a ``filepath`` hydrates ``data`` from disk.
81
+ Construction never *writes*: persistence is always explicit via
82
+ ``save()``. Auto-saving on construction (the previous behaviour)
83
+ made an in-memory, region-scoped copy that reused its source's
84
+ ``filepath`` silently truncate the shared dataset file it was
85
+ derived from. See run() in model.py, which now builds scoped
86
+ copies with ``filepath=None``.
87
+ """
88
+ if self.data is None and self.filepath:
89
+ self.load()
90
+ self._check_data_year()
91
+
92
+ def _check_data_year(self) -> None:
93
+ """Refuse a data year that cannot be the source of ``year``."""
94
+ if self.data_year is None:
95
+ if self.data_year_data is not None:
96
+ raise ValueError(
97
+ "PolicyEngineUKDataset has data-year tables but no "
98
+ "data_year. Set data_year to the year they hold."
99
+ )
100
+ return
101
+ if self.data_year > self.year:
102
+ raise ValueError(
103
+ f"PolicyEngineUKDataset data_year {self.data_year} is after "
104
+ f"its year {self.year}; data can only be projected forward."
105
+ )
106
+ if self.data_year == self.year and self.data_year_data is not None:
107
+ raise ValueError(
108
+ f"PolicyEngineUKDataset year {self.year} is its data year, so "
109
+ "`data` holds the observed tables and data_year_data must be "
110
+ "None."
111
+ )
112
+ # An output records the data year its run anchored on, without the
113
+ # tables.
114
+ if (
115
+ self.data_year < self.year
116
+ and self.data is not None
117
+ and self.data_year_data is None
118
+ and not self.is_output_dataset
119
+ ):
120
+ raise ValueError(
121
+ f"PolicyEngineUKDataset for {self.year} was projected from "
122
+ f"{self.data_year} but has no data_year_data, so "
123
+ "policyengine-uk cannot anchor the State Pension on the "
124
+ "observed year. Pass the data year's tables, or set "
125
+ "data_year=None to treat `data` as observed data."
126
+ )
127
+
128
+ def save(self) -> None:
129
+ """Save dataset to HDF5 file.
130
+
131
+ Converts object columns to categorical dtype to avoid slow pickle serialization.
132
+ """
133
+ if not self.filepath:
134
+ raise ValueError(
135
+ "Cannot save a PolicyEngineUKDataset with no filepath. This "
136
+ "is an in-memory dataset (e.g. a region-scoped copy); set "
137
+ "`.filepath` to a destination before calling save()."
138
+ )
139
+ filepath = Path(self.filepath)
140
+ if not filepath.parent.exists():
141
+ filepath.parent.mkdir(parents=True, exist_ok=True)
142
+
143
+ with pd.HDFStore(filepath, mode="w") as store:
144
+ _put_year_data(store, self.data)
145
+ if self.data_year_data is not None:
146
+ _put_year_data(store, self.data_year_data, DATA_YEAR_TABLE_PREFIX)
147
+ # Written last: its presence marks a complete file.
148
+ if self.data_year is not None:
149
+ store.put(DATA_YEAR_KEY, pd.Series([int(self.data_year)]))
150
+
151
+ def load(self) -> None:
152
+ """Load dataset from HDF5 file into this instance."""
153
+ filepath = self.filepath
154
+ with pd.HDFStore(filepath, mode="r") as store:
155
+ keys = set(store.keys())
156
+ self.data = _read_year_data(store)
157
+ self.data_year = (
158
+ int(store[DATA_YEAR_KEY].iloc[0])
159
+ if f"/{DATA_YEAR_KEY}" in keys
160
+ else None
161
+ )
162
+ self.data_year_data = (
163
+ _read_year_data(store, DATA_YEAR_TABLE_PREFIX)
164
+ if f"/{DATA_YEAR_TABLE_PREFIX}person" in keys
165
+ else None
166
+ )
167
+ self._check_data_year()
168
+
169
+ def __repr__(self) -> str:
170
+ if self.data is None:
171
+ return f"<PolicyEngineUKDataset id={self.id} year={self.year} filepath={self.filepath} (not loaded)>"
172
+ else:
173
+ n_people = len(self.data.person)
174
+ n_benunits = len(self.data.benunit)
175
+ n_households = len(self.data.household)
176
+ return f"<PolicyEngineUKDataset id={self.id} year={self.year} data_year={self.data_year} filepath={self.filepath} people={n_people} benunits={n_benunits} households={n_households}>"
177
+
178
+
179
+ def _put_year_data(store: pd.HDFStore, data: UKYearData, prefix: str = "") -> None:
180
+ """Write one year's entity tables to an open HDF5 store.
181
+
182
+ Object columns are stored as categoricals to avoid slow pickle
183
+ serialization; ``format="table"`` supports categorical dtypes.
184
+ """
185
+ for name, frame in data.entity_data.items():
186
+ frame = pd.DataFrame(frame)
187
+ for column in frame.columns:
188
+ if frame[column].dtype == "object":
189
+ frame[column] = frame[column].astype("category")
190
+ store.put(f"{prefix}{name}", frame, format="table")
191
+
192
+
193
+ def _read_year_data(store: pd.HDFStore, prefix: str = "") -> UKYearData:
194
+ """Read one year's entity tables from an open HDF5 store."""
195
+ return UKYearData(
196
+ person=MicroDataFrame(store[f"{prefix}person"], weights="person_weight"),
197
+ benunit=MicroDataFrame(store[f"{prefix}benunit"], weights="benunit_weight"),
198
+ household=MicroDataFrame(
199
+ store[f"{prefix}household"], weights="household_weight"
200
+ ),
201
+ )
202
+
203
+
204
+ def _year_data_with_weights(year_dataset) -> UKYearData:
205
+ """Return a policyengine-uk year's tables with person and benunit weights.
206
+
207
+ policyengine-uk stores household weights only; each person and benefit
208
+ unit takes its household's weight.
209
+ """
210
+ # Convert to pandas DataFrames and add weight columns
211
+ person_df = pd.DataFrame(year_dataset.person)
212
+ benunit_df = pd.DataFrame(year_dataset.benunit)
213
+ household_df = pd.DataFrame(year_dataset.household)
214
+
215
+ # Map household weights to person and benunit levels
216
+ person_df = person_df.merge(
217
+ household_df[["household_id", "household_weight"]],
218
+ left_on="person_household_id",
219
+ right_on="household_id",
220
+ how="left",
221
+ )
222
+ person_df = person_df.rename(columns={"household_weight": "person_weight"})
223
+ person_df = person_df.drop(columns=["household_id"])
224
+
225
+ # Get household_id for each benunit from person table
226
+ benunit_household_map = person_df[
227
+ ["person_benunit_id", "person_household_id"]
228
+ ].drop_duplicates()
229
+ benunit_df = benunit_df.merge(
230
+ benunit_household_map,
231
+ left_on="benunit_id",
232
+ right_on="person_benunit_id",
233
+ how="left",
234
+ )
235
+ benunit_df = benunit_df.merge(
236
+ household_df[["household_id", "household_weight"]],
237
+ left_on="person_household_id",
238
+ right_on="household_id",
239
+ how="left",
240
+ )
241
+ benunit_df = benunit_df.rename(columns={"household_weight": "benunit_weight"})
242
+ benunit_df = benunit_df.drop(
243
+ columns=[
244
+ "person_benunit_id",
245
+ "person_household_id",
246
+ "household_id",
247
+ ],
248
+ errors="ignore",
249
+ )
250
+ return UKYearData(
251
+ person=MicroDataFrame(person_df, weights="person_weight"),
252
+ benunit=MicroDataFrame(benunit_df, weights="benunit_weight"),
253
+ household=MicroDataFrame(household_df, weights="household_weight"),
254
+ )
255
+
256
+
257
+ def _year_file_records_data_year(path: Path) -> bool:
258
+ """Return whether a UK file records its data year (``DATA_YEAR_KEY``).
259
+
260
+ A missing or unreadable file raises rather than reading as unrecorded.
261
+ """
262
+ with pd.HDFStore(path, mode="r") as store:
263
+ return f"/{DATA_YEAR_KEY}" in store.keys()
264
+
265
+
266
+ def create_datasets(
267
+ datasets: Optional[list[str]] = None,
268
+ years: list[int] = [2026, 2027, 2028, 2029, 2030],
269
+ data_folder: str = "./data",
270
+ allow_unmanaged: bool = False,
271
+ ) -> dict[str, PolicyEngineUKDataset]:
272
+ dataset_requests: list[Optional[str]] = [None] if datasets is None else datasets
273
+ result = {}
274
+ for dataset in dataset_requests:
275
+ source = materialize_dataset(
276
+ "uk",
277
+ dataset,
278
+ allow_unmanaged=allow_unmanaged,
279
+ data_dir=Path(data_folder),
280
+ )
281
+ dataset_stem = source.name
282
+ from policyengine_uk import Microsimulation
283
+
284
+ sim = Microsimulation(dataset=source.path)
285
+ if not years:
286
+ continue
287
+ # policyengine-uk takes a dataset's first year as observed data and
288
+ # projects the rest from it (see PolicyEngineUKDataset.data_year).
289
+ # Each year file keeps that year and its tables, so a run of the
290
+ # file anchors on the same observed year as a run of the source.
291
+ data_year = int(min(sim.dataset.years))
292
+ data_year_data = _year_data_with_weights(sim.dataset[data_year])
293
+ for year in years:
294
+ uk_dataset = PolicyEngineUKDataset(
295
+ id=f"{dataset_stem}_year_{year}",
296
+ name=f"{dataset_stem}-year-{year}",
297
+ description=f"UK Dataset for year {year} based on {dataset_stem}",
298
+ filepath=f"{data_folder}/{dataset_stem}_year_{year}.h5",
299
+ year=int(year),
300
+ data=_year_data_with_weights(sim.dataset[year]),
301
+ data_year=data_year,
302
+ data_year_data=data_year_data if int(year) > data_year else None,
303
+ )
304
+ uk_dataset.save()
305
+
306
+ dataset_key = f"{dataset_stem}_{year}"
307
+ result[dataset_key] = uk_dataset
308
+
309
+ return result
310
+
311
+
312
+ def load_datasets(
313
+ datasets: Optional[list[str]] = None,
314
+ years: list[int] = [2026, 2027, 2028, 2029, 2030],
315
+ data_folder: str = "./data",
316
+ ) -> dict[str, PolicyEngineUKDataset]:
317
+ """Load UK year files written by ``create_datasets``.
318
+
319
+ A year file without a recorded data year (``DATA_YEAR_KEY``) was written
320
+ before policyengine.py kept the observed year its tables were projected
321
+ from. policyengine-uk would take those projected tables as observed data
322
+ and uprate the State Pension by CPI rather than the triple lock, so such
323
+ a file is refused.
324
+ """
325
+ if datasets is None:
326
+ datasets = [get_release_manifest("uk").default_dataset]
327
+ result = {}
328
+ for dataset in datasets:
329
+ resolved_dataset = resolve_dataset_reference("uk", dataset)
330
+ dataset_stem = dataset_logical_name(resolved_dataset)
331
+ for year in years:
332
+ filepath = f"{data_folder}/{dataset_stem}_year_{year}.h5"
333
+ if Path(filepath).exists() and not _year_file_records_data_year(
334
+ Path(filepath)
335
+ ):
336
+ raise ValueError(
337
+ f"UK year file {filepath} has no recorded data year, so it "
338
+ "was written before policyengine.py kept the observed year "
339
+ "its tables were projected from. policyengine-uk would "
340
+ "take the projected tables as observed data and uprate the "
341
+ "State Pension by CPI rather than the triple lock. "
342
+ "Regenerate it with ensure_datasets() or create_datasets()."
343
+ )
344
+ # Constructing with a filepath loads the file.
345
+ uk_dataset = PolicyEngineUKDataset(
346
+ name=f"{dataset_stem}-year-{year}",
347
+ description=f"UK Dataset for year {year} based on {dataset_stem}",
348
+ filepath=filepath,
349
+ year=int(year),
350
+ )
351
+
352
+ dataset_key = f"{dataset_stem}_{year}"
353
+ result[dataset_key] = uk_dataset
354
+
355
+ return result
356
+
357
+
358
+ def ensure_datasets(
359
+ datasets: Optional[list[str]] = None,
360
+ years: list[int] = [2026, 2027, 2028, 2029, 2030],
361
+ data_folder: str = "./data",
362
+ allow_unmanaged: bool = False,
363
+ ) -> dict[str, PolicyEngineUKDataset]:
364
+ """Ensure datasets exist, loading if available or creating if not.
365
+
366
+ Year files without a recorded data year were written before
367
+ policyengine.py kept the observed year their tables were projected from
368
+ (see ``load_datasets``), so they are created again rather than loaded.
369
+
370
+ Args:
371
+ datasets: List of HuggingFace dataset paths
372
+ years: List of years to load/create data for
373
+ data_folder: Directory containing or to save the dataset files
374
+
375
+ Returns:
376
+ Dictionary mapping dataset keys to PolicyEngineUKDataset objects
377
+ """
378
+ if datasets is None:
379
+ datasets = [get_release_manifest("uk").default_dataset]
380
+
381
+ # Check if all dataset files exist
382
+ all_exist = True
383
+ for dataset in datasets:
384
+ resolved_dataset = resolve_dataset_reference("uk", dataset)
385
+ dataset_stem = dataset_logical_name(resolved_dataset)
386
+ for year in years:
387
+ filepath = Path(f"{data_folder}/{dataset_stem}_year_{year}.h5")
388
+ # A year file without a recorded data year would run as observed
389
+ # data, so it is regenerated rather than reused.
390
+ if not filepath.exists() or not _year_file_records_data_year(filepath):
391
+ all_exist = False
392
+ break
393
+ if not all_exist:
394
+ break
395
+
396
+ if all_exist:
397
+ return load_datasets(datasets=datasets, years=years, data_folder=data_folder)
398
+ else:
399
+ return create_datasets(
400
+ datasets=datasets,
401
+ years=years,
402
+ data_folder=data_folder,
403
+ allow_unmanaged=allow_unmanaged,
404
+ )