policyengine 3.1.13__tar.gz → 3.1.15__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 (90) hide show
  1. {policyengine-3.1.13 → policyengine-3.1.15}/CHANGELOG.md +14 -0
  2. {policyengine-3.1.13 → policyengine-3.1.15}/PKG-INFO +1 -1
  3. {policyengine-3.1.13 → policyengine-3.1.15}/changelog.yaml +10 -0
  4. policyengine-3.1.15/examples/household_impact_example.py +128 -0
  5. {policyengine-3.1.13 → policyengine-3.1.15}/pyproject.toml +1 -1
  6. policyengine-3.1.15/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  7. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/tax_benefit_model_version.py +9 -1
  8. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/outputs/decile_impact.py +22 -2
  9. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/uk/__init__.py +10 -2
  10. policyengine-3.1.15/src/policyengine/tax_benefit_models/uk/analysis.py +268 -0
  11. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/uk/datasets.py +2 -2
  12. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/uk/model.py +5 -2
  13. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/us/__init__.py +10 -2
  14. policyengine-3.1.15/src/policyengine/tax_benefit_models/us/analysis.py +288 -0
  15. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/us/datasets.py +2 -2
  16. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/us/model.py +1 -2
  17. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine.egg-info/PKG-INFO +1 -1
  18. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine.egg-info/SOURCES.txt +4 -1
  19. policyengine-3.1.15/tests/test_household_impact.py +207 -0
  20. policyengine-3.1.15/tests/test_models.py +77 -0
  21. {policyengine-3.1.13 → policyengine-3.1.15}/uv.lock +3 -1
  22. policyengine-3.1.13/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  23. policyengine-3.1.13/src/policyengine/tax_benefit_models/uk/analysis.py +0 -97
  24. policyengine-3.1.13/src/policyengine/tax_benefit_models/us/analysis.py +0 -99
  25. {policyengine-3.1.13 → policyengine-3.1.15}/.claude/policyengine-guide.md +0 -0
  26. {policyengine-3.1.13 → policyengine-3.1.15}/.claude/quick-reference.md +0 -0
  27. {policyengine-3.1.13 → policyengine-3.1.15}/.github/CONTRIBUTING.md +0 -0
  28. {policyengine-3.1.13 → policyengine-3.1.15}/.github/changelog_template.md +0 -0
  29. {policyengine-3.1.13 → policyengine-3.1.15}/.github/fetch_version.py +0 -0
  30. {policyengine-3.1.13 → policyengine-3.1.15}/.github/get-changelog-diff.sh +0 -0
  31. {policyengine-3.1.13 → policyengine-3.1.15}/.github/has-functional-changes.sh +0 -0
  32. {policyengine-3.1.13 → policyengine-3.1.15}/.github/is-version-number-acceptable.sh +0 -0
  33. {policyengine-3.1.13 → policyengine-3.1.15}/.github/publish-git-tag.sh +0 -0
  34. {policyengine-3.1.13 → policyengine-3.1.15}/.github/workflows/code_changes.yaml +0 -0
  35. {policyengine-3.1.13 → policyengine-3.1.15}/.github/workflows/docs.yml +0 -0
  36. {policyengine-3.1.13 → policyengine-3.1.15}/.github/workflows/pr_code_changes.yaml +0 -0
  37. {policyengine-3.1.13 → policyengine-3.1.15}/.github/workflows/pr_docs_changes.yaml +0 -0
  38. {policyengine-3.1.13 → policyengine-3.1.15}/.github/workflows/versioning.yaml +0 -0
  39. {policyengine-3.1.13 → policyengine-3.1.15}/.gitignore +0 -0
  40. {policyengine-3.1.13 → policyengine-3.1.15}/CLAUDE.md +0 -0
  41. {policyengine-3.1.13 → policyengine-3.1.15}/LICENSE +0 -0
  42. {policyengine-3.1.13 → policyengine-3.1.15}/Makefile +0 -0
  43. {policyengine-3.1.13 → policyengine-3.1.15}/README.md +0 -0
  44. {policyengine-3.1.13 → policyengine-3.1.15}/changelog_entry.yaml +0 -0
  45. {policyengine-3.1.13 → policyengine-3.1.15}/docs/.gitignore +0 -0
  46. {policyengine-3.1.13 → policyengine-3.1.15}/docs/core-concepts.md +0 -0
  47. {policyengine-3.1.13 → policyengine-3.1.15}/docs/country-models-uk.md +0 -0
  48. {policyengine-3.1.13 → policyengine-3.1.15}/docs/country-models-us.md +0 -0
  49. {policyengine-3.1.13 → policyengine-3.1.15}/docs/dev.md +0 -0
  50. {policyengine-3.1.13 → policyengine-3.1.15}/docs/index.md +0 -0
  51. {policyengine-3.1.13 → policyengine-3.1.15}/docs/myst.yml +0 -0
  52. {policyengine-3.1.13 → policyengine-3.1.15}/docs/visualisation.md +0 -0
  53. {policyengine-3.1.13 → policyengine-3.1.15}/examples/employment_income_variation_uk.py +0 -0
  54. {policyengine-3.1.13 → policyengine-3.1.15}/examples/employment_income_variation_us.py +0 -0
  55. {policyengine-3.1.13 → policyengine-3.1.15}/examples/income_bands_uk.py +0 -0
  56. {policyengine-3.1.13 → policyengine-3.1.15}/examples/income_distribution_us.py +0 -0
  57. {policyengine-3.1.13 → policyengine-3.1.15}/examples/policy_change_uk.py +0 -0
  58. {policyengine-3.1.13 → policyengine-3.1.15}/examples/speedtest_us_simulation.py +0 -0
  59. {policyengine-3.1.13 → policyengine-3.1.15}/setup.cfg +0 -0
  60. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/__init__.py +0 -0
  61. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/__init__.py +0 -0
  62. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/cache.py +0 -0
  63. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/dataset.py +0 -0
  64. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/dataset_version.py +0 -0
  65. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/dynamic.py +0 -0
  66. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/output.py +0 -0
  67. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/parameter.py +0 -0
  68. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/parameter_value.py +0 -0
  69. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/policy.py +0 -0
  70. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/simulation.py +0 -0
  71. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/tax_benefit_model.py +0 -0
  72. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/core/variable.py +0 -0
  73. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/outputs/__init__.py +0 -0
  74. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/outputs/aggregate.py +0 -0
  75. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/outputs/change_aggregate.py +0 -0
  76. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/uk/outputs.py +0 -0
  77. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/uk.py +0 -0
  78. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/us/outputs.py +0 -0
  79. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/tax_benefit_models/us.py +0 -0
  80. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/utils/__init__.py +0 -0
  81. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/utils/dates.py +0 -0
  82. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/utils/parametric_reforms.py +0 -0
  83. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine/utils/plotting.py +0 -0
  84. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine.egg-info/dependency_links.txt +0 -0
  85. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine.egg-info/requires.txt +0 -0
  86. {policyengine-3.1.13 → policyengine-3.1.15}/src/policyengine.egg-info/top_level.txt +0 -0
  87. {policyengine-3.1.13 → policyengine-3.1.15}/tests/test_aggregate.py +0 -0
  88. {policyengine-3.1.13 → policyengine-3.1.15}/tests/test_cache.py +0 -0
  89. {policyengine-3.1.13 → policyengine-3.1.15}/tests/test_change_aggregate.py +0 -0
  90. {policyengine-3.1.13 → policyengine-3.1.15}/tests/test_entity_mapping.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.15] - 2025-12-14 23:51:27
9
+
10
+ ### Added
11
+
12
+ - Household impacts
13
+
14
+ ## [3.1.14] - 2025-12-10 21:59:24
15
+
16
+ ### Fixed
17
+
18
+ - Improvements to loading taxbenefitsystems.
19
+
8
20
  ## [3.1.13] - 2025-12-10 19:29:28
9
21
 
10
22
  ### Fixed
@@ -275,6 +287,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
275
287
 
276
288
 
277
289
 
290
+ [3.1.15]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.14...3.1.15
291
+ [3.1.14]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.13...3.1.14
278
292
  [3.1.13]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.12...3.1.13
279
293
  [3.1.12]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.11...3.1.12
280
294
  [3.1.11]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.10...3.1.11
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.13
3
+ Version: 3.1.15
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
@@ -225,3 +225,13 @@
225
225
  fixed:
226
226
  - Naming improvements.
227
227
  date: 2025-12-10 19:29:28
228
+ - bump: patch
229
+ changes:
230
+ fixed:
231
+ - Improvements to loading taxbenefitsystems.
232
+ date: 2025-12-10 21:59:24
233
+ - bump: patch
234
+ changes:
235
+ added:
236
+ - Household impacts
237
+ date: 2025-12-14 23:51:27
@@ -0,0 +1,128 @@
1
+ """Example: Calculate household tax and benefit impacts.
2
+
3
+ This script demonstrates using calculate_household_impact for both UK and US
4
+ to compute taxes and benefits for custom households.
5
+
6
+ Run: python examples/household_impact_example.py
7
+ """
8
+
9
+ from policyengine.tax_benefit_models.uk import (
10
+ UKHouseholdInput,
11
+ )
12
+ from policyengine.tax_benefit_models.uk import (
13
+ calculate_household_impact as calculate_uk_impact,
14
+ )
15
+ from policyengine.tax_benefit_models.us import (
16
+ USHouseholdInput,
17
+ )
18
+ from policyengine.tax_benefit_models.us import (
19
+ calculate_household_impact as calculate_us_impact,
20
+ )
21
+
22
+
23
+ def uk_example():
24
+ """UK household impact example."""
25
+ print("=" * 60)
26
+ print("UK HOUSEHOLD IMPACT")
27
+ print("=" * 60)
28
+
29
+ # Single adult earning £50,000
30
+ household = UKHouseholdInput(
31
+ people=[{"age": 35, "employment_income": 50_000}],
32
+ year=2026,
33
+ )
34
+ result = calculate_uk_impact(household)
35
+
36
+ print("\nSingle adult, £50k income:")
37
+ print(
38
+ f" Net income: £{result.household['hbai_household_net_income']:,.0f}"
39
+ )
40
+ print(f" Income tax: £{result.person[0]['income_tax']:,.0f}")
41
+ print(
42
+ f" National Insurance: £{result.person[0]['national_insurance']:,.0f}"
43
+ )
44
+ print(f" Total tax: £{result.household['household_tax']:,.0f}")
45
+
46
+ # Family with two children, £30k income, renting
47
+ household = UKHouseholdInput(
48
+ people=[
49
+ {"age": 35, "employment_income": 30_000},
50
+ {"age": 33},
51
+ {"age": 8},
52
+ {"age": 5},
53
+ ],
54
+ benunit={
55
+ "would_claim_uc": True,
56
+ "would_claim_child_benefit": True,
57
+ },
58
+ household={
59
+ "rent": 12_000, # £1k/month
60
+ "region": "NORTH_WEST",
61
+ },
62
+ year=2026,
63
+ )
64
+ result = calculate_uk_impact(household)
65
+
66
+ print("\nFamily (2 adults, 2 children), £30k income, renting:")
67
+ print(
68
+ f" Net income: £{result.household['hbai_household_net_income']:,.0f}"
69
+ )
70
+ print(f" Income tax: £{result.person[0]['income_tax']:,.0f}")
71
+ print(f" Child benefit: £{result.benunit[0]['child_benefit']:,.0f}")
72
+ print(f" Universal credit: £{result.benunit[0]['universal_credit']:,.0f}")
73
+ print(f" Total benefits: £{result.household['household_benefits']:,.0f}")
74
+
75
+
76
+ def us_example():
77
+ """US household impact example."""
78
+ print("\n" + "=" * 60)
79
+ print("US HOUSEHOLD IMPACT")
80
+ print("=" * 60)
81
+
82
+ # Single adult earning $50,000
83
+ household = USHouseholdInput(
84
+ people=[
85
+ {"age": 35, "employment_income": 50_000, "is_tax_unit_head": True}
86
+ ],
87
+ tax_unit={"filing_status": "SINGLE"},
88
+ household={"state_code_str": "CA"},
89
+ year=2024,
90
+ )
91
+ result = calculate_us_impact(household)
92
+
93
+ print("\nSingle adult, $50k income (California):")
94
+ print(f" Net income: ${result.household['household_net_income']:,.0f}")
95
+ print(f" Income tax: ${result.tax_unit[0]['income_tax']:,.0f}")
96
+ print(f" Payroll tax: ${result.tax_unit[0]['employee_payroll_tax']:,.0f}")
97
+
98
+ # Married couple with children, lower income
99
+ household = USHouseholdInput(
100
+ people=[
101
+ {"age": 35, "employment_income": 40_000, "is_tax_unit_head": True},
102
+ {"age": 33, "is_tax_unit_spouse": True},
103
+ {"age": 8, "is_tax_unit_dependent": True},
104
+ {"age": 5, "is_tax_unit_dependent": True},
105
+ ],
106
+ tax_unit={"filing_status": "JOINT"},
107
+ household={"state_code_str": "TX"},
108
+ year=2024,
109
+ )
110
+ result = calculate_us_impact(household)
111
+
112
+ print("\nMarried couple with 2 children, $40k income (Texas):")
113
+ print(f" Net income: ${result.household['household_net_income']:,.0f}")
114
+ print(f" Federal income tax: ${result.tax_unit[0]['income_tax']:,.0f}")
115
+ print(f" EITC: ${result.tax_unit[0]['eitc']:,.0f}")
116
+ print(f" Child tax credit: ${result.tax_unit[0]['ctc']:,.0f}")
117
+ print(f" SNAP: ${result.spm_unit[0]['snap']:,.0f}")
118
+
119
+
120
+ def main():
121
+ uk_example()
122
+ us_example()
123
+ print("\n" + "=" * 60)
124
+ print("Done!")
125
+
126
+
127
+ if __name__ == "__main__":
128
+ main()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "3.1.13"
7
+ version = "3.1.15"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -24,7 +24,15 @@ class TaxBenefitModelVersion(BaseModel):
24
24
 
25
25
  variables: list["Variable"] = Field(default_factory=list)
26
26
  parameters: list["Parameter"] = Field(default_factory=list)
27
- parameter_values: list["ParameterValue"] = Field(default_factory=list)
27
+
28
+ @property
29
+ def parameter_values(self) -> list["ParameterValue"]:
30
+ """Aggregate all parameter values from all parameters."""
31
+ return [
32
+ pv
33
+ for parameter in self.parameters
34
+ for pv in parameter.parameter_values
35
+ ]
28
36
 
29
37
  # Lookup dicts for O(1) access (excluded from serialization)
30
38
  variables_by_name: dict[str, "Variable"] = Field(
@@ -2,6 +2,10 @@ import pandas as pd
2
2
  from pydantic import ConfigDict
3
3
 
4
4
  from policyengine.core import Output, OutputCollection, Simulation
5
+ from policyengine.core.dataset import Dataset
6
+ from policyengine.core.dynamic import Dynamic
7
+ from policyengine.core.policy import Policy
8
+ from policyengine.core.tax_benefit_model_version import TaxBenefitModelVersion
5
9
 
6
10
 
7
11
  class DecileImpact(Output):
@@ -93,8 +97,11 @@ class DecileImpact(Output):
93
97
 
94
98
 
95
99
  def calculate_decile_impacts(
96
- baseline_simulation: Simulation,
97
- reform_simulation: Simulation,
100
+ dataset: Dataset,
101
+ tax_benefit_model_version: TaxBenefitModelVersion,
102
+ baseline_policy: Policy | None = None,
103
+ reform_policy: Policy | None = None,
104
+ dynamic: Dynamic | None = None,
98
105
  income_variable: str = "equiv_hbai_household_net_income",
99
106
  entity: str | None = None,
100
107
  quantiles: int = 10,
@@ -104,6 +111,19 @@ def calculate_decile_impacts(
104
111
  Returns:
105
112
  OutputCollection containing list of DecileImpact objects and DataFrame
106
113
  """
114
+ baseline_simulation = Simulation(
115
+ dataset=dataset,
116
+ tax_benefit_model_version=tax_benefit_model_version,
117
+ policy=baseline_policy,
118
+ dynamic=dynamic,
119
+ )
120
+ reform_simulation = Simulation(
121
+ dataset=dataset,
122
+ tax_benefit_model_version=tax_benefit_model_version,
123
+ policy=reform_policy,
124
+ dynamic=dynamic,
125
+ )
126
+
107
127
  results = []
108
128
  for decile in range(1, quantiles + 1):
109
129
  impact = DecileImpact(
@@ -5,7 +5,12 @@ from importlib.util import find_spec
5
5
  if find_spec("policyengine_uk") is not None:
6
6
  from policyengine.core import Dataset
7
7
 
8
- from .analysis import general_policy_reform_analysis
8
+ from .analysis import (
9
+ UKHouseholdInput,
10
+ UKHouseholdOutput,
11
+ calculate_household_impact,
12
+ economic_impact_analysis,
13
+ )
9
14
  from .datasets import (
10
15
  PolicyEngineUKDataset,
11
16
  UKYearData,
@@ -37,7 +42,10 @@ if find_spec("policyengine_uk") is not None:
37
42
  "PolicyEngineUKLatest",
38
43
  "uk_model",
39
44
  "uk_latest",
40
- "general_policy_reform_analysis",
45
+ "economic_impact_analysis",
46
+ "calculate_household_impact",
47
+ "UKHouseholdInput",
48
+ "UKHouseholdOutput",
41
49
  "ProgrammeStatistics",
42
50
  ]
43
51
  else:
@@ -0,0 +1,268 @@
1
+ """General utility functions for UK policy reform analysis."""
2
+
3
+ import tempfile
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import pandas as pd
8
+ from microdf import MicroDataFrame
9
+ from pydantic import BaseModel, Field, create_model
10
+
11
+ from policyengine.core import OutputCollection, Simulation
12
+ from policyengine.core.policy import Policy
13
+ from policyengine.outputs.decile_impact import (
14
+ DecileImpact,
15
+ calculate_decile_impacts,
16
+ )
17
+
18
+ from .datasets import PolicyEngineUKDataset, UKYearData
19
+ from .model import uk_latest
20
+ from .outputs import ProgrammeStatistics
21
+
22
+
23
+ def _create_entity_output_model(
24
+ entity: str, variables: list[str]
25
+ ) -> type[BaseModel]:
26
+ """Create a dynamic Pydantic model for entity output variables."""
27
+ fields = {var: (float, ...) for var in variables}
28
+ return create_model(f"{entity.title()}Output", **fields)
29
+
30
+
31
+ # Create output models dynamically from uk_latest.entity_variables
32
+ PersonOutput = _create_entity_output_model(
33
+ "person", uk_latest.entity_variables["person"]
34
+ )
35
+ BenunitOutput = _create_entity_output_model(
36
+ "benunit", uk_latest.entity_variables["benunit"]
37
+ )
38
+ HouseholdEntityOutput = _create_entity_output_model(
39
+ "household", uk_latest.entity_variables["household"]
40
+ )
41
+
42
+
43
+ class UKHouseholdOutput(BaseModel):
44
+ """Output from a UK household calculation with all entity data."""
45
+
46
+ person: list[dict[str, Any]]
47
+ benunit: list[dict[str, Any]]
48
+ household: dict[str, Any]
49
+
50
+
51
+ class UKHouseholdInput(BaseModel):
52
+ """Input for a UK household calculation."""
53
+
54
+ people: list[dict[str, Any]]
55
+ benunit: dict[str, Any] = Field(default_factory=dict)
56
+ household: dict[str, Any] = Field(default_factory=dict)
57
+ year: int = 2026
58
+
59
+
60
+ def calculate_household_impact(
61
+ household_input: UKHouseholdInput,
62
+ policy: Policy | None = None,
63
+ ) -> UKHouseholdOutput:
64
+ """Calculate tax and benefit impacts for a single UK household."""
65
+ n_people = len(household_input.people)
66
+
67
+ # Build person data with defaults
68
+ person_data = {
69
+ "person_id": list(range(n_people)),
70
+ "person_benunit_id": [0] * n_people,
71
+ "person_household_id": [0] * n_people,
72
+ "person_weight": [1.0] * n_people,
73
+ }
74
+ # Add user-provided person fields
75
+ for i, person in enumerate(household_input.people):
76
+ for key, value in person.items():
77
+ if key not in person_data:
78
+ person_data[key] = [
79
+ 0.0
80
+ ] * n_people # Default to 0 for numeric fields
81
+ person_data[key][i] = value
82
+
83
+ # Build benunit data with defaults
84
+ benunit_data = {
85
+ "benunit_id": [0],
86
+ "benunit_weight": [1.0],
87
+ }
88
+ for key, value in household_input.benunit.items():
89
+ benunit_data[key] = [value]
90
+
91
+ # Build household data with defaults (required for uprating)
92
+ household_data = {
93
+ "household_id": [0],
94
+ "household_weight": [1.0],
95
+ "region": ["LONDON"],
96
+ "tenure_type": ["RENT_PRIVATELY"],
97
+ "council_tax": [0.0],
98
+ "rent": [0.0],
99
+ }
100
+ for key, value in household_input.household.items():
101
+ household_data[key] = [value]
102
+
103
+ # Create MicroDataFrames
104
+ person_df = MicroDataFrame(
105
+ pd.DataFrame(person_data), weights="person_weight"
106
+ )
107
+ benunit_df = MicroDataFrame(
108
+ pd.DataFrame(benunit_data), weights="benunit_weight"
109
+ )
110
+ household_df = MicroDataFrame(
111
+ pd.DataFrame(household_data), weights="household_weight"
112
+ )
113
+
114
+ # Create temporary dataset
115
+ tmpdir = tempfile.mkdtemp()
116
+ filepath = str(Path(tmpdir) / "household_impact.h5")
117
+
118
+ dataset = PolicyEngineUKDataset(
119
+ name="Household impact calculation",
120
+ description="Single household for impact calculation",
121
+ filepath=filepath,
122
+ year=household_input.year,
123
+ data=UKYearData(
124
+ person=person_df,
125
+ benunit=benunit_df,
126
+ household=household_df,
127
+ ),
128
+ )
129
+
130
+ # Run simulation
131
+ simulation = Simulation(
132
+ dataset=dataset,
133
+ tax_benefit_model_version=uk_latest,
134
+ policy=policy,
135
+ )
136
+ simulation.run()
137
+
138
+ # Extract all output variables defined in entity_variables
139
+ output_data = simulation.output_dataset.data
140
+
141
+ def safe_convert(value):
142
+ """Convert value to float if numeric, otherwise return as string."""
143
+ try:
144
+ return float(value)
145
+ except (ValueError, TypeError):
146
+ return str(value)
147
+
148
+ person_outputs = []
149
+ for i in range(n_people):
150
+ person_dict = {}
151
+ for var in uk_latest.entity_variables["person"]:
152
+ person_dict[var] = safe_convert(output_data.person[var].iloc[i])
153
+ person_outputs.append(person_dict)
154
+
155
+ benunit_outputs = []
156
+ for i in range(len(output_data.benunit)):
157
+ benunit_dict = {}
158
+ for var in uk_latest.entity_variables["benunit"]:
159
+ benunit_dict[var] = safe_convert(output_data.benunit[var].iloc[i])
160
+ benunit_outputs.append(benunit_dict)
161
+
162
+ household_dict = {}
163
+ for var in uk_latest.entity_variables["household"]:
164
+ household_dict[var] = safe_convert(output_data.household[var].iloc[0])
165
+
166
+ return UKHouseholdOutput(
167
+ person=person_outputs,
168
+ benunit=benunit_outputs,
169
+ household=household_dict,
170
+ )
171
+
172
+
173
+ class PolicyReformAnalysis(BaseModel):
174
+ """Complete policy reform analysis result."""
175
+
176
+ decile_impacts: OutputCollection[DecileImpact]
177
+ programme_statistics: OutputCollection[ProgrammeStatistics]
178
+
179
+
180
+ def economic_impact_analysis(
181
+ baseline_simulation: Simulation,
182
+ reform_simulation: Simulation,
183
+ ) -> PolicyReformAnalysis:
184
+ """Perform comprehensive analysis of a policy reform.
185
+
186
+ Returns:
187
+ PolicyReformAnalysis containing decile impacts and programme statistics
188
+ """
189
+ baseline_simulation.ensure()
190
+ reform_simulation.ensure()
191
+
192
+ assert len(baseline_simulation.dataset.data.household) > 100, (
193
+ "Baseline simulation must have more than 100 households"
194
+ )
195
+ assert len(reform_simulation.dataset.data.household) > 100, (
196
+ "Reform simulation must have more than 100 households"
197
+ )
198
+
199
+ # Decile impact
200
+ decile_impacts = calculate_decile_impacts(
201
+ dataset=baseline_simulation.dataset,
202
+ tax_benefit_model_version=baseline_simulation.tax_benefit_model_version,
203
+ baseline_policy=baseline_simulation.policy,
204
+ reform_policy=reform_simulation.policy,
205
+ dynamic=baseline_simulation.dynamic,
206
+ )
207
+
208
+ # Major programmes to analyse
209
+ programmes = {
210
+ # Tax
211
+ "income_tax": {"entity": "person", "is_tax": True},
212
+ "national_insurance": {"entity": "person", "is_tax": True},
213
+ "vat": {"entity": "household", "is_tax": True},
214
+ "council_tax": {"entity": "household", "is_tax": True},
215
+ # Benefits
216
+ "universal_credit": {"entity": "person", "is_tax": False},
217
+ "child_benefit": {"entity": "person", "is_tax": False},
218
+ "pension_credit": {"entity": "person", "is_tax": False},
219
+ "income_support": {"entity": "person", "is_tax": False},
220
+ "working_tax_credit": {"entity": "person", "is_tax": False},
221
+ "child_tax_credit": {"entity": "person", "is_tax": False},
222
+ }
223
+
224
+ programme_statistics = []
225
+
226
+ for programme_name, programme_info in programmes.items():
227
+ entity = programme_info["entity"]
228
+ is_tax = programme_info["is_tax"]
229
+
230
+ stats = ProgrammeStatistics(
231
+ baseline_simulation=baseline_simulation,
232
+ reform_simulation=reform_simulation,
233
+ programme_name=programme_name,
234
+ entity=entity,
235
+ is_tax=is_tax,
236
+ )
237
+ stats.run()
238
+ programme_statistics.append(stats)
239
+
240
+ # Create DataFrame
241
+ programme_df = pd.DataFrame(
242
+ [
243
+ {
244
+ "baseline_simulation_id": p.baseline_simulation.id,
245
+ "reform_simulation_id": p.reform_simulation.id,
246
+ "programme_name": p.programme_name,
247
+ "entity": p.entity,
248
+ "is_tax": p.is_tax,
249
+ "baseline_total": p.baseline_total,
250
+ "reform_total": p.reform_total,
251
+ "change": p.change,
252
+ "baseline_count": p.baseline_count,
253
+ "reform_count": p.reform_count,
254
+ "winners": p.winners,
255
+ "losers": p.losers,
256
+ }
257
+ for p in programme_statistics
258
+ ]
259
+ )
260
+
261
+ programme_collection = OutputCollection(
262
+ outputs=programme_statistics, dataframe=programme_df
263
+ )
264
+
265
+ return PolicyReformAnalysis(
266
+ decile_impacts=decile_impacts,
267
+ programme_statistics=programme_collection,
268
+ )
@@ -196,8 +196,8 @@ def load_datasets(
196
196
  for year in years:
197
197
  filepath = f"{data_folder}/{Path(dataset).stem}_year_{year}.h5"
198
198
  uk_dataset = PolicyEngineUKDataset(
199
- name=f"{dataset}-year-{year}",
200
- description=f"UK Dataset for year {year} based on {dataset}",
199
+ name=f"{Path(dataset).stem}-year-{year}",
200
+ description=f"UK Dataset for year {year} based on {Path(dataset).stem}",
201
201
  filepath=filepath,
202
202
  year=int(year),
203
203
  )
@@ -9,12 +9,10 @@ from microdf import MicroDataFrame
9
9
 
10
10
  from policyengine.core import (
11
11
  Parameter,
12
- ParameterValue,
13
12
  TaxBenefitModel,
14
13
  TaxBenefitModelVersion,
15
14
  Variable,
16
15
  )
17
- from policyengine.utils import parse_safe_date
18
16
 
19
17
  from .datasets import PolicyEngineUKDataset, UKYearData
20
18
 
@@ -108,6 +106,11 @@ class PolicyEngineUKLatest(TaxBenefitModelVersion):
108
106
  "rent",
109
107
  "council_tax",
110
108
  "tenure_type",
109
+ # Poverty measures
110
+ "in_poverty_bhc",
111
+ "in_poverty_ahc",
112
+ "in_relative_poverty_bhc",
113
+ "in_relative_poverty_ahc",
111
114
  ],
112
115
  }
113
116
 
@@ -5,7 +5,12 @@ from importlib.util import find_spec
5
5
  if find_spec("policyengine_us") is not None:
6
6
  from policyengine.core import Dataset
7
7
 
8
- from .analysis import general_policy_reform_analysis
8
+ from .analysis import (
9
+ USHouseholdInput,
10
+ USHouseholdOutput,
11
+ calculate_household_impact,
12
+ economic_impact_analysis,
13
+ )
9
14
  from .datasets import (
10
15
  PolicyEngineUSDataset,
11
16
  USYearData,
@@ -37,7 +42,10 @@ if find_spec("policyengine_us") is not None:
37
42
  "PolicyEngineUSLatest",
38
43
  "us_model",
39
44
  "us_latest",
40
- "general_policy_reform_analysis",
45
+ "economic_impact_analysis",
46
+ "calculate_household_impact",
47
+ "USHouseholdInput",
48
+ "USHouseholdOutput",
41
49
  "ProgramStatistics",
42
50
  ]
43
51
  else: