diff --git a/README.md b/README.md index 7fc607d5..853bd346 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,47 @@ A Python package for tax-benefit microsimulation analysis. Run policy simulation ## Quick start +### Household calculator + ```python -from policyengine.core import Simulation -from policyengine.tax_benefit_models.uk import PolicyEngineUKDataset, uk_latest -from policyengine.outputs.aggregate import Aggregate, AggregateType +import policyengine as pe -# Load representative microdata -dataset = PolicyEngineUKDataset( - name="FRS 2023-24", - filepath="./data/frs_2023_24_year_2026.h5", +# UK: single adult earning £50,000 +uk = pe.uk.calculate_household( + people=[{"age": 35, "employment_income": 50_000}], year=2026, ) +print(uk.person[0].income_tax) # income tax +print(uk.household.hbai_household_net_income) # net income + +# US: single filer in California, with a reform +us = pe.us.calculate_household( + people=[{"age": 35, "employment_income": 60_000}], + tax_unit={"filing_status": "SINGLE"}, + household={"state_code_str": "CA"}, + year=2026, + reform={"gov.irs.credits.ctc.amount.adult_dependent": 1000}, +) +print(us.tax_unit.income_tax, us.household.household_net_income) +``` -# Run simulation -simulation = Simulation( - dataset=dataset, - tax_benefit_model_version=uk_latest, +### Population analysis + +```python +import policyengine as pe +from policyengine.core import Simulation +from policyengine.outputs.aggregate import Aggregate, AggregateType + +datasets = pe.uk.ensure_datasets( + datasets=["hf://policyengine/policyengine-uk-data/enhanced_frs_2023_24.h5"], + years=[2026], + data_folder="./data", ) +dataset = datasets["enhanced_frs_2023_24_2026"] + +simulation = Simulation(dataset=dataset, tax_benefit_model_version=pe.uk.model) simulation.run() -# Calculate total universal credit spending agg = Aggregate( simulation=simulation, variable="universal_credit", @@ -34,6 +55,9 @@ agg.run() print(f"Total UC spending: £{agg.result / 1e9:.1f}bn") ``` +For baseline-vs-reform comparisons, see `pe.uk.economic_impact_analysis` +and its US counterpart. + ## Documentation **Core concepts:** @@ -179,12 +203,12 @@ dataset.load() Simulations apply tax-benefit models to datasets: ```python +import policyengine as pe from policyengine.core import Simulation -from policyengine.tax_benefit_models.uk import uk_latest simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, ) simulation.run() @@ -223,7 +247,7 @@ import datetime parameter = Parameter( name="gov.hmrc.income_tax.allowances.personal_allowance.amount", - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, data_type=float, ) @@ -242,7 +266,7 @@ policy = Policy( # Run reform simulation reform_sim = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, policy=policy, ) reform_sim.run() diff --git a/changelog.d/v4-docs-refresh.changed.md b/changelog.d/v4-docs-refresh.changed.md new file mode 100644 index 00000000..11e7d0d2 --- /dev/null +++ b/changelog.d/v4-docs-refresh.changed.md @@ -0,0 +1 @@ +Documentation refreshed for the v4 agent-first surface. README, `core-concepts`, `economic-impact-analysis`, `country-models-{uk,us}`, `regions-and-scoping`, `examples`, and `dev` now lead with `pe.uk.*` / `pe.us.*` entry points and flat-kwarg `calculate_household` usage. Removed leftover docs for the dropped `filter_field`/`filter_value` simulation fields. `examples/household_impact_example.py` rewritten against the v4 API. diff --git a/docs/core-concepts.md b/docs/core-concepts.md index 425c5f62..383f403c 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -2,6 +2,55 @@ PolicyEngine.py is a Python package for tax-benefit microsimulation analysis. It provides a unified interface for running policy simulations, analysing distributional impacts, and visualising results across different countries. +## Quick start + +Most analyses start from the country entry points on the top-level +package — ``policyengine.uk`` and ``policyengine.us``. They expose flat +keyword-argument functions that return structured results with +dot-access for scalar lookups. + +```python +import policyengine as pe + +# UK: single adult earning £50,000 +uk = pe.uk.calculate_household( + people=[{"age": 35, "employment_income": 50_000}], + year=2026, +) +print(uk.household.hbai_household_net_income) # net income +print(uk.person[0].income_tax) # per-person dot access + +# US: married couple with two kids in Texas +us = pe.us.calculate_household( + people=[ + {"age": 35, "employment_income": 40_000}, + {"age": 33}, + {"age": 8}, + {"age": 5}, + ], + tax_unit={"filing_status": "JOINT"}, + household={"state_code_str": "TX"}, + year=2026, +) +print(us.tax_unit.income_tax, us.tax_unit.eitc, us.tax_unit.ctc) + +# Apply a reform: just pass a parameter-path dict +reformed = pe.us.calculate_household( + people=[{"age": 35, "employment_income": 60_000}], + tax_unit={"filing_status": "SINGLE"}, + year=2026, + reform={"gov.irs.credits.ctc.amount.adult_dependent": 1000}, +) +``` + +Reforms can be scalar values (treated as ``{year}-01-01`` onwards) or a +mapping of effective-date strings to values for time-varying reforms. +Unknown variable names raise with suggestions instead of silently +returning zero. + +For population-level analysis (budget impact, distributional effects), +see [Economic impact analysis](economic-impact-analysis.md). + ## Architecture overview The package is organised around several core concepts: @@ -22,9 +71,14 @@ Tax-benefit models define the rules and calculations for a country's tax and ben ### Using a tax-benefit model +The country entry points expose pinned model versions as ``pe.uk.model`` +and ``pe.us.model``: + ```python -from policyengine.tax_benefit_models.uk import uk_latest -from policyengine.tax_benefit_models.us import us_latest +import policyengine as pe + +uk_latest = pe.uk.model +us_latest = pe.us.model # UK model includes variables like: # - income_tax, national_insurance, universal_credit @@ -46,7 +100,7 @@ Datasets contain microdata representing a population. Each dataset has: ### Dataset structure ```python -from policyengine.tax_benefit_models.uk import PolicyEngineUKDataset +from policyengine.tax_benefit_models.uk import PolicyEngineUKDataset # or: pe.uk.PolicyEngineUKDataset dataset = PolicyEngineUKDataset( name="FRS 2023-24", @@ -126,7 +180,7 @@ Before running simulations, you need representative microdata. The package provi - **`load_datasets()`**: Load previously saved HDF5 files from disk ```python -from policyengine.tax_benefit_models.us import ensure_datasets +from policyengine.tax_benefit_models.us import ensure_datasets # or: pe.us.ensure_datasets # First run: downloads from HuggingFace, computes variables, saves to ./data/ # Subsequent runs: loads from disk instantly @@ -139,7 +193,7 @@ dataset = datasets["enhanced_cps_2024_2026"] ``` ```python -from policyengine.tax_benefit_models.uk import ensure_datasets +from policyengine.tax_benefit_models.uk import ensure_datasets # or: pe.uk.ensure_datasets datasets = ensure_datasets( datasets=["hf://policyengine/policyengine-uk-data/enhanced_frs_2023_24.h5"], @@ -158,12 +212,12 @@ Simulations apply tax-benefit models to datasets, calculating all variables for ### Running a simulation ```python +import policyengine as pe from policyengine.core import Simulation -from policyengine.tax_benefit_models.uk import uk_latest simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, ) simulation.run() @@ -201,7 +255,7 @@ After running a simulation, you can access the calculated variables from the out ```python simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, ) simulation.run() @@ -225,10 +279,13 @@ import datetime # Define parameter to modify parameter = Parameter( name="gov.hmrc.income_tax.allowances.personal_allowance.amount", - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, description="Personal allowance for income tax", data_type=float, ) +# Shortcut: when using `calculate_household`, reforms may be passed as a dict +# and compiled internally. This low-level construction is only needed when +# you are building a Simulation manually. # Set new value parameter_value = ParameterValue( @@ -251,14 +308,14 @@ policy = Policy( # Baseline simulation baseline = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, ) baseline.run() # Reform simulation reform = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, policy=policy, ) reform.run() @@ -306,7 +363,7 @@ dynamic = Dynamic( simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, policy=policy, dynamic=dynamic, ) diff --git a/docs/country-models-uk.md b/docs/country-models-uk.md index 0bc54505..27b09d0a 100644 --- a/docs/country-models-uk.md +++ b/docs/country-models-uk.md @@ -2,6 +2,44 @@ The UK tax-benefit model implements the United Kingdom's tax and benefit system using PolicyEngine UK as the underlying calculation engine. +## Quick start + +```python +import policyengine as pe + +# Single adult earning £50k +result = pe.uk.calculate_household( + people=[{"age": 35, "employment_income": 50_000}], + year=2026, +) +print(result.person[0].income_tax, result.household.hbai_household_net_income) + +# Family renting, with benefit claims explicitly on +result = pe.uk.calculate_household( + people=[ + {"age": 35, "employment_income": 30_000}, + {"age": 33}, + {"age": 8}, + {"age": 5}, + ], + benunit={"would_claim_uc": True, "would_claim_child_benefit": True}, + household={"rent": 12_000, "region": "NORTH_WEST"}, + year=2026, +) + +# Apply a reform +result = pe.uk.calculate_household( + people=[{"age": 35, "employment_income": 50_000}], + year=2026, + reform={ + "gov.hmrc.income_tax.allowances.personal_allowance.amount": 15_000, + }, +) +``` + +For population-level analysis and reform analysis, see +[Economic impact analysis](economic-impact-analysis.md). + ## Entity structure The UK model uses three entity levels: @@ -149,11 +187,11 @@ dataset = PolicyEngineUKDataset( ```python from policyengine.core import Simulation -from policyengine.tax_benefit_models.uk import uk_latest +import policyengine as pe simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, ) simulation.run() @@ -203,7 +241,7 @@ import datetime parameter = Parameter( name="gov.hmrc.income_tax.allowances.personal_allowance.amount", - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, description="Personal allowance", data_type=float, ) @@ -227,7 +265,7 @@ policy = Policy( ```python parameter = Parameter( name="gov.dwp.universal_credit.means_test.reduction_rate", - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, description="UC taper rate", data_type=float, ) @@ -252,7 +290,7 @@ policy = Policy( # Set subsequent child element equal to first child parameter = Parameter( name="gov.dwp.universal_credit.elements.child.subsequent_child", - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, description="UC subsequent child element", data_type=float, ) diff --git a/docs/country-models-us.md b/docs/country-models-us.md index 268c888f..46557a92 100644 --- a/docs/country-models-us.md +++ b/docs/country-models-us.md @@ -2,6 +2,39 @@ The US tax-benefit model implements the United States federal tax and benefit system using PolicyEngine US as the underlying calculation engine. +## Quick start + +```python +import policyengine as pe + +# Single adult earning $60k (SINGLE filer, default state) +result = pe.us.calculate_household( + people=[{"age": 35, "employment_income": 60_000}], + tax_unit={"filing_status": "SINGLE"}, + year=2026, +) +print(result.tax_unit.income_tax, result.household.household_net_income) + +# With a reform +result = pe.us.calculate_household( + people=[{"age": 35, "employment_income": 60_000}], + tax_unit={"filing_status": "SINGLE"}, + year=2026, + reform={"gov.irs.credits.ctc.amount.adult_dependent": 1000}, +) + +# Request extra variables not in the default result +result = pe.us.calculate_household( + people=[{"age": 35, "employment_income": 60_000}], + tax_unit={"filing_status": "SINGLE"}, + year=2026, + extra_variables=["adjusted_gross_income", "taxable_income"], +) +``` + +For population-level analysis and reform analysis, see +[Economic impact analysis](economic-impact-analysis.md). + ## Entity structure The US model uses a more complex entity hierarchy: @@ -183,11 +216,11 @@ dataset = PolicyEngineUSDataset( ```python from policyengine.core import Simulation -from policyengine.tax_benefit_models.us import us_latest +import policyengine as pe simulation = Simulation( dataset=dataset, - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, ) simulation.run() @@ -247,7 +280,7 @@ import datetime parameter = Parameter( name="gov.irs.income.standard_deduction.single", - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, description="Standard deduction (single)", data_type=float, ) @@ -271,7 +304,7 @@ policy = Policy( ```python parameter = Parameter( name="gov.irs.credits.ctc.amount.base", - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, description="Base CTC amount", data_type=float, ) @@ -295,7 +328,7 @@ policy = Policy( ```python parameter = Parameter( name="gov.irs.credits.ctc.refundable.amount.max", - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, description="Maximum refundable CTC", data_type=float, ) diff --git a/docs/dev.md b/docs/dev.md index 007a94e5..c6f0937d 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -83,12 +83,15 @@ For the target release-bundle architecture, see [Release bundles](release-bundle ``` src/policyengine/ +├── __init__.py # Public surface: `pe.uk`, `pe.us`, `pe.Simulation` ├── core/ # Domain models (Simulation, Dataset, Policy, etc.) ├── tax_benefit_models/ -│ ├── uk/ # UK model, datasets, analysis, outputs -│ └── us/ # US model, datasets, analysis, outputs +│ ├── common/ # MicrosimulationModelVersion base, result types, reform compiler +│ ├── uk/ # UK model, datasets, household calculator, reform analysis +│ └── us/ # US model, datasets, household calculator, reform analysis ├── outputs/ # Output templates (Aggregate, Poverty, etc.) -├── countries/ # Geographic region registries +├── provenance/ # Release manifests + TRACE TRO export +├── countries/ # Geographic region registries (scoping, constituencies, districts) └── utils/ # Helpers (reforms, entity mapping, plotting) ``` @@ -98,7 +101,7 @@ src/policyengine/ **HDF5 for storage**: Datasets and simulation outputs are stored as HDF5 files. No database server is required. The `MicroDataFrame` from the `microdf` package wraps pandas DataFrames with weight-aware `.sum()`, `.mean()`, `.count()`. -**Country-specific model classes**: `PolicyEngineUSLatest` and `PolicyEngineUKLatest` each implement `run()`, `save()`, and `load()`. The US model passes reforms as a dict at `Microsimulation(reform=...)` construction time. The UK model supports both parametric reforms and `simulation_modifier` callables applied post-construction. +**Country-specific model classes**: `PolicyEngineUSLatest` and `PolicyEngineUKLatest` inherit from a shared `MicrosimulationModelVersion` base (variable/parameter loading, manifest certification, `save`/`load`). Each subclass only implements `run()` and a handful of country hooks (`_load_system`, `_load_region_registry`, `_dataset_class`, `_get_runtime_data_build_metadata`). The US `run` applies reforms as a dict at `Microsimulation(reform=...)` construction time; the UK `run` wraps inputs as `UKSingleYearDataset` and applies reforms via a modifier after construction. **LRU cache + file caching**: `Simulation.ensure()` checks an in-process LRU cache (max 100 entries), then tries loading from disk, then falls back to `run()` + `save()`. diff --git a/docs/economic-impact-analysis.md b/docs/economic-impact-analysis.md index 0d28dff8..88888515 100644 --- a/docs/economic-impact-analysis.md +++ b/docs/economic-impact-analysis.md @@ -19,15 +19,11 @@ There are two approaches to comparing simulations: ```python import datetime +import policyengine as pe from policyengine.core import Parameter, ParameterValue, Policy, Simulation -from policyengine.tax_benefit_models.us import ( - economic_impact_analysis, - ensure_datasets, - us_latest, -) # 1. Load data -datasets = ensure_datasets( +datasets = pe.us.ensure_datasets( datasets=["hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5"], years=[2026], data_folder="./data", @@ -37,7 +33,7 @@ dataset = datasets["enhanced_cps_2024_2026"] # 2. Define reform param = Parameter( name="gov.irs.deductions.standard.amount.SINGLE", - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, ) reform = Policy( name="Double standard deduction (single)", @@ -52,32 +48,23 @@ reform = Policy( ) # 3. Create simulations (no need to call .run() — ensure() is called internally) -baseline_sim = Simulation( - dataset=dataset, - tax_benefit_model_version=us_latest, -) +baseline_sim = Simulation(dataset=dataset, tax_benefit_model_version=pe.us.model) reform_sim = Simulation( - dataset=dataset, - tax_benefit_model_version=us_latest, - policy=reform, + dataset=dataset, tax_benefit_model_version=pe.us.model, policy=reform ) # 4. Run full analysis -analysis = economic_impact_analysis(baseline_sim, reform_sim) +analysis = pe.us.economic_impact_analysis(baseline_sim, reform_sim) ``` ### UK example ```python import datetime +import policyengine as pe from policyengine.core import Parameter, ParameterValue, Policy, Simulation -from policyengine.tax_benefit_models.uk import ( - economic_impact_analysis, - ensure_datasets, - uk_latest, -) -datasets = ensure_datasets( +datasets = pe.uk.ensure_datasets( datasets=["hf://policyengine/policyengine-uk-data/enhanced_frs_2023_24.h5"], years=[2026], data_folder="./data", @@ -86,7 +73,7 @@ dataset = datasets["enhanced_frs_2023_24_2026"] param = Parameter( name="gov.hmrc.income_tax.allowances.personal_allowance.amount", - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, ) reform = Policy( name="Zero personal allowance", @@ -100,17 +87,12 @@ reform = Policy( ], ) -baseline_sim = Simulation( - dataset=dataset, - tax_benefit_model_version=uk_latest, -) +baseline_sim = Simulation(dataset=dataset, tax_benefit_model_version=pe.uk.model) reform_sim = Simulation( - dataset=dataset, - tax_benefit_model_version=uk_latest, - policy=reform, + dataset=dataset, tax_benefit_model_version=pe.uk.model, policy=reform ) -analysis = economic_impact_analysis(baseline_sim, reform_sim) +analysis = pe.uk.economic_impact_analysis(baseline_sim, reform_sim) ``` ## What `economic_impact_analysis()` computes diff --git a/docs/examples.md b/docs/examples.md index b7b4e91a..715d30bf 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -50,9 +50,9 @@ Same approach as the UK version, varying employment income from $0 to $200k and :language: python ``` -## Household impact calculation +## Household calculation -Using `calculate_household_impact()` to compute taxes and benefits for individual custom households (both UK and US). +Using `pe.uk.calculate_household()` and `pe.us.calculate_household()` to compute taxes and benefits for individual custom households with flat keyword arguments and dot-access result objects. ```{literalinclude} ../examples/household_impact_example.py :language: python diff --git a/docs/regions-and-scoping.md b/docs/regions-and-scoping.md index 9be4ddbc..2e1334e0 100644 --- a/docs/regions-and-scoping.md +++ b/docs/regions-and-scoping.md @@ -23,9 +23,9 @@ A `Region` represents a geographic area with a unique prefixed code: Each model version has a `RegionRegistry` providing O(1) lookups: ```python -from policyengine.tax_benefit_models.us import us_latest +import policyengine as pe -registry = us_latest.region_registry +registry = pe.us.model.region_registry # Look up by code california = registry.get("state/ca") @@ -43,9 +43,9 @@ ca_districts = registry.get_children("state/ca") ``` ```python -from policyengine.tax_benefit_models.uk import uk_latest +import policyengine as pe -registry = uk_latest.region_registry +registry = pe.uk.model.region_registry # UK countries countries = registry.get_by_type("country") @@ -74,7 +74,7 @@ from policyengine.core.scoping_strategy import RowFilterStrategy # Simulate only California households simulation = Simulation( dataset=dataset, - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, scoping_strategy=RowFilterStrategy( variable_name="state_code", variable_value="CA", @@ -89,7 +89,7 @@ This removes all non-California households from the dataset before running the s # UK: simulate only England simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, scoping_strategy=RowFilterStrategy( variable_name="country", variable_value="ENGLAND", @@ -106,7 +106,7 @@ from policyengine.core.scoping_strategy import WeightReplacementStrategy simulation = Simulation( dataset=dataset, - tax_benefit_model_version=uk_latest, + tax_benefit_model_version=pe.uk.model, scoping_strategy=WeightReplacementStrategy( weight_matrix_bucket="policyengine-uk-data", weight_matrix_key="parliamentary_constituency_weights.h5", @@ -119,29 +119,6 @@ simulation = Simulation( Unlike row filtering, weight replacement keeps all households but assigns region-specific weights. This is more statistically robust for small geographic areas where filtering would leave too few households. -### Legacy filter fields - -For backward compatibility, `Simulation` also accepts `filter_field` and `filter_value` parameters, which are auto-converted to a `RowFilterStrategy`: - -```python -# These two are equivalent: -simulation = Simulation( - dataset=dataset, - tax_benefit_model_version=us_latest, - filter_field="state_code", - filter_value="CA", -) - -simulation = Simulation( - dataset=dataset, - tax_benefit_model_version=us_latest, - scoping_strategy=RowFilterStrategy( - variable_name="state_code", - variable_value="CA", - ), -) -``` - ## Geographic impact outputs The package provides output types that compute per-region metrics across all regions simultaneously. @@ -230,7 +207,7 @@ from policyengine.core.scoping_strategy import RowFilterStrategy # State-level analysis baseline_sim = Simulation( dataset=dataset, - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, scoping_strategy=RowFilterStrategy( variable_name="state_code", variable_value="CA", @@ -238,7 +215,7 @@ baseline_sim = Simulation( ) reform_sim = Simulation( dataset=dataset, - tax_benefit_model_version=us_latest, + tax_benefit_model_version=pe.us.model, policy=reform, scoping_strategy=RowFilterStrategy( variable_name="state_code", diff --git a/examples/household_impact_example.py b/examples/household_impact_example.py index f2902daf..4b96cd96 100644 --- a/examples/household_impact_example.py +++ b/examples/household_impact_example.py @@ -1,46 +1,37 @@ -"""Example: Calculate household tax and benefit impacts. +"""Example: calculate tax and benefit outcomes for custom households. -This script demonstrates using calculate_household_impact for both UK and US -to compute taxes and benefits for custom households. +Demonstrates the v4 :func:`policyengine.us.calculate_household` and +:func:`policyengine.uk.calculate_household` entry points. Both take flat +keyword arguments, accept reform dicts directly, and return a +:class:`~policyengine.tax_benefit_models.common.HouseholdResult` that +supports dot-access for scalar lookups. -Run: python examples/household_impact_example.py +Run: ``python examples/household_impact_example.py`` """ -from policyengine.tax_benefit_models.uk import ( - UKHouseholdInput, -) -from policyengine.tax_benefit_models.uk import ( - calculate_household_impact as calculate_uk_impact, -) -from policyengine.tax_benefit_models.us import ( - USHouseholdInput, -) -from policyengine.tax_benefit_models.us import ( - calculate_household_impact as calculate_us_impact, -) - - -def uk_example(): - """UK household impact example.""" +from __future__ import annotations + +import policyengine as pe + + +def uk_example() -> None: print("=" * 60) - print("UK HOUSEHOLD IMPACT") + print("UK household calculator") print("=" * 60) - # Single adult earning £50,000 - household = UKHouseholdInput( + # Single adult earning £50,000. + single = pe.uk.calculate_household( people=[{"age": 35, "employment_income": 50_000}], year=2026, ) - result = calculate_uk_impact(household) - print("\nSingle adult, £50k income:") - print(f" Net income: £{result.household['hbai_household_net_income']:,.0f}") - print(f" Income tax: £{result.person[0]['income_tax']:,.0f}") - print(f" National Insurance: £{result.person[0]['national_insurance']:,.0f}") - print(f" Total tax: £{result.household['household_tax']:,.0f}") + print(f" Net income: £{single.household.hbai_household_net_income:,.0f}") + print(f" Income tax: £{single.person[0].income_tax:,.0f}") + print(f" National Insurance: £{single.person[0].national_insurance:,.0f}") + print(f" Total tax: £{single.household.household_tax:,.0f}") - # Family with two children, £30k income, renting - household = UKHouseholdInput( + # Family with two children, £30k income, renting in the North West. + family = pe.uk.calculate_household( people=[ {"age": 35, "employment_income": 30_000}, {"age": 33}, @@ -57,59 +48,52 @@ def uk_example(): }, year=2026, ) - result = calculate_uk_impact(household) - print("\nFamily (2 adults, 2 children), £30k income, renting:") - print(f" Net income: £{result.household['hbai_household_net_income']:,.0f}") - print(f" Income tax: £{result.person[0]['income_tax']:,.0f}") - print(f" Child benefit: £{result.benunit[0]['child_benefit']:,.0f}") - print(f" Universal credit: £{result.benunit[0]['universal_credit']:,.0f}") - print(f" Total benefits: £{result.household['household_benefits']:,.0f}") + print(f" Net income: £{family.household.hbai_household_net_income:,.0f}") + print(f" Income tax: £{family.person[0].income_tax:,.0f}") + print(f" Child benefit: £{family.benunit.child_benefit:,.0f}") + print(f" Universal credit: £{family.benunit.universal_credit:,.0f}") + print(f" Total benefits: £{family.household.household_benefits:,.0f}") -def us_example(): - """US household impact example.""" +def us_example() -> None: print("\n" + "=" * 60) - print("US HOUSEHOLD IMPACT") + print("US household calculator") print("=" * 60) - # Single adult earning $50,000 - household = USHouseholdInput( - people=[{"age": 35, "employment_income": 50_000, "is_tax_unit_head": True}], + # Single adult earning $50,000 in California. + single = pe.us.calculate_household( + people=[{"age": 35, "employment_income": 50_000}], tax_unit={"filing_status": "SINGLE"}, household={"state_code_str": "CA"}, - year=2024, + year=2026, ) - result = calculate_us_impact(household) - print("\nSingle adult, $50k income (California):") - print(f" Net income: ${result.household['household_net_income']:,.0f}") - print(f" Income tax: ${result.tax_unit[0]['income_tax']:,.0f}") - print(f" Payroll tax: ${result.tax_unit[0]['employee_payroll_tax']:,.0f}") + print(f" Net income: ${single.household.household_net_income:,.0f}") + print(f" Income tax: ${single.tax_unit.income_tax:,.0f}") + print(f" Payroll tax: ${single.tax_unit.employee_payroll_tax:,.0f}") - # Married couple with children, lower income - household = USHouseholdInput( + # Married couple with two kids, Texas, lower income. + family = pe.us.calculate_household( people=[ - {"age": 35, "employment_income": 40_000, "is_tax_unit_head": True}, - {"age": 33, "is_tax_unit_spouse": True}, - {"age": 8, "is_tax_unit_dependent": True}, - {"age": 5, "is_tax_unit_dependent": True}, + {"age": 35, "employment_income": 40_000}, + {"age": 33}, + {"age": 8}, + {"age": 5}, ], tax_unit={"filing_status": "JOINT"}, household={"state_code_str": "TX"}, - year=2024, + year=2026, ) - result = calculate_us_impact(household) - print("\nMarried couple with 2 children, $40k income (Texas):") - print(f" Net income: ${result.household['household_net_income']:,.0f}") - print(f" Federal income tax: ${result.tax_unit[0]['income_tax']:,.0f}") - print(f" EITC: ${result.tax_unit[0]['eitc']:,.0f}") - print(f" Child tax credit: ${result.tax_unit[0]['ctc']:,.0f}") - print(f" SNAP: ${result.spm_unit[0]['snap']:,.0f}") + print(f" Net income: ${family.household.household_net_income:,.0f}") + print(f" Federal income tax: ${family.tax_unit.income_tax:,.0f}") + print(f" EITC: ${family.tax_unit.eitc:,.0f}") + print(f" Child tax credit: ${family.tax_unit.ctc:,.0f}") + print(f" SNAP: ${family.spm_unit.snap:,.0f}") -def main(): +def main() -> None: uk_example() us_example() print("\n" + "=" * 60)