From 35dad90b0389c70dcfd8977826350bcc945bddaf Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 24 Jul 2026 07:56:36 -0400 Subject: [PATCH 1/4] Repin the US local-area overlay to the buildo-acs-local release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dataset_overlays.us.populace_us_2024_acs_local moves from the stale Build L artifact (36de5d9a, 2026-07-12 — pre-truthful-CG/interest chains, old SSI machinery, no adult-care/SE-health inputs, plain-Microsimulation-unloadable) to populace-us-2024-buildo-acs-local-77e2061-20260724T110908Z: rebuilt on the certified Build O lineage (donor = the certified O-1 sparse release, base-O block-ladder geography preserved), 1.589M households, 4,461-target state surface at 92.83% within-10%, consumer-ready bytes (plain loads proven), published immutably under populace#398's non-default local-area contract with latest.json untouched (verified). Revision + sha256 only; the overlay-preserved / default-resolution-unchanged semantics tests are constant-driven and stay intact (44 passing). populace#512 carries the build receipts and the 10 declared boundaries (inherited populace#507 SSI aged-band collapse included; the release manifest's refresh_recipe makes the O-2 rerun one command when the fix certifies). Co-Authored-By: Claude Fable 5 --- docs/release-bundles.md | 2 +- src/policyengine/data/bundle/manifest.json | 4 ++-- tests/test_release_manifests.py | 4 ++-- uv.lock | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/release-bundles.md b/docs/release-bundles.md index 17841211..c7f40889 100644 --- a/docs/release-bundles.md +++ b/docs/release-bundles.md @@ -155,7 +155,7 @@ sibling `dataset_overlays.{country}` map: "populace_us_2024_acs_local": { "path": "populace_us_2024_acs_local.h5", "repo_id": "policyengine/populace-us", - "revision": "populace-us-2024-buildl-acs-local-...", + "revision": "populace-us-2024-buildo-acs-local-...", "sha256": "..." } } diff --git a/src/policyengine/data/bundle/manifest.json b/src/policyengine/data/bundle/manifest.json index d6e1489c..79e23f07 100644 --- a/src/policyengine/data/bundle/manifest.json +++ b/src/policyengine/data/bundle/manifest.json @@ -189,8 +189,8 @@ "populace_us_2024_acs_local": { "path": "populace_us_2024_acs_local.h5", "repo_id": "policyengine/populace-us", - "revision": "populace-us-2024-buildl-acs-local-36de5d9a-20260712T104640Z", - "sha256": "36de5d9aa69fa932cd42b1b9c08ef72b20176414118997de6240a588fab30a6b" + "revision": "populace-us-2024-buildo-acs-local-77e2061-20260724T110908Z", + "sha256": "71763290ded993789af0ad818fd833a6aabb9ca7ff7d14f78315340d8617c6f4" } } }, diff --git a/tests/test_release_manifests.py b/tests/test_release_manifests.py index cbe47f62..2a5eca46 100644 --- a/tests/test_release_manifests.py +++ b/tests/test_release_manifests.py @@ -60,9 +60,9 @@ # Non-default local-area overlay: a staged Populace US artifact published in its # own immutable release (Build L), loadable by name but never the default. US_LOCAL_AREA_DATASET = "populace_us_2024_acs_local" -US_LOCAL_AREA_RELEASE_ID = "populace-us-2024-buildl-acs-local-36de5d9a-20260712T104640Z" +US_LOCAL_AREA_RELEASE_ID = "populace-us-2024-buildo-acs-local-77e2061-20260724T110908Z" US_LOCAL_AREA_SHA256 = ( - "36de5d9aa69fa932cd42b1b9c08ef72b20176414118997de6240a588fab30a6b" + "71763290ded993789af0ad818fd833a6aabb9ca7ff7d14f78315340d8617c6f4" ) US_LOCAL_AREA_DATASET_URI = ( "hf://policyengine/populace-us/populace_us_2024_acs_local.h5" diff --git a/uv.lock b/uv.lock index 66bc83bb..0d43e277 100644 --- a/uv.lock +++ b/uv.lock @@ -2820,7 +2820,7 @@ wheels = [ [[package]] name = "policyengine" -version = "4.22.0" +version = "4.22.2" source = { editable = "." } dependencies = [ { name = "diskcache" }, From 9f1570921842fd6820b9b8c8195c7a3c77af6cfd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 24 Jul 2026 08:06:22 -0400 Subject: [PATCH 2/4] Format markdown code blocks with current ruff (CI lint drift) The Lint gate installs unpinned latest ruff, which now formats fenced code blocks in markdown; these 11 docs files carried pre-existing drift that fails 'ruff format --check .' for every code-path PR. Mechanical only. Co-Authored-By: Claude Fable 5 --- .claude/policyengine-guide.md | 212 ++++++++++++++++++---------------- .claude/quick-reference.md | 208 ++++++++++++++++++++------------- README.md | 4 +- docs/households.md | 10 +- docs/impact-analysis.md | 7 +- docs/microsim.md | 11 +- docs/outputs.md | 8 +- docs/reforms.md | 6 +- docs/regions.md | 14 ++- docs/release-bundles.md | 20 +++- docs/visualisation.md | 18 ++- 11 files changed, 297 insertions(+), 221 deletions(-) diff --git a/.claude/policyengine-guide.md b/.claude/policyengine-guide.md index 922c542c..e7523bd7 100644 --- a/.claude/policyengine-guide.md +++ b/.claude/policyengine-guide.md @@ -40,42 +40,48 @@ from microdf import MicroDataFrame from policyengine.tax_benefit_models.uk import ( PolicyEngineUKDataset, UKYearData, - uk_latest + uk_latest, ) from policyengine.core import Simulation # Create synthetic person data person_df = MicroDataFrame( - pd.DataFrame({ - "person_id": [0, 1, 2], - "person_household_id": [0, 0, 1], - "person_benunit_id": [0, 0, 1], - "age": [35, 8, 40], - "employment_income": [30000, 0, 50000], - "person_weight": [1.0, 1.0, 1.0], - }), - weights="person_weight" + pd.DataFrame( + { + "person_id": [0, 1, 2], + "person_household_id": [0, 0, 1], + "person_benunit_id": [0, 0, 1], + "age": [35, 8, 40], + "employment_income": [30000, 0, 50000], + "person_weight": [1.0, 1.0, 1.0], + } + ), + weights="person_weight", ) # Create household data household_df = MicroDataFrame( - pd.DataFrame({ - "household_id": [0, 1], - "region": ["LONDON", "SOUTH_EAST"], - "rent": [15000, 12000], - "household_weight": [1.0, 1.0], - }), - weights="household_weight" + pd.DataFrame( + { + "household_id": [0, 1], + "region": ["LONDON", "SOUTH_EAST"], + "rent": [15000, 12000], + "household_weight": [1.0, 1.0], + } + ), + weights="household_weight", ) # Create benunit data (UK only) benunit_df = MicroDataFrame( - pd.DataFrame({ - "benunit_id": [0, 1], - "would_claim_uc": [True, True], - "benunit_weight": [1.0, 1.0], - }), - weights="benunit_weight" + pd.DataFrame( + { + "benunit_id": [0, 1], + "would_claim_uc": [True, True], + "benunit_weight": [1.0, 1.0], + } + ), + weights="benunit_weight", ) # Package into dataset @@ -88,7 +94,7 @@ dataset = PolicyEngineUKDataset( person=person_df, household=household_df, benunit=benunit_df, - ) + ), ) # Run simulation @@ -109,23 +115,25 @@ print(output.household[["household_id", "household_net_income"]]) from policyengine.tax_benefit_models.us import ( PolicyEngineUSDataset, USYearData, - us_latest + us_latest, ) # Create person data (note: US has more entity types) person_df = MicroDataFrame( - pd.DataFrame({ - "person_id": [0, 1, 2, 3], - "person_household_id": [0, 0, 0, 0], - "person_tax_unit_id": [0, 0, 0, 0], - "person_spm_unit_id": [0, 0, 0, 0], - "person_family_id": [0, 0, 0, 0], - "person_marital_unit_id": [0, 0, 1, 2], - "age": [35, 33, 8, 5], - "employment_income": [60000, 40000, 0, 0], - "person_weight": [1.0, 1.0, 1.0, 1.0], - }), - weights="person_weight" + pd.DataFrame( + { + "person_id": [0, 1, 2, 3], + "person_household_id": [0, 0, 0, 0], + "person_tax_unit_id": [0, 0, 0, 0], + "person_spm_unit_id": [0, 0, 0, 0], + "person_family_id": [0, 0, 0, 0], + "person_marital_unit_id": [0, 0, 1, 2], + "age": [35, 33, 8, 5], + "employment_income": [60000, 40000, 0, 0], + "person_weight": [1.0, 1.0, 1.0, 1.0], + } + ), + weights="person_weight", ) # Create entity dataframes (tax_unit, spm_unit, family, marital_unit, household) @@ -142,7 +150,7 @@ dataset = PolicyEngineUSDataset( family=family_df, marital_unit=marital_unit_df, household=household_df, - ) + ), ) ``` @@ -159,26 +167,30 @@ income_values = np.linspace(0, 100000, n_scenarios) # Create person data with all scenarios person_df = MicroDataFrame( - pd.DataFrame({ - "person_id": range(n_scenarios), - "person_household_id": range(n_scenarios), - "person_benunit_id": range(n_scenarios), - "age": [35] * n_scenarios, - "employment_income": income_values, - "person_weight": [1.0] * n_scenarios, - }), - weights="person_weight" + pd.DataFrame( + { + "person_id": range(n_scenarios), + "person_household_id": range(n_scenarios), + "person_benunit_id": range(n_scenarios), + "age": [35] * n_scenarios, + "employment_income": income_values, + "person_weight": [1.0] * n_scenarios, + } + ), + weights="person_weight", ) # Create matching household/benunit data household_df = MicroDataFrame( - pd.DataFrame({ - "household_id": range(n_scenarios), - "region": ["LONDON"] * n_scenarios, - "rent": [15000] * n_scenarios, - "household_weight": [1.0] * n_scenarios, - }), - weights="household_weight" + pd.DataFrame( + { + "household_id": range(n_scenarios), + "region": ["LONDON"] * n_scenarios, + "rent": [15000] * n_scenarios, + "household_weight": [1.0] * n_scenarios, + } + ), + weights="household_weight", ) # ... create dataset and run simulation once for all scenarios @@ -229,10 +241,7 @@ reform_sim = Simulation( reform_sim.run() # Analyse impact -from policyengine.outputs.change_aggregate import ( - ChangeAggregate, - ChangeAggregateType -) +from policyengine.outputs.change_aggregate import ChangeAggregate, ChangeAggregateType winners = ChangeAggregate( baseline_simulation=baseline_sim, @@ -298,15 +307,12 @@ household_income = dataset.data.map_to_entity( source_entity="person", target_entity="household", columns=["employment_income"], - how="sum" + how="sum", ) # Map household rent to person level (broadcast) person_rent = dataset.data.map_to_entity( - source_entity="household", - target_entity="person", - columns=["rent"], - how="project" + source_entity="household", target_entity="person", columns=["rent"], how="project" ) # Split household savings equally per person @@ -314,17 +320,15 @@ person_savings_share = dataset.data.map_to_entity( source_entity="household", target_entity="person", columns=["total_savings"], - how="divide" + how="divide", ) # Map custom values import numpy as np + custom_values = np.array([100, 200, 150]) household_totals = dataset.data.map_to_entity( - source_entity="person", - target_entity="household", - values=custom_values, - how="sum" + source_entity="person", target_entity="household", values=custom_values, how="sum" ) ``` @@ -335,13 +339,15 @@ from policyengine.utils.plotting import format_fig, COLORS import plotly.graph_objects as go fig = go.Figure() -fig.add_trace(go.Scatter( - x=income_values, - y=net_income_values, - mode='lines', - name='Net income', - line=dict(color=COLORS["primary"], width=3) -)) +fig.add_trace( + go.Scatter( + x=income_values, + y=net_income_values, + mode="lines", + name="Net income", + line=dict(color=COLORS["primary"], width=3), + ) +) format_fig( fig, @@ -492,42 +498,50 @@ When user asks to: import pandas as pd from microdf import MicroDataFrame from policyengine.tax_benefit_models.uk import ( - PolicyEngineUKDataset, UKYearData, uk_latest + PolicyEngineUKDataset, + UKYearData, + uk_latest, ) from policyengine.core import Simulation # Create person data person_df = MicroDataFrame( - pd.DataFrame({ - "person_id": [0], - "person_household_id": [0], - "person_benunit_id": [0], - "age": [30], - "employment_income": [30000], - "person_weight": [1.0], - }), - weights="person_weight" + pd.DataFrame( + { + "person_id": [0], + "person_household_id": [0], + "person_benunit_id": [0], + "age": [30], + "employment_income": [30000], + "person_weight": [1.0], + } + ), + weights="person_weight", ) # Create household data household_df = MicroDataFrame( - pd.DataFrame({ - "household_id": [0], - "region": ["LONDON"], - "rent": [12000], # Typical London rent - "household_weight": [1.0], - }), - weights="household_weight" + pd.DataFrame( + { + "household_id": [0], + "region": ["LONDON"], + "rent": [12000], # Typical London rent + "household_weight": [1.0], + } + ), + weights="household_weight", ) # Create benunit data benunit_df = MicroDataFrame( - pd.DataFrame({ - "benunit_id": [0], - "would_claim_uc": [True], - "benunit_weight": [1.0], - }), - weights="benunit_weight" + pd.DataFrame( + { + "benunit_id": [0], + "would_claim_uc": [True], + "benunit_weight": [1.0], + } + ), + weights="benunit_weight", ) # Create and run simulation @@ -539,7 +553,7 @@ dataset = PolicyEngineUKDataset( person=person_df, household=household_df, benunit=benunit_df, - ) + ), ) simulation = Simulation( diff --git a/.claude/quick-reference.md b/.claude/quick-reference.md index d5b5c872..b8917cbe 100644 --- a/.claude/quick-reference.md +++ b/.claude/quick-reference.md @@ -10,14 +10,14 @@ from policyengine.core import Simulation, Policy, Parameter, ParameterValue from policyengine.tax_benefit_models.uk import ( PolicyEngineUKDataset, UKYearData, - uk_latest + uk_latest, ) # US from policyengine.tax_benefit_models.us import ( PolicyEngineUSDataset, USYearData, - us_latest + us_latest, ) # Outputs @@ -37,41 +37,58 @@ import numpy as np import pandas as pd from microdf import MicroDataFrame from policyengine.tax_benefit_models.uk import ( - PolicyEngineUKDataset, UKYearData, uk_latest + PolicyEngineUKDataset, + UKYearData, + uk_latest, ) from policyengine.core import Simulation # Person data -person_df = MicroDataFrame(pd.DataFrame({ - "person_id": [0], - "person_household_id": [0], - "person_benunit_id": [0], - "age": [30], - "employment_income": [30000], - "person_weight": [1.0], -}), weights="person_weight") +person_df = MicroDataFrame( + pd.DataFrame( + { + "person_id": [0], + "person_household_id": [0], + "person_benunit_id": [0], + "age": [30], + "employment_income": [30000], + "person_weight": [1.0], + } + ), + weights="person_weight", +) # Household data -household_df = MicroDataFrame(pd.DataFrame({ - "household_id": [0], - "region": ["LONDON"], - "rent": [12000], - "household_weight": [1.0], -}), weights="household_weight") +household_df = MicroDataFrame( + pd.DataFrame( + { + "household_id": [0], + "region": ["LONDON"], + "rent": [12000], + "household_weight": [1.0], + } + ), + weights="household_weight", +) # Benunit data -benunit_df = MicroDataFrame(pd.DataFrame({ - "benunit_id": [0], - "would_claim_uc": [True], - "benunit_weight": [1.0], -}), weights="benunit_weight") +benunit_df = MicroDataFrame( + pd.DataFrame( + { + "benunit_id": [0], + "would_claim_uc": [True], + "benunit_weight": [1.0], + } + ), + weights="benunit_weight", +) # Create dataset dataset = PolicyEngineUKDataset( name="Example", filepath="./temp.h5", year=2026, - data=UKYearData(person=person_df, household=household_df, benunit=benunit_df) + data=UKYearData(person=person_df, household=household_df, benunit=benunit_df), ) # Run simulation @@ -89,36 +106,53 @@ print(output.household[["household_net_income"]]) import pandas as pd from microdf import MicroDataFrame from policyengine.tax_benefit_models.us import ( - PolicyEngineUSDataset, USYearData, us_latest + PolicyEngineUSDataset, + USYearData, + us_latest, ) from policyengine.core import Simulation # Person data (US requires more entity links) -person_df = MicroDataFrame(pd.DataFrame({ - "person_id": [0, 1], - "person_household_id": [0, 0], - "person_tax_unit_id": [0, 0], - "person_spm_unit_id": [0, 0], - "person_family_id": [0, 0], - "person_marital_unit_id": [0, 0], - "age": [35, 33], - "employment_income": [60000, 40000], - "person_weight": [1.0, 1.0], -}), weights="person_weight") +person_df = MicroDataFrame( + pd.DataFrame( + { + "person_id": [0, 1], + "person_household_id": [0, 0], + "person_tax_unit_id": [0, 0], + "person_spm_unit_id": [0, 0], + "person_family_id": [0, 0], + "person_marital_unit_id": [0, 0], + "age": [35, 33], + "employment_income": [60000, 40000], + "person_weight": [1.0, 1.0], + } + ), + weights="person_weight", +) # Create minimal entity dataframes entities = {} for entity in ["tax_unit", "spm_unit", "family", "marital_unit"]: - entities[entity] = MicroDataFrame(pd.DataFrame({ - f"{entity}_id": [0], - f"{entity}_weight": [1.0], - }), weights=f"{entity}_weight") + entities[entity] = MicroDataFrame( + pd.DataFrame( + { + f"{entity}_id": [0], + f"{entity}_weight": [1.0], + } + ), + weights=f"{entity}_weight", + ) -household_df = MicroDataFrame(pd.DataFrame({ - "household_id": [0], - "state_code": ["CA"], - "household_weight": [1.0], -}), weights="household_weight") +household_df = MicroDataFrame( + pd.DataFrame( + { + "household_id": [0], + "state_code": ["CA"], + "household_weight": [1.0], + } + ), + weights="household_weight", +) # Create dataset dataset = PolicyEngineUSDataset( @@ -132,7 +166,7 @@ dataset = PolicyEngineUSDataset( family=entities["family"], marital_unit=entities["marital_unit"], household=household_df, - ) + ), ) # Run simulation @@ -150,14 +184,19 @@ print(sim.output_dataset.data.household[["household_net_income"]]) n = 50 incomes = np.linspace(0, 100000, n) -person_df = MicroDataFrame(pd.DataFrame({ - "person_id": range(n), - "person_household_id": range(n), - "person_benunit_id": range(n), - "age": [30] * n, - "employment_income": incomes, - "person_weight": [1.0] * n, -}), weights="person_weight") +person_df = MicroDataFrame( + pd.DataFrame( + { + "person_id": range(n), + "person_household_id": range(n), + "person_benunit_id": range(n), + "age": [30] * n, + "employment_income": incomes, + "person_weight": [1.0] * n, + } + ), + weights="person_weight", +) # Create matching household/benunit data with n rows # ... then run simulation once for all scenarios @@ -178,16 +217,20 @@ parameter = Parameter( policy = Policy( name="Reform", description="Change PA", - parameter_values=[ParameterValue( - parameter=parameter, - start_date=datetime.date(2026, 1, 1), - end_date=datetime.date(2026, 12, 31), - value=15000, - )] + parameter_values=[ + ParameterValue( + parameter=parameter, + start_date=datetime.date(2026, 1, 1), + end_date=datetime.date(2026, 12, 31), + value=15000, + ) + ], ) # Run with policy -reform_sim = Simulation(dataset=dataset, tax_benefit_model_version=uk_latest, policy=policy) +reform_sim = Simulation( + dataset=dataset, tax_benefit_model_version=uk_latest, policy=policy +) ``` ### Extract aggregate statistics @@ -253,15 +296,12 @@ household_income = dataset.data.map_to_entity( source_entity="person", target_entity="household", columns=["employment_income"], - how="sum" + how="sum", ) # Broadcast household rent to persons person_rent = dataset.data.map_to_entity( - source_entity="household", - target_entity="person", - columns=["rent"], - how="project" + source_entity="household", target_entity="person", columns=["rent"], how="project" ) # Divide household value equally per person @@ -269,15 +309,12 @@ per_person = dataset.data.map_to_entity( source_entity="household", target_entity="person", columns=["total_savings"], - how="divide" + how="divide", ) # Map custom values custom_totals = dataset.data.map_to_entity( - source_entity="person", - target_entity="household", - values=custom_array, - how="sum" + source_entity="person", target_entity="household", values=custom_array, how="sum" ) ``` @@ -295,9 +332,20 @@ custom_totals = dataset.data.map_to_entity( ## Common UK regions ```python -["LONDON", "SOUTH_EAST", "SOUTH_WEST", "EAST_OF_ENGLAND", - "WEST_MIDLANDS", "EAST_MIDLANDS", "YORKSHIRE", - "NORTH_WEST", "NORTH_EAST", "WALES", "SCOTLAND", "NORTHERN_IRELAND"] +[ + "LONDON", + "SOUTH_EAST", + "SOUTH_WEST", + "EAST_OF_ENGLAND", + "WEST_MIDLANDS", + "EAST_MIDLANDS", + "YORKSHIRE", + "NORTH_WEST", + "NORTH_EAST", + "WALES", + "SCOTLAND", + "NORTHERN_IRELAND", +] ``` ## Common US state codes @@ -308,19 +356,19 @@ custom_totals = dataset.data.map_to_entity( ## Aggregate filter options ```python # Exact match -filter_eq=value +filter_eq = value # Greater than/equal -filter_geq=value +filter_geq = value # Less than/equal -filter_leq=value +filter_leq = value # Quantile filtering (deciles) -quantile=10 # Split into 10 groups -quantile_eq=1 # First decile only -quantile_geq=9 # Top two deciles -quantile_leq=2 # Bottom two deciles +quantile = 10 # Split into 10 groups +quantile_eq = 1 # First decile only +quantile_geq = 9 # Top two deciles +quantile_leq = 2 # Bottom two deciles ``` ## Common parameters diff --git a/README.md b/README.md index 86481cbc..4f0d009f 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ 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 +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( diff --git a/docs/households.md b/docs/households.md index 29488727..bd6f8973 100644 --- a/docs/households.md +++ b/docs/households.md @@ -134,7 +134,7 @@ result = pe.us.calculate_household( ) result.person[0].charitable_cash_donations # [0, 5000, 10000] -result.tax_unit.income_tax # one value per axis point +result.tax_unit.income_tax # one value per axis point ``` When axes are present, result values are lists ordered by the axis grid instead @@ -144,10 +144,10 @@ each variable on that person is its own axis series. ## Accessing the result ```python -result.person[0].income_tax # first person -result.person[2].age # third person -result.tax_unit.income_tax # single tax unit -result.household.household_net_income # single household +result.person[0].income_tax # first person +result.person[2].age # third person +result.tax_unit.income_tax # single tax unit +result.household.household_net_income # single household ``` The result is a Pydantic model — `.model_dump()` gives you a dict, individual sections are regular attribute lookups. diff --git a/docs/impact-analysis.md b/docs/impact-analysis.md index 275dc14c..491ecac2 100644 --- a/docs/impact-analysis.md +++ b/docs/impact-analysis.md @@ -83,7 +83,8 @@ for amount in [0, 1_000, 2_000, 3_000]: ```python from policyengine.outputs import ( - ChangeAggregate, ChangeAggregateType, + ChangeAggregate, + ChangeAggregateType, calculate_decile_impacts, calculate_us_poverty_rates, calculate_us_inequality, @@ -97,7 +98,9 @@ budget = ChangeAggregate( ) budget.run() -deciles = calculate_decile_impacts(baseline_simulation=baseline, reform_simulation=reformed) +deciles = calculate_decile_impacts( + baseline_simulation=baseline, reform_simulation=reformed +) baseline_poverty = calculate_us_poverty_rates(simulation=baseline) reform_poverty = calculate_us_poverty_rates(simulation=reformed) diff --git a/docs/microsim.md b/docs/microsim.md index 1811459b..34fb376b 100644 --- a/docs/microsim.md +++ b/docs/microsim.md @@ -35,7 +35,7 @@ Microdata is stored as HDF5 on Hugging Face. `ensure_datasets` downloads, caches ```python datasets = pe.us.ensure_datasets( years=[2024, 2026], - data_folder="./data", # local cache directory + data_folder="./data", # local cache directory ) dataset = datasets["populace_us_2024_2026"] ``` @@ -45,7 +45,7 @@ The default US dataset is **Populace US 2024** — a Populace-built dataset cali List datasets already known to the country: ```python -pe.us.load_datasets() # or pe.uk.load_datasets() +pe.us.load_datasets() # or pe.uk.load_datasets() ``` ### US local-area dataset @@ -64,6 +64,7 @@ Two-line load: ```python import policyengine as pe + sim = pe.us.managed_microsimulation(dataset="populace_us_2024_acs_local") ``` @@ -173,8 +174,10 @@ Every output has the same lifecycle: instantiate with the simulation(s) and conf ```python from policyengine.outputs import ( - Aggregate, AggregateType, - ChangeAggregate, ChangeAggregateType, + Aggregate, + AggregateType, + ChangeAggregate, + ChangeAggregateType, ) snap_cost = Aggregate( diff --git a/docs/outputs.md b/docs/outputs.md index 2bab1cd1..2bd807e1 100644 --- a/docs/outputs.md +++ b/docs/outputs.md @@ -55,7 +55,7 @@ Aggregate( aggregate_type=AggregateType.MEAN, filter_variable="household_net_income", quantile=10, - quantile_eq=1, # bottom decile + quantile_eq=1, # bottom decile ) ``` @@ -86,7 +86,7 @@ winners = ChangeAggregate( reform_simulation=reform, variable="household_net_income", aggregate_type=ChangeAggregateType.COUNT, - change_geq=1, # households gaining at least $1 + change_geq=1, # households gaining at least $1 ) ``` @@ -118,7 +118,7 @@ wealth_deciles = calculate_decile_impacts( entity="household", ) -impacts.dataframe # includes the decile_variable column +impacts.dataframe # includes the decile_variable column ``` ## IntraDecileImpact @@ -165,7 +165,7 @@ For all canonical poverty measures over one simulation: from policyengine.outputs import calculate_us_poverty_rates rates = calculate_us_poverty_rates(simulation=baseline) -rates.outputs # Poverty entries for each measure +rates.outputs # Poverty entries for each measure rates.dataframe ``` diff --git a/docs/reforms.md b/docs/reforms.md index f66dffd0..cb66dbd6 100644 --- a/docs/reforms.md +++ b/docs/reforms.md @@ -49,8 +49,8 @@ Scale parameters (brackets with thresholds and amounts) are addressed by bracket ```python reform = { - "gov.irs.income.tax.rate[0]": 0.12, # first bracket rate - "gov.irs.income.tax.threshold[1]": 50_000, # second bracket threshold + "gov.irs.income.tax.rate[0]": 0.12, # first bracket rate + "gov.irs.income.tax.threshold[1]": 50_000, # second bracket threshold "gov.irs.credits.ctc.amount.base[0].amount": 3_000, } ``` @@ -87,10 +87,12 @@ For rule changes that can't be expressed as a parameter change — swapping a fo from policyengine_us import Microsimulation from policyengine_us.model_api import Reform + class NeutralizeEITC(Reform): def apply(self): self.neutralize_variable("eitc") + sim = Microsimulation(reform=NeutralizeEITC) ``` diff --git a/docs/regions.md b/docs/regions.md index f493dcf9..3bf64d73 100644 --- a/docs/regions.md +++ b/docs/regions.md @@ -119,12 +119,14 @@ reform.run() baseline_hh = baseline.output_dataset.data.household reform_hh = reform.output_dataset.data.household -df = pd.DataFrame({ - "baseline": baseline_hh["household_net_income"].values, - "reform": reform_hh["household_net_income"].values, - "geo": baseline_hh["custom_geography_id"].values, - "weight": baseline_hh["household_weight"].values, -}) +df = pd.DataFrame( + { + "baseline": baseline_hh["household_net_income"].values, + "reform": reform_hh["household_net_income"].values, + "geo": baseline_hh["custom_geography_id"].values, + "weight": baseline_hh["household_weight"].values, + } +) df["change"] = df["reform"] - df["baseline"] df.groupby("geo").apply(lambda g: (g["change"] * g["weight"]).sum() / g["weight"].sum()) diff --git a/docs/release-bundles.md b/docs/release-bundles.md index c7f40889..a362630e 100644 --- a/docs/release-bundles.md +++ b/docs/release-bundles.md @@ -339,8 +339,14 @@ That is a country-data concern and lives in those repos. From Python: ```python -from policyengine.provenance.manifest import get_data_release_manifest, get_release_manifest -from policyengine.provenance.trace import build_trace_tro_from_release_bundle, serialize_trace_tro +from policyengine.provenance.manifest import ( + get_data_release_manifest, + get_release_manifest, +) +from policyengine.provenance.trace import ( + build_trace_tro_from_release_bundle, + serialize_trace_tro, +) country = get_release_manifest("us") tro = build_trace_tro_from_release_bundle(country, get_data_release_manifest("us")) @@ -364,9 +370,9 @@ wheel ships with the matching TRO. from policyengine.results import write_results_with_trace_tro write_results_with_trace_tro( - results, # ResultsJson instance - "results.json", # where to write results - bundle_tro=bundle_tro, # loaded from the shipped bundle + results, # ResultsJson instance + "results.json", # where to write results + bundle_tro=bundle_tro, # loaded from the shipped bundle reform_payload={"salt_cap": 0}, bundle_tro_url=( "https://raw.githubusercontent.com/PolicyEngine/policyengine.py/" @@ -421,7 +427,9 @@ assert bundle_hash == recorded, "bundle_tro_url content does not match sim TRO" # 3. Confirm the fingerprint recorded on the performance matches the # fingerprint inside the fetched bundle. bundle = json.loads(bundle_bytes) -bundle_fp = bundle["@graph"][0]["trov:hasComposition"]["trov:hasFingerprint"]["trov:sha256"] +bundle_fp = bundle["@graph"][0]["trov:hasComposition"]["trov:hasFingerprint"][ + "trov:sha256" +] assert perf["pe:bundleFingerprint"] == bundle_fp ``` diff --git a/docs/visualisation.md b/docs/visualisation.md index 662ec3b1..092a1e41 100644 --- a/docs/visualisation.md +++ b/docs/visualisation.md @@ -21,7 +21,7 @@ format_fig( xaxis_title="X axis", yaxis_title="Y axis", height=600, - width=800 + width=800, ) fig.show() @@ -36,11 +36,7 @@ The formatting applies these principles automatically: ```python from policyengine.utils import COLORS -fig.add_trace(go.Scatter( - x=x_data, - y=y_data, - line=dict(color=COLORS["primary"]) -)) +fig.add_trace(go.Scatter(x=x_data, y=y_data, line=dict(color=COLORS["primary"]))) ``` **Typography**: Inter font family with appropriate sizing (12px for labels, 14px for body text, 16px for titles). @@ -53,13 +49,13 @@ fig.add_trace(go.Scatter( ```python COLORS = { - "primary": "#319795", # Teal (main brand colour) + "primary": "#319795", # Teal (main brand colour) "primary_light": "#E6FFFA", "primary_dark": "#1D4044", - "success": "#22C55E", # Green (positive changes) - "warning": "#FEC601", # Yellow (cautions) - "error": "#EF4444", # Red (negative changes) - "info": "#1890FF", # Blue (neutral information) + "success": "#22C55E", # Green (positive changes) + "warning": "#FEC601", # Yellow (cautions) + "error": "#EF4444", # Red (negative changes) + "info": "#1890FF", # Blue (neutral information) "gray_light": "#F2F4F7", "gray": "#667085", "gray_dark": "#101828", From 34e134dadd40ea6c69175f01d7c52bea8a091433 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 24 Jul 2026 08:06:22 -0400 Subject: [PATCH 3/4] Add the towncrier fragment for the overlay repin Co-Authored-By: Claude Fable 5 --- changelog.d/488.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/488.changed.md diff --git a/changelog.d/488.changed.md b/changelog.d/488.changed.md new file mode 100644 index 00000000..83885074 --- /dev/null +++ b/changelog.d/488.changed.md @@ -0,0 +1 @@ +Repin the US local-area overlay (`populace_us_2024_acs_local`) to the buildo-acs-local release built on the certified Build O lineage: consumer-loadable bytes, 4,461-target local surface, immutable tag, default resolution unchanged. From adf60bd28a4057e8ed6a1feb25d3406c14f62a45 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 24 Jul 2026 08:40:25 -0400 Subject: [PATCH 4/4] Rebind the TRACE TRO sidecars to the repinned bundle manifest scripts/generate_trace_tros.py regeneration; the sidecars bind sha256(manifest.json), which the overlay repin changed. Co-Authored-By: Claude Fable 5 --- src/policyengine/data/bundle/uk.trace.tro.jsonld | 4 ++-- src/policyengine/data/bundle/us.trace.tro.jsonld | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/policyengine/data/bundle/uk.trace.tro.jsonld b/src/policyengine/data/bundle/uk.trace.tro.jsonld index 23b7aa8c..3c3d45fb 100644 --- a/src/policyengine/data/bundle/uk.trace.tro.jsonld +++ b/src/policyengine/data/bundle/uk.trace.tro.jsonld @@ -75,7 +75,7 @@ "@type": "trov:ResearchArtifact", "schema:name": "policyengine.py bundle manifest for uk", "trov:mimeType": "application/json", - "trov:sha256": "5360e5b6e9df7be09b4921342029d47ef6108424be8b8ec4c98995ae6a1e87ab" + "trov:sha256": "0b32b8338bf7ebad6ebde3033820e938ce4616536dfb791b0139e04b2788b928" }, { "@id": "composition/1/artifact/data_release_manifest", @@ -102,7 +102,7 @@ "trov:hasFingerprint": { "@id": "composition/1/fingerprint", "@type": "trov:CompositionFingerprint", - "trov:sha256": "34e37a030cee2ddcb22f66d4aee90c8882061e9925078f478e9a925e0c9f1766" + "trov:sha256": "5de95462556c78c65223c49eacc17003767648b3d30b734fa7f121ce0fcc9194" } }, "trov:hasPerformance": { diff --git a/src/policyengine/data/bundle/us.trace.tro.jsonld b/src/policyengine/data/bundle/us.trace.tro.jsonld index b83f9c8d..0f802c71 100644 --- a/src/policyengine/data/bundle/us.trace.tro.jsonld +++ b/src/policyengine/data/bundle/us.trace.tro.jsonld @@ -75,7 +75,7 @@ "@type": "trov:ResearchArtifact", "schema:name": "policyengine.py bundle manifest for us", "trov:mimeType": "application/json", - "trov:sha256": "5360e5b6e9df7be09b4921342029d47ef6108424be8b8ec4c98995ae6a1e87ab" + "trov:sha256": "0b32b8338bf7ebad6ebde3033820e938ce4616536dfb791b0139e04b2788b928" }, { "@id": "composition/1/artifact/data_release_manifest", @@ -102,7 +102,7 @@ "trov:hasFingerprint": { "@id": "composition/1/fingerprint", "@type": "trov:CompositionFingerprint", - "trov:sha256": "92bc787cb0db73721429dd4bc4353018581a7722580c6b7f9c88bb8634d7206a" + "trov:sha256": "4945767cbe533e7ea127de33b97b2b4aeb1458f1c56d78b3393cce93a34531b5" } }, "trov:hasPerformance": {