From 97aa93d06117244d1432448dc0ea6acff57d58db Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 5 Mar 2026 21:27:26 -0500 Subject: [PATCH] Switch from black to ruff format Replace black with ruff format for code formatting. Update pyproject.toml to remove black dependency and [tool.black] section, bump ruff to >=0.9.0, set line-length to 88. Add ruff format --check to CI workflows. Co-Authored-By: Claude Opus 4.6 --- .github/bump_version.py | 4 +- .github/workflows/code_changes.yaml | 3 + .github/workflows/pr_code_changes.yaml | 3 + changelog.d/switch-to-ruff.changed.md | 1 + examples/employment_income_variation_uk.py | 25 ++----- examples/employment_income_variation_us.py | 30 ++------ examples/household_impact_example.py | 16 +--- examples/income_distribution_us.py | 20 ++--- examples/policy_change_uk.py | 16 +--- examples/speedtest_us_simulation.py | 75 ++++++------------- pyproject.toml | 23 +----- src/policyengine/core/dataset.py | 70 +++++------------ src/policyengine/core/parameter.py | 8 +- src/policyengine/core/region.py | 12 +-- .../core/tax_benefit_model_version.py | 8 +- src/policyengine/countries/uk/regions.py | 18 +---- src/policyengine/outputs/aggregate.py | 34 +++------ src/policyengine/outputs/change_aggregate.py | 34 +++------ src/policyengine/outputs/decile_impact.py | 10 +-- src/policyengine/outputs/inequality.py | 34 +++------ .../outputs/intra_decile_impact.py | 4 +- src/policyengine/outputs/poverty.py | 26 +++---- .../tax_benefit_models/uk/analysis.py | 16 +--- .../tax_benefit_models/uk/datasets.py | 32 ++------ .../tax_benefit_models/uk/model.py | 33 ++------ .../tax_benefit_models/us/analysis.py | 20 ++--- .../tax_benefit_models/us/datasets.py | 44 +++-------- .../tax_benefit_models/us/model.py | 35 +++------ src/policyengine/utils/dates.py | 4 +- src/policyengine/utils/entity_utils.py | 8 +- src/policyengine/utils/parametric_reforms.py | 16 +--- src/policyengine/utils/plotting.py | 4 +- tests/fixtures/filtering_fixtures.py | 8 +- .../poverty_by_demographics_fixtures.py | 4 +- tests/fixtures/region_fixtures.py | 8 +- tests/test_constituency_impact.py | 21 +++--- tests/test_entity_mapping.py | 44 +++-------- tests/test_entity_utils.py | 12 +-- tests/test_filtering.py | 8 +- tests/test_household_impact.py | 8 +- tests/test_intra_decile_impact.py | 10 ++- tests/test_local_authority_impact.py | 16 +--- tests/test_models.py | 6 +- tests/test_parameter_labels.py | 21 ++---- tests/test_parametric_reforms.py | 5 +- tests/test_poverty.py | 14 +--- tests/test_poverty_by_demographics.py | 36 +++------ tests/test_region.py | 28 ++----- tests/test_uk_regions.py | 5 +- tests/test_us_reform_application.py | 1 - tests/test_us_regions.py | 13 +--- uv.lock | 59 +++++---------- 52 files changed, 275 insertions(+), 738 deletions(-) create mode 100644 changelog.d/switch-to-ruff.changed.md diff --git a/.github/bump_version.py b/.github/bump_version.py index bb0fd6dd..779a82e3 100644 --- a/.github/bump_version.py +++ b/.github/bump_version.py @@ -19,9 +19,7 @@ def get_current_version(pyproject_path: Path) -> str: def infer_bump(changelog_dir: Path) -> str: fragments = [ - f - for f in changelog_dir.iterdir() - if f.is_file() and f.name != ".gitkeep" + f for f in changelog_dir.iterdir() if f.is_file() and f.name != ".gitkeep" ] if not fragments: print("No changelog fragments found", file=sys.stderr) diff --git a/.github/workflows/code_changes.yaml b/.github/workflows/code_changes.yaml index 486efd99..c18b527c 100644 --- a/.github/workflows/code_changes.yaml +++ b/.github/workflows/code_changes.yaml @@ -21,6 +21,9 @@ jobs: - name: Install ruff run: pip install ruff + - name: Run ruff format check + run: ruff format --check . + - name: Run ruff check run: ruff check . Test: diff --git a/.github/workflows/pr_code_changes.yaml b/.github/workflows/pr_code_changes.yaml index d323667e..8ca3092b 100644 --- a/.github/workflows/pr_code_changes.yaml +++ b/.github/workflows/pr_code_changes.yaml @@ -18,6 +18,9 @@ jobs: - name: Install ruff run: pip install ruff + - name: Run ruff format check + run: ruff format --check . + - name: Run ruff check run: ruff check . Test: diff --git a/changelog.d/switch-to-ruff.changed.md b/changelog.d/switch-to-ruff.changed.md new file mode 100644 index 00000000..3e176424 --- /dev/null +++ b/changelog.d/switch-to-ruff.changed.md @@ -0,0 +1 @@ +Switched code formatter from black to ruff format. diff --git a/examples/employment_income_variation_uk.py b/examples/employment_income_variation_uk.py index 22bcb93c..a20b5d6d 100644 --- a/examples/employment_income_variation_uk.py +++ b/examples/employment_income_variation_uk.py @@ -125,17 +125,12 @@ def create_dataset_with_varied_employment_income( "region": ["LONDON"] * n_households, # Required by policyengine-uk "council_tax": [0.0] * n_households, # Simplified - no council tax "rent": [median_annual_rent] * n_households, # Median UK rent - "tenure_type": ["RENT_PRIVATELY"] - * n_households, # Required for uprating + "tenure_type": ["RENT_PRIVATELY"] * n_households, # Required for uprating } # Create MicroDataFrames - person_df = MicroDataFrame( - pd.DataFrame(person_data), weights="person_weight" - ) - benunit_df = MicroDataFrame( - pd.DataFrame(benunit_data), weights="benunit_weight" - ) + person_df = MicroDataFrame(pd.DataFrame(person_data), weights="person_weight") + benunit_df = MicroDataFrame(pd.DataFrame(benunit_data), weights="benunit_weight") household_df = MicroDataFrame( pd.DataFrame(household_data), weights="household_weight" ) @@ -273,9 +268,7 @@ def visualise_results(results: dict) -> None: # Calculate net employment income (employment income minus tax) net_employment = [ emp - tax - for emp, tax in zip( - results["employment_income_hh"], results["household_tax"] - ) + for emp, tax in zip(results["employment_income_hh"], results["household_tax"]) ] # Stack benefits and income components using PolicyEngine colors @@ -339,17 +332,11 @@ def main(): simulation = run_simulation(dataset) print("Extracting results using aggregate filters...") - results = extract_results_by_employment_income( - simulation, employment_incomes - ) + results = extract_results_by_employment_income(simulation, employment_incomes) print("\nSample results:") for emp_inc in [0, 25000, 50000, 100000]: - idx = ( - employment_incomes.index(emp_inc) - if emp_inc in employment_incomes - else -1 - ) + idx = employment_incomes.index(emp_inc) if emp_inc in employment_incomes else -1 if idx >= 0: print( f" Employment income £{emp_inc:,}: HBAI net income £{results['hbai_household_net_income'][idx]:,.0f}" diff --git a/examples/employment_income_variation_us.py b/examples/employment_income_variation_us.py index f4ceb80e..9ac2891f 100644 --- a/examples/employment_income_variation_us.py +++ b/examples/employment_income_variation_us.py @@ -127,24 +127,16 @@ def create_dataset_with_varied_employment_income( } # Create MicroDataFrames - person_df = MicroDataFrame( - pd.DataFrame(person_data), weights="person_weight" - ) + person_df = MicroDataFrame(pd.DataFrame(person_data), weights="person_weight") household_df = MicroDataFrame( pd.DataFrame(household_data), weights="household_weight" ) marital_unit_df = MicroDataFrame( pd.DataFrame(marital_unit_data), weights="marital_unit_weight" ) - family_df = MicroDataFrame( - pd.DataFrame(family_data), weights="family_weight" - ) - spm_unit_df = MicroDataFrame( - pd.DataFrame(spm_unit_data), weights="spm_unit_weight" - ) - tax_unit_df = MicroDataFrame( - pd.DataFrame(tax_unit_data), weights="tax_unit_weight" - ) + family_df = MicroDataFrame(pd.DataFrame(family_data), weights="family_weight") + spm_unit_df = MicroDataFrame(pd.DataFrame(spm_unit_data), weights="spm_unit_weight") + tax_unit_df = MicroDataFrame(pd.DataFrame(tax_unit_data), weights="tax_unit_weight") # Create temporary file tmpdir = tempfile.mkdtemp() @@ -227,9 +219,7 @@ def visualise_results(results: dict) -> None: # Calculate net employment income (employment income minus tax) net_employment = [ emp - tax - for emp, tax in zip( - results["employment_income_hh"], results["household_tax"] - ) + for emp, tax in zip(results["employment_income_hh"], results["household_tax"]) ] # Stack benefits and income components using PolicyEngine colors @@ -287,17 +277,11 @@ def main(): simulation = run_simulation(dataset) print("Extracting results using aggregate filters...") - results = extract_results_by_employment_income( - simulation, employment_incomes - ) + results = extract_results_by_employment_income(simulation, employment_incomes) print("\nSample results:") for emp_inc in [0, 50000, 100000, 200000]: - idx = ( - employment_incomes.index(emp_inc) - if emp_inc in employment_incomes - else -1 - ) + idx = employment_incomes.index(emp_inc) if emp_inc in employment_incomes else -1 if idx >= 0: print( f" Employment income ${emp_inc:,}: household net income ${results['household_net_income'][idx]:,.0f}" diff --git a/examples/household_impact_example.py b/examples/household_impact_example.py index 3474447b..f2902daf 100644 --- a/examples/household_impact_example.py +++ b/examples/household_impact_example.py @@ -34,13 +34,9 @@ def uk_example(): result = calculate_uk_impact(household) print("\nSingle adult, £50k income:") - print( - f" Net income: £{result.household['hbai_household_net_income']:,.0f}" - ) + 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" National Insurance: £{result.person[0]['national_insurance']:,.0f}") print(f" Total tax: £{result.household['household_tax']:,.0f}") # Family with two children, £30k income, renting @@ -64,9 +60,7 @@ def uk_example(): 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" 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}") @@ -81,9 +75,7 @@ def us_example(): # Single adult earning $50,000 household = USHouseholdInput( - people=[ - {"age": 35, "employment_income": 50_000, "is_tax_unit_head": True} - ], + people=[{"age": 35, "employment_income": 50_000, "is_tax_unit_head": True}], tax_unit={"filing_status": "SINGLE"}, household={"state_code_str": "CA"}, year=2024, diff --git a/examples/income_distribution_us.py b/examples/income_distribution_us.py index 67417e13..659a2d5b 100644 --- a/examples/income_distribution_us.py +++ b/examples/income_distribution_us.py @@ -26,9 +26,7 @@ def load_representative_data(year: int = 2024) -> PolicyEngineUSDataset: """Load representative household microdata for a given year.""" - dataset_path = ( - Path(__file__).parent / "data" / f"enhanced_cps_2024_year_{year}.h5" - ) + dataset_path = Path(__file__).parent / "data" / f"enhanced_cps_2024_year_{year}.h5" if not dataset_path.exists(): raise FileNotFoundError( @@ -83,15 +81,11 @@ def calculate_income_decile_statistics(simulation: Simulation) -> dict: quantile_eq=decile_num, ) if decile_num == 1: - print( - f" First Aggregate created ({time.time() - pre_create:.2f}s)" - ) + print(f" First Aggregate created ({time.time() - pre_create:.2f}s)") pre_run = time.time() agg.run() if decile_num == 1: - print( - f" First Aggregate.run() complete ({time.time() - pre_run:.2f}s)" - ) + print(f" First Aggregate.run() complete ({time.time() - pre_run:.2f}s)") market_incomes.append(agg.result / 1e9) agg = Aggregate( @@ -234,9 +228,7 @@ def calculate_income_decile_statistics(simulation: Simulation) -> dict: print(f" {prog} complete ({time.time() - prog_start:.2f}s)") print(f"Tax benefits complete ({time.time() - tax_benefits_start:.2f}s)") - print( - f"\nTotal statistics calculation time: {time.time() - start_time:.2f}s" - ) + print(f"\nTotal statistics calculation time: {time.time() - start_time:.2f}s") return { "deciles": deciles, @@ -392,9 +384,7 @@ def main(): print(f"Total benefits: ${total_benefits:.1f}bn") print(f"Total net income: ${total_net_income:.1f}bn") print(f"Total households: {total_households:.1f}m") - print( - f"Average effective tax rate: {total_tax / total_market_income * 100:.1f}%" - ) + print(f"Average effective tax rate: {total_tax / total_market_income * 100:.1f}%") print("\nBenefit programs by decile:") benefit_programs = [ diff --git a/examples/policy_change_uk.py b/examples/policy_change_uk.py index d708448b..00162d49 100644 --- a/examples/policy_change_uk.py +++ b/examples/policy_change_uk.py @@ -82,9 +82,7 @@ def run_baseline_simulation(dataset: PolicyEngineUKDataset) -> Simulation: return simulation -def run_reform_simulation( - dataset: PolicyEngineUKDataset, policy: Policy -) -> Simulation: +def run_reform_simulation(dataset: PolicyEngineUKDataset, policy: Policy) -> Simulation: """Run reform microsimulation with policy changes.""" simulation = Simulation( dataset=dataset, @@ -95,9 +93,7 @@ def run_reform_simulation( return simulation -def analyse_overall_impact( - baseline_sim: Simulation, reform_sim: Simulation -) -> dict: +def analyse_overall_impact(baseline_sim: Simulation, reform_sim: Simulation) -> dict: """Analyse overall winners, losers, and financial impact.""" winners = ChangeAggregate( baseline_simulation=baseline_sim, @@ -198,9 +194,7 @@ def analyse_impact_by_income_decile( } -def visualise_results( - overall: dict, by_decile: dict, reform_name: str -) -> None: +def visualise_results(overall: dict, by_decile: dict, reform_name: str) -> None: """Create visualisations of policy change impacts.""" fig = make_subplots( rows=1, @@ -270,9 +264,7 @@ def print_summary(overall: dict, decile: dict, reform_name: str) -> None: print(f" Losers: {overall['losers']:.2f}m households") print(f" No change: {overall['no_change']:.2f}m households") print("\nFinancial impact:") - print( - f" Net income change: £{overall['total_change']:.2f}bn (negative = loss)" - ) + print(f" Net income change: £{overall['total_change']:.2f}bn (negative = loss)") print(f" Tax revenue change: £{overall['tax_revenue_change']:.2f}bn") print("\nImpact by income decile:") for i, label in enumerate(decile["labels"]): diff --git a/examples/speedtest_us_simulation.py b/examples/speedtest_us_simulation.py index e0b18fb0..ce75f513 100644 --- a/examples/speedtest_us_simulation.py +++ b/examples/speedtest_us_simulation.py @@ -48,9 +48,7 @@ def create_subset_dataset( else "marital_unit_id" ) family_id_col = ( - "person_family_id" - if "person_family_id" in person_df.columns - else "family_id" + "person_family_id" if "person_family_id" in person_df.columns else "family_id" ) spm_unit_id_col = ( "person_spm_unit_id" @@ -69,9 +67,7 @@ def create_subset_dataset( ].copy() # Get IDs of group entities that have members in sampled households - sampled_marital_unit_ids = set( - sampled_person[marital_unit_id_col].unique() - ) + sampled_marital_unit_ids = set(sampled_person[marital_unit_id_col].unique()) sampled_family_ids = set(sampled_person[family_id_col].unique()) sampled_spm_unit_ids = set(sampled_person[spm_unit_id_col].unique()) sampled_tax_unit_ids = set(sampled_person[tax_unit_id_col].unique()) @@ -80,9 +76,7 @@ def create_subset_dataset( sampled_marital_unit = marital_unit_df[ marital_unit_df["marital_unit_id"].isin(sampled_marital_unit_ids) ].copy() - sampled_family = family_df[ - family_df["family_id"].isin(sampled_family_ids) - ].copy() + sampled_family = family_df[family_df["family_id"].isin(sampled_family_ids)].copy() sampled_spm_unit = spm_unit_df[ spm_unit_df["spm_unit_id"].isin(sampled_spm_unit_ids) ].copy() @@ -92,24 +86,19 @@ def create_subset_dataset( # Create ID mappings to reindex to contiguous integers starting from 0 household_id_map = { - old_id: new_id - for new_id, old_id in enumerate(sorted(sampled_household_ids)) + old_id: new_id for new_id, old_id in enumerate(sorted(sampled_household_ids)) } marital_unit_id_map = { - old_id: new_id - for new_id, old_id in enumerate(sorted(sampled_marital_unit_ids)) + old_id: new_id for new_id, old_id in enumerate(sorted(sampled_marital_unit_ids)) } family_id_map = { - old_id: new_id - for new_id, old_id in enumerate(sorted(sampled_family_ids)) + old_id: new_id for new_id, old_id in enumerate(sorted(sampled_family_ids)) } spm_unit_id_map = { - old_id: new_id - for new_id, old_id in enumerate(sorted(sampled_spm_unit_ids)) + old_id: new_id for new_id, old_id in enumerate(sorted(sampled_spm_unit_ids)) } tax_unit_id_map = { - old_id: new_id - for new_id, old_id in enumerate(sorted(sampled_tax_unit_ids)) + old_id: new_id for new_id, old_id in enumerate(sorted(sampled_tax_unit_ids)) } person_id_map = { old_id: new_id @@ -117,23 +106,19 @@ def create_subset_dataset( } # Reindex all entity IDs in household table - sampled_households["household_id"] = sampled_households[ - "household_id" - ].map(household_id_map) + sampled_households["household_id"] = sampled_households["household_id"].map( + household_id_map + ) # Reindex all entity IDs in person table - sampled_person["person_id"] = sampled_person["person_id"].map( - person_id_map - ) + sampled_person["person_id"] = sampled_person["person_id"].map(person_id_map) sampled_person[household_id_col] = sampled_person[household_id_col].map( household_id_map ) - sampled_person[marital_unit_id_col] = sampled_person[ - marital_unit_id_col - ].map(marital_unit_id_map) - sampled_person[family_id_col] = sampled_person[family_id_col].map( - family_id_map + sampled_person[marital_unit_id_col] = sampled_person[marital_unit_id_col].map( + marital_unit_id_map ) + sampled_person[family_id_col] = sampled_person[family_id_col].map(family_id_map) sampled_person[spm_unit_id_col] = sampled_person[spm_unit_id_col].map( spm_unit_id_map ) @@ -145,9 +130,7 @@ def create_subset_dataset( sampled_marital_unit["marital_unit_id"] = sampled_marital_unit[ "marital_unit_id" ].map(marital_unit_id_map) - sampled_family["family_id"] = sampled_family["family_id"].map( - family_id_map - ) + sampled_family["family_id"] = sampled_family["family_id"].map(family_id_map) sampled_spm_unit["spm_unit_id"] = sampled_spm_unit["spm_unit_id"].map( spm_unit_id_map ) @@ -156,18 +139,14 @@ def create_subset_dataset( ) # Sort by ID to ensure proper ordering - sampled_households = sampled_households.sort_values( - "household_id" - ).reset_index(drop=True) - sampled_person = sampled_person.sort_values("person_id").reset_index( + sampled_households = sampled_households.sort_values("household_id").reset_index( drop=True ) + sampled_person = sampled_person.sort_values("person_id").reset_index(drop=True) sampled_marital_unit = sampled_marital_unit.sort_values( "marital_unit_id" ).reset_index(drop=True) - sampled_family = sampled_family.sort_values("family_id").reset_index( - drop=True - ) + sampled_family = sampled_family.sort_values("family_id").reset_index(drop=True) sampled_spm_unit = sampled_spm_unit.sort_values("spm_unit_id").reset_index( drop=True ) @@ -183,19 +162,13 @@ def create_subset_dataset( year=original_dataset.year, data=USYearData( person=MicroDataFrame(sampled_person, weights="person_weight"), - household=MicroDataFrame( - sampled_households, weights="household_weight" - ), + household=MicroDataFrame(sampled_households, weights="household_weight"), marital_unit=MicroDataFrame( sampled_marital_unit, weights="marital_unit_weight" ), family=MicroDataFrame(sampled_family, weights="family_weight"), - spm_unit=MicroDataFrame( - sampled_spm_unit, weights="spm_unit_weight" - ), - tax_unit=MicroDataFrame( - sampled_tax_unit, weights="tax_unit_weight" - ), + spm_unit=MicroDataFrame(sampled_spm_unit, weights="spm_unit_weight"), + tax_unit=MicroDataFrame(sampled_tax_unit, weights="tax_unit_weight"), ), ) @@ -218,9 +191,7 @@ def speedtest_simulation(dataset: PolicyEngineUSDataset) -> float: def main(): print("Loading full enhanced CPS dataset...") - dataset_path = ( - Path(__file__).parent / "data" / "enhanced_cps_2024_year_2024.h5" - ) + dataset_path = Path(__file__).parent / "data" / "enhanced_cps_2024_year_2024.h5" if not dataset_path.exists(): raise FileNotFoundError( diff --git a/pyproject.toml b/pyproject.toml index 4406601a..fadcc314 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ us = [ "policyengine-us>=1.213.1", ] dev = [ - "black", "pytest", "furo", "autodoc_pydantic", @@ -44,7 +43,7 @@ dev = [ "itables", "build", "pytest-asyncio>=0.26.0", - "ruff>=0.5.0", + "ruff>=0.9.0", "policyengine_core>=3.23.6", "policyengine-uk>=2.51.0", "policyengine-us>=1.213.1", "towncrier>=24.8.0", @@ -70,26 +69,8 @@ filterwarnings = [ "ignore::pydantic.warnings.PydanticDeprecatedSince20", ] -[tool.black] -line-length = 79 -target-version = ['py311'] -include = '\.pyi?$' -extend-exclude = ''' -/( - # directories - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | build - | dist -)/ -''' - [tool.ruff] -line-length = 79 +line-length = 88 target-version = "py313" extend-exclude = ["*.ipynb"] diff --git a/src/policyengine/core/dataset.py b/src/policyengine/core/dataset.py index 4261d98a..f10a5d22 100644 --- a/src/policyengine/core/dataset.py +++ b/src/policyengine/core/dataset.py @@ -20,9 +20,7 @@ def entity_data(self) -> dict[str, MicroDataFrame]: This should be implemented by subclasses to return the appropriate entities. """ - raise NotImplementedError( - "Subclasses must implement entity_data property" - ) + raise NotImplementedError("Subclasses must implement entity_data property") @property def person_entity(self) -> str: @@ -184,17 +182,13 @@ def map_to_entity( # Check for both naming patterns: "entity_id" and "person_entity_id" person_target_key = f"{person_entity}_{target_entity}_id" join_key = ( - person_target_key - if person_target_key in source_df.columns - else target_key + person_target_key if person_target_key in source_df.columns else target_key ) if join_key in source_df.columns: # Get columns to aggregate (exclude ID and weight columns) id_cols = {col for col in source_df.columns if col.endswith("_id")} - weight_cols = { - col for col in source_df.columns if col.endswith("_weight") - } + weight_cols = {col for col in source_df.columns if col.endswith("_weight")} agg_cols = [ c for c in source_df.columns @@ -203,9 +197,7 @@ def map_to_entity( # Group by join key and aggregate if how == "sum": - aggregated = source_df.groupby(join_key, as_index=False)[ - agg_cols - ].sum() + aggregated = source_df.groupby(join_key, as_index=False)[agg_cols].sum() elif how == "first": aggregated = source_df.groupby(join_key, as_index=False)[ agg_cols @@ -224,9 +216,7 @@ def map_to_entity( # Sort back to original order result = ( - result.sort_values("index") - .drop("index", axis=1) - .reset_index(drop=True) + result.sort_values("index").drop("index", axis=1).reset_index(drop=True) ) # Fill NaN with 0 for groups with no members in source entity @@ -249,9 +239,7 @@ def map_to_entity( target_pd = pd.DataFrame(target_df) join_key = ( - person_source_key - if person_source_key in target_pd.columns - else source_key + person_source_key if person_source_key in target_pd.columns else source_key ) if join_key in target_pd.columns: @@ -264,12 +252,8 @@ def map_to_entity( # Handle divide operation if how == "divide": # Get columns to divide (exclude ID and weight columns) - id_cols = { - col for col in result.columns if col.endswith("_id") - } - weight_cols = { - col for col in result.columns if col.endswith("_weight") - } + id_cols = {col for col in result.columns if col.endswith("_id")} + weight_cols = {col for col in result.columns if col.endswith("_weight")} value_cols = [ c for c in result.columns @@ -311,14 +295,10 @@ def map_to_entity( # Determine which keys exist in person table source_link_key = ( - person_source_key - if person_source_key in person_df.columns - else source_key + person_source_key if person_source_key in person_df.columns else source_key ) target_link_key = ( - person_target_key - if person_target_key in person_df.columns - else target_key + person_target_key if person_target_key in person_df.columns else target_key ) # Link source -> person -> target @@ -332,10 +312,7 @@ def map_to_entity( # Rename source key to match link key if needed source_df_copy = source_df.copy() - if ( - source_link_key != source_key - and source_key in source_df_copy.columns - ): + if source_link_key != source_key and source_key in source_df_copy.columns: source_df_copy = source_df_copy.rename( columns={source_key: source_link_key} ) @@ -346,15 +323,9 @@ def map_to_entity( ) # Aggregate to target level - id_cols = { - col - for col in source_with_target.columns - if col.endswith("_id") - } + id_cols = {col for col in source_with_target.columns if col.endswith("_id")} weight_cols = { - col - for col in source_with_target.columns - if col.endswith("_weight") + col for col in source_with_target.columns if col.endswith("_weight") } agg_cols = [ c @@ -389,8 +360,7 @@ def map_to_entity( # Divide values by source group count (per-person share) for col in agg_cols: source_with_target[col] = ( - source_with_target[col] - / source_with_target["__source_count"] + source_with_target[col] / source_with_target["__source_count"] ) # Now aggregate (sum of per-person shares) to target level @@ -402,9 +372,7 @@ def map_to_entity( # Rename target link key to target key if needed if target_link_key != target_key: - aggregated = aggregated.rename( - columns={target_link_key: target_key} - ) + aggregated = aggregated.rename(columns={target_link_key: target_key}) # Merge with target, preserving original order target_pd = pd.DataFrame(target_df)[[target_key, target_weight]] @@ -413,9 +381,7 @@ def map_to_entity( # Sort back to original order result = ( - result.sort_values("index") - .drop("index", axis=1) - .reset_index(drop=True) + result.sort_values("index").drop("index", axis=1).reset_index(drop=True) ) # Fill NaN with 0 @@ -426,6 +392,4 @@ def map_to_entity( return result_df["__mapped_value"] return result_df - raise ValueError( - f"Unsupported mapping from {source_entity} to {target_entity}" - ) + raise ValueError(f"Unsupported mapping from {source_entity} to {target_entity}") diff --git a/src/policyengine/core/parameter.py b/src/policyengine/core/parameter.py index 684e5803..cd5a2c88 100644 --- a/src/policyengine/core/parameter.py +++ b/src/policyengine/core/parameter.py @@ -23,9 +23,7 @@ class Parameter(BaseModel): # Lazy loading: store core param ref, build values on demand _core_param: Any = PrivateAttr(default=None) - _parameter_values: list["ParameterValue"] | None = PrivateAttr( - default=None - ) + _parameter_values: list["ParameterValue"] | None = PrivateAttr(default=None) def __init__(self, _core_param: Any = None, **data): super().__init__(**data) @@ -48,9 +46,7 @@ def parameter_values(self) -> list["ParameterValue"]: next_instant = None pv = ParameterValue( parameter=self, - start_date=parse_safe_date( - param_at_instant.instant_str - ), + start_date=parse_safe_date(param_at_instant.instant_str), end_date=parse_safe_date(next_instant.instant_str) if next_instant else None, diff --git a/src/policyengine/core/region.py b/src/policyengine/core/region.py index 36cbc71e..9da86260 100644 --- a/src/policyengine/core/region.py +++ b/src/policyengine/core/region.py @@ -12,9 +12,7 @@ # Region type literals for US and UK USRegionType = Literal["national", "state", "congressional_district", "place"] -UKRegionType = Literal[ - "national", "country", "constituency", "local_authority" -] +UKRegionType = Literal["national", "country", "constituency", "local_authority"] RegionType = USRegionType | UKRegionType @@ -40,9 +38,7 @@ class Region(BaseModel): ..., description="Unique region code with type prefix (e.g., 'state/ca', 'place/NJ-57000')", ) - label: str = Field( - ..., description="Human-readable label (e.g., 'California')" - ) + label: str = Field(..., description="Human-readable label (e.g., 'California')") region_type: RegionType = Field( ..., description="Type of region (e.g., 'state', 'place')" ) @@ -100,9 +96,7 @@ class RegionRegistry(BaseModel): Indices are rebuilt automatically after initialization. """ - country_id: str = Field( - ..., description="Country identifier (e.g., 'us', 'uk')" - ) + country_id: str = Field(..., description="Country identifier (e.g., 'us', 'uk')") regions: list[Region] = Field(default_factory=list) # Private indexed lookups (excluded from serialization) diff --git a/src/policyengine/core/tax_benefit_model_version.py b/src/policyengine/core/tax_benefit_model_version.py index e74f82c1..af37172c 100644 --- a/src/policyengine/core/tax_benefit_model_version.py +++ b/src/policyengine/core/tax_benefit_model_version.py @@ -35,15 +35,11 @@ class TaxBenefitModelVersion(BaseModel): def parameter_values(self) -> list["ParameterValue"]: """Aggregate all parameter values from all parameters.""" return [ - pv - for parameter in self.parameters - for pv in parameter.parameter_values + pv for parameter in self.parameters for pv in parameter.parameter_values ] # Lookup dicts for O(1) access (excluded from serialization) - variables_by_name: dict[str, "Variable"] = Field( - default_factory=dict, exclude=True - ) + variables_by_name: dict[str, "Variable"] = Field(default_factory=dict, exclude=True) parameters_by_name: dict[str, "Parameter"] = Field( default_factory=dict, exclude=True ) diff --git a/src/policyengine/countries/uk/regions.py b/src/policyengine/countries/uk/regions.py index 1a379933..fa5bac40 100644 --- a/src/policyengine/countries/uk/regions.py +++ b/src/policyengine/countries/uk/regions.py @@ -55,17 +55,12 @@ def _load_constituencies_from_csv() -> list[dict]: import pandas as pd df = pd.read_csv(csv_path) - return [ - {"code": row["code"], "name": row["name"]} - for _, row in df.iterrows() - ] + return [{"code": row["code"], "name": row["name"]} for _, row in df.iterrows()] except (OSError, KeyError, ValueError) as exc: logger.warning("Failed to load constituencies CSV: %s", exc) return [] except Exception: - logger.error( - "Unexpected error loading constituencies CSV", exc_info=True - ) + logger.error("Unexpected error loading constituencies CSV", exc_info=True) return [] @@ -92,17 +87,12 @@ def _load_local_authorities_from_csv() -> list[dict]: import pandas as pd df = pd.read_csv(csv_path) - return [ - {"code": row["code"], "name": row["name"]} - for _, row in df.iterrows() - ] + return [{"code": row["code"], "name": row["name"]} for _, row in df.iterrows()] except (OSError, KeyError, ValueError) as exc: logger.warning("Failed to load local authorities CSV: %s", exc) return [] except Exception: - logger.error( - "Unexpected error loading local authorities CSV", exc_info=True - ) + logger.error("Unexpected error loading local authorities CSV", exc_info=True) return [] diff --git a/src/policyengine/outputs/aggregate.py b/src/policyengine/outputs/aggregate.py index cf4dbca4..9406a4d7 100644 --- a/src/policyengine/outputs/aggregate.py +++ b/src/policyengine/outputs/aggregate.py @@ -27,12 +27,8 @@ class Aggregate(Output): None # Number of quantiles (e.g., 10 for deciles, 5 for quintiles) ) quantile_eq: int | None = None # Exact quantile (e.g., 3 for 3rd decile) - quantile_leq: int | None = ( - None # Maximum quantile (e.g., 5 for bottom 5 deciles) - ) - quantile_geq: int | None = ( - None # Minimum quantile (e.g., 9 for top 2 deciles) - ) + quantile_leq: int | None = None # Maximum quantile (e.g., 5 for bottom 5 deciles) + quantile_geq: int | None = None # Minimum quantile (e.g., 9 for top 2 deciles) result: Any | None = None @@ -42,16 +38,12 @@ def run(self): self.filter_variable_describes_quantiles = True if self.quantile_eq is not None: # For a specific quantile, filter between (quantile-1)/n and quantile/n - self.filter_variable_geq = ( - self.quantile_eq - 1 - ) / self.quantile + self.filter_variable_geq = (self.quantile_eq - 1) / self.quantile self.filter_variable_leq = self.quantile_eq / self.quantile elif self.quantile_leq is not None: self.filter_variable_leq = self.quantile_leq / self.quantile elif self.quantile_geq is not None: - self.filter_variable_geq = ( - self.quantile_geq - 1 - ) / self.quantile + self.filter_variable_geq = (self.quantile_geq - 1) / self.quantile # Get variable object var_obj = next( @@ -82,12 +74,10 @@ def run(self): ) if filter_var_obj.entity != target_entity: - filter_mapped = ( - self.simulation.output_dataset.data.map_to_entity( - filter_var_obj.entity, - target_entity, - columns=[self.filter_variable], - ) + filter_mapped = self.simulation.output_dataset.data.map_to_entity( + filter_var_obj.entity, + target_entity, + columns=[self.filter_variable], ) filter_series = filter_mapped[self.filter_variable] else: @@ -98,14 +88,10 @@ def run(self): threshold = filter_series.quantile(self.filter_variable_eq) series = series[filter_series <= threshold] if self.filter_variable_leq is not None: - threshold = filter_series.quantile( - self.filter_variable_leq - ) + threshold = filter_series.quantile(self.filter_variable_leq) series = series[filter_series <= threshold] if self.filter_variable_geq is not None: - threshold = filter_series.quantile( - self.filter_variable_geq - ) + threshold = filter_series.quantile(self.filter_variable_geq) series = series[filter_series >= threshold] else: if self.filter_variable_eq is not None: diff --git a/src/policyengine/outputs/change_aggregate.py b/src/policyengine/outputs/change_aggregate.py index 29fe5069..e1cd3985 100644 --- a/src/policyengine/outputs/change_aggregate.py +++ b/src/policyengine/outputs/change_aggregate.py @@ -39,12 +39,8 @@ class ChangeAggregate(Output): None # Number of quantiles (e.g., 10 for deciles, 5 for quintiles) ) quantile_eq: int | None = None # Exact quantile (e.g., 3 for 3rd decile) - quantile_leq: int | None = ( - None # Maximum quantile (e.g., 5 for bottom 5 deciles) - ) - quantile_geq: int | None = ( - None # Minimum quantile (e.g., 9 for top 2 deciles) - ) + quantile_leq: int | None = None # Maximum quantile (e.g., 5 for bottom 5 deciles) + quantile_geq: int | None = None # Minimum quantile (e.g., 9 for top 2 deciles) result: Any | None = None @@ -54,16 +50,12 @@ def run(self): self.filter_variable_describes_quantiles = True if self.quantile_eq is not None: # For a specific quantile, filter between (quantile-1)/n and quantile/n - self.filter_variable_geq = ( - self.quantile_eq - 1 - ) / self.quantile + self.filter_variable_geq = (self.quantile_eq - 1) / self.quantile self.filter_variable_leq = self.quantile_eq / self.quantile elif self.quantile_leq is not None: self.filter_variable_leq = self.quantile_leq / self.quantile elif self.quantile_geq is not None: - self.filter_variable_geq = ( - self.quantile_geq - 1 - ) / self.quantile + self.filter_variable_geq = (self.quantile_geq - 1) / self.quantile # Get variable object var_obj = next( @@ -77,9 +69,7 @@ def run(self): baseline_data = getattr( self.baseline_simulation.output_dataset.data, target_entity ) - reform_data = getattr( - self.reform_simulation.output_dataset.data, target_entity - ) + reform_data = getattr(self.reform_simulation.output_dataset.data, target_entity) # Map variable to target entity if needed if var_obj.entity != target_entity: @@ -90,10 +80,8 @@ def run(self): ) baseline_series = baseline_mapped[self.variable] - reform_mapped = ( - self.reform_simulation.output_dataset.data.map_to_entity( - var_obj.entity, target_entity - ) + reform_mapped = self.reform_simulation.output_dataset.data.map_to_entity( + var_obj.entity, target_entity ) reform_series = reform_mapped[self.variable] else: @@ -155,14 +143,10 @@ def run(self): threshold = filter_series.quantile(self.filter_variable_eq) mask &= filter_series <= threshold if self.filter_variable_leq is not None: - threshold = filter_series.quantile( - self.filter_variable_leq - ) + threshold = filter_series.quantile(self.filter_variable_leq) mask &= filter_series <= threshold if self.filter_variable_geq is not None: - threshold = filter_series.quantile( - self.filter_variable_geq - ) + threshold = filter_series.quantile(self.filter_variable_geq) mask &= filter_series >= threshold else: if self.filter_variable_eq is not None: diff --git a/src/policyengine/outputs/decile_impact.py b/src/policyengine/outputs/decile_impact.py index d767e0ff..9d5e2e43 100644 --- a/src/policyengine/outputs/decile_impact.py +++ b/src/policyengine/outputs/decile_impact.py @@ -46,9 +46,7 @@ def run(self): baseline_data = getattr( self.baseline_simulation.output_dataset.data, target_entity ) - reform_data = getattr( - self.reform_simulation.output_dataset.data, target_entity - ) + reform_data = getattr(self.reform_simulation.output_dataset.data, target_entity) # Map income variable to target entity if needed if var_obj.entity != target_entity: @@ -59,10 +57,8 @@ def run(self): ) baseline_income = baseline_mapped[self.income_variable] - reform_mapped = ( - self.reform_simulation.output_dataset.data.map_to_entity( - var_obj.entity, target_entity - ) + reform_mapped = self.reform_simulation.output_dataset.data.map_to_entity( + var_obj.entity, target_entity ) reform_income = reform_mapped[self.income_variable] else: diff --git a/src/policyengine/outputs/inequality.py b/src/policyengine/outputs/inequality.py index e17e704a..582428e2 100644 --- a/src/policyengine/outputs/inequality.py +++ b/src/policyengine/outputs/inequality.py @@ -77,10 +77,8 @@ class Inequality(Output): def run(self): """Calculate inequality metrics.""" # Get income variable info - income_var_obj = ( - self.simulation.tax_benefit_model_version.get_variable( - self.income_variable - ) + income_var_obj = self.simulation.tax_benefit_model_version.get_variable( + self.income_variable ) # Get target entity data @@ -107,19 +105,15 @@ def run(self): # Apply demographic filter if specified if self.filter_variable is not None: - filter_var_obj = ( - self.simulation.tax_benefit_model_version.get_variable( - self.filter_variable - ) + filter_var_obj = self.simulation.tax_benefit_model_version.get_variable( + self.filter_variable ) if filter_var_obj.entity != target_entity: - filter_mapped = ( - self.simulation.output_dataset.data.map_to_entity( - filter_var_obj.entity, - target_entity, - columns=[self.filter_variable], - ) + filter_mapped = self.simulation.output_dataset.data.map_to_entity( + filter_var_obj.entity, + target_entity, + columns=[self.filter_variable], ) filter_series = filter_mapped[self.filter_variable] else: @@ -168,19 +162,14 @@ def run(self): # Top 10% share top_10_mask = weight_fractions > 0.9 self.top_10_share = float( - np.sum( - sorted_values[top_10_mask] - * sorted_weights[top_10_mask] - ) + np.sum(sorted_values[top_10_mask] * sorted_weights[top_10_mask]) / total_income ) # Top 1% share top_1_mask = weight_fractions > 0.99 self.top_1_share = float( - np.sum( - sorted_values[top_1_mask] * sorted_weights[top_1_mask] - ) + np.sum(sorted_values[top_1_mask] * sorted_weights[top_1_mask]) / total_income ) @@ -188,8 +177,7 @@ def run(self): bottom_50_mask = weight_fractions <= 0.5 self.bottom_50_share = float( np.sum( - sorted_values[bottom_50_mask] - * sorted_weights[bottom_50_mask] + sorted_values[bottom_50_mask] * sorted_weights[bottom_50_mask] ) / total_income ) diff --git a/src/policyengine/outputs/intra_decile_impact.py b/src/policyengine/outputs/intra_decile_impact.py index 52ab692b..e2b01243 100644 --- a/src/policyengine/outputs/intra_decile_impact.py +++ b/src/policyengine/outputs/intra_decile_impact.py @@ -58,9 +58,7 @@ def run(self): baseline_data = getattr( self.baseline_simulation.output_dataset.data, self.entity ) - reform_data = getattr( - self.reform_simulation.output_dataset.data, self.entity - ) + reform_data = getattr(self.reform_simulation.output_dataset.data, self.entity) baseline_income = baseline_data[self.income_variable].values reform_income = reform_data[self.income_variable].values diff --git a/src/policyengine/outputs/poverty.py b/src/policyengine/outputs/poverty.py index bd9b6595..10db4682 100644 --- a/src/policyengine/outputs/poverty.py +++ b/src/policyengine/outputs/poverty.py @@ -71,10 +71,8 @@ class Poverty(Output): def run(self): """Calculate poverty headcount and rate.""" # Get poverty variable info - poverty_var_obj = ( - self.simulation.tax_benefit_model_version.get_variable( - self.poverty_variable - ) + poverty_var_obj = self.simulation.tax_benefit_model_version.get_variable( + self.poverty_variable ) # Get target entity data @@ -94,19 +92,15 @@ def run(self): # Apply demographic filter if specified if self.filter_variable is not None: - filter_var_obj = ( - self.simulation.tax_benefit_model_version.get_variable( - self.filter_variable - ) + filter_var_obj = self.simulation.tax_benefit_model_version.get_variable( + self.filter_variable ) if filter_var_obj.entity != target_entity: - filter_mapped = ( - self.simulation.output_dataset.data.map_to_entity( - filter_var_obj.entity, - target_entity, - columns=[self.filter_variable], - ) + filter_mapped = self.simulation.output_dataset.data.map_to_entity( + filter_var_obj.entity, + target_entity, + columns=[self.filter_variable], ) filter_series = filter_mapped[self.filter_variable] else: @@ -128,9 +122,7 @@ def run(self): self.headcount = float((poverty_series == True).sum()) # noqa: E712 self.total_population = float(poverty_series.count()) self.rate = ( - self.headcount / self.total_population - if self.total_population > 0 - else 0.0 + self.headcount / self.total_population if self.total_population > 0 else 0.0 ) diff --git a/src/policyengine/tax_benefit_models/uk/analysis.py b/src/policyengine/tax_benefit_models/uk/analysis.py index f329dbfc..c4b32016 100644 --- a/src/policyengine/tax_benefit_models/uk/analysis.py +++ b/src/policyengine/tax_benefit_models/uk/analysis.py @@ -28,9 +28,7 @@ from .outputs import ProgrammeStatistics -def _create_entity_output_model( - entity: str, variables: list[str] -) -> type[BaseModel]: +def _create_entity_output_model(entity: str, variables: list[str]) -> type[BaseModel]: """Create a dynamic Pydantic model for entity output variables.""" fields = {var: (float, ...) for var in variables} return create_model(f"{entity.title()}Output", **fields) @@ -83,9 +81,7 @@ def calculate_household_impact( for i, person in enumerate(household_input.people): for key, value in person.items(): if key not in person_data: - person_data[key] = [ - 0.0 - ] * n_people # Default to 0 for numeric fields + person_data[key] = [0.0] * n_people # Default to 0 for numeric fields person_data[key][i] = value # Build benunit data with defaults @@ -109,12 +105,8 @@ def calculate_household_impact( household_data[key] = [value] # Create MicroDataFrames - person_df = MicroDataFrame( - pd.DataFrame(person_data), weights="person_weight" - ) - benunit_df = MicroDataFrame( - pd.DataFrame(benunit_data), weights="benunit_weight" - ) + person_df = MicroDataFrame(pd.DataFrame(person_data), weights="person_weight") + benunit_df = MicroDataFrame(pd.DataFrame(benunit_data), weights="benunit_weight") household_df = MicroDataFrame( pd.DataFrame(household_data), weights="household_weight" ) diff --git a/src/policyengine/tax_benefit_models/uk/datasets.py b/src/policyengine/tax_benefit_models/uk/datasets.py index 5642f8ff..442e45a5 100644 --- a/src/policyengine/tax_benefit_models/uk/datasets.py +++ b/src/policyengine/tax_benefit_models/uk/datasets.py @@ -77,12 +77,8 @@ def load(self) -> None: filepath = self.filepath with pd.HDFStore(filepath, mode="r") as store: self.data = UKYearData( - person=MicroDataFrame( - store["person"], weights="person_weight" - ), - benunit=MicroDataFrame( - store["benunit"], weights="benunit_weight" - ), + person=MicroDataFrame(store["person"], weights="person_weight"), + benunit=MicroDataFrame(store["benunit"], weights="benunit_weight"), household=MicroDataFrame( store["household"], weights="household_weight" ), @@ -126,9 +122,7 @@ def create_datasets( right_on="household_id", how="left", ) - person_df = person_df.rename( - columns={"household_weight": "person_weight"} - ) + person_df = person_df.rename(columns={"household_weight": "person_weight"}) person_df = person_df.drop(columns=["household_id"]) # Get household_id for each benunit from person table @@ -167,12 +161,8 @@ def create_datasets( year=int(year), data=UKYearData( person=MicroDataFrame(person_df, weights="person_weight"), - benunit=MicroDataFrame( - benunit_df, weights="benunit_weight" - ), - household=MicroDataFrame( - household_df, weights="household_weight" - ), + benunit=MicroDataFrame(benunit_df, weights="benunit_weight"), + household=MicroDataFrame(household_df, weights="household_weight"), ), ) uk_dataset.save() @@ -231,9 +221,7 @@ def ensure_datasets( all_exist = True for dataset in datasets: for year in years: - filepath = Path( - f"{data_folder}/{Path(dataset).stem}_year_{year}.h5" - ) + filepath = Path(f"{data_folder}/{Path(dataset).stem}_year_{year}.h5") if not filepath.exists(): all_exist = False break @@ -241,10 +229,6 @@ def ensure_datasets( break if all_exist: - return load_datasets( - datasets=datasets, years=years, data_folder=data_folder - ) + return load_datasets(datasets=datasets, years=years, data_folder=data_folder) else: - return create_datasets( - datasets=datasets, years=years, data_folder=data_folder - ) + return create_datasets(datasets=datasets, years=years, data_folder=data_folder) diff --git a/src/policyengine/tax_benefit_models/uk/model.py b/src/policyengine/tax_benefit_models/uk/model.py index 04860e58..231f6c73 100644 --- a/src/policyengine/tax_benefit_models/uk/model.py +++ b/src/policyengine/tax_benefit_models/uk/model.py @@ -53,9 +53,7 @@ def _get_uk_package_metadata(): data = response.json() upload_time = data["releases"][pkg_version][0]["upload_time_iso_8601"] except (requests.RequestException, KeyError, IndexError) as exc: - _logger.warning( - "Could not fetch PyPI metadata for policyengine-uk: %s", exc - ) + _logger.warning("Could not fetch PyPI metadata for policyengine-uk: %s", exc) upload_time = None return pkg_version, upload_time @@ -147,9 +145,7 @@ def __init__(self, **kwargs: dict): pkg_version, upload_time = _get_uk_package_metadata() kwargs["version"] = pkg_version if upload_time is not None: - kwargs["created_at"] = datetime.datetime.fromisoformat( - upload_time - ) + kwargs["created_at"] = datetime.datetime.fromisoformat(upload_time) super().__init__(**kwargs) from policyengine_core.enums import Enum @@ -176,9 +172,7 @@ def __init__(self, **kwargs: dict): tax_benefit_model_version=self, entity=var_obj.entity.key, description=var_obj.documentation, - data_type=var_obj.value_type - if var_obj.value_type is not Enum - else str, + data_type=var_obj.value_type if var_obj.value_type is not Enum else str, default_value=default_val, value_type=var_obj.value_type, ) @@ -275,10 +269,7 @@ def run(self, simulation: "Simulation") -> "Simulation": ) microsim = Microsimulation(dataset=input_data) - if ( - simulation.policy - and simulation.policy.simulation_modifier is not None - ): + if simulation.policy and simulation.policy.simulation_modifier is not None: simulation.policy.simulation_modifier(microsim) elif simulation.policy: modifier = simulation_modifier_from_parameter_values( @@ -286,10 +277,7 @@ def run(self, simulation: "Simulation") -> "Simulation": ) modifier(microsim) - if ( - simulation.dynamic - and simulation.dynamic.simulation_modifier is not None - ): + if simulation.dynamic and simulation.dynamic.simulation_modifier is not None: simulation.dynamic.simulation_modifier(microsim) elif simulation.dynamic: modifier = simulation_modifier_from_parameter_values( @@ -309,12 +297,8 @@ def run(self, simulation: "Simulation") -> "Simulation": var, period=simulation.dataset.year, map_to=entity ).values - data["person"] = MicroDataFrame( - data["person"], weights="person_weight" - ) - data["benunit"] = MicroDataFrame( - data["benunit"], weights="benunit_weight" - ) + data["person"] = MicroDataFrame(data["person"], weights="person_weight") + data["benunit"] = MicroDataFrame(data["benunit"], weights="benunit_weight") data["household"] = MicroDataFrame( data["household"], weights="household_weight" ) @@ -324,8 +308,7 @@ def run(self, simulation: "Simulation") -> "Simulation": name=dataset.name, description=dataset.description, filepath=str( - Path(simulation.dataset.filepath).parent - / (simulation.id + ".h5") + Path(simulation.dataset.filepath).parent / (simulation.id + ".h5") ), year=simulation.dataset.year, is_output_dataset=True, diff --git a/src/policyengine/tax_benefit_models/us/analysis.py b/src/policyengine/tax_benefit_models/us/analysis.py index fad0a5b1..4b14a93f 100644 --- a/src/policyengine/tax_benefit_models/us/analysis.py +++ b/src/policyengine/tax_benefit_models/us/analysis.py @@ -72,9 +72,7 @@ def calculate_household_impact( for i, person in enumerate(household_input.people): for key, value in person.items(): if key not in person_data: - person_data[key] = [ - 0.0 - ] * n_people # Default to 0 for numeric fields + person_data[key] = [0.0] * n_people # Default to 0 for numeric fields person_data[key][i] = value # Build entity data with defaults @@ -114,24 +112,16 @@ def calculate_household_impact( tax_unit_data[key] = [value] # Create MicroDataFrames - person_df = MicroDataFrame( - pd.DataFrame(person_data), weights="person_weight" - ) + person_df = MicroDataFrame(pd.DataFrame(person_data), weights="person_weight") household_df = MicroDataFrame( pd.DataFrame(household_data), weights="household_weight" ) marital_unit_df = MicroDataFrame( pd.DataFrame(marital_unit_data), weights="marital_unit_weight" ) - family_df = MicroDataFrame( - pd.DataFrame(family_data), weights="family_weight" - ) - spm_unit_df = MicroDataFrame( - pd.DataFrame(spm_unit_data), weights="spm_unit_weight" - ) - tax_unit_df = MicroDataFrame( - pd.DataFrame(tax_unit_data), weights="tax_unit_weight" - ) + family_df = MicroDataFrame(pd.DataFrame(family_data), weights="family_weight") + spm_unit_df = MicroDataFrame(pd.DataFrame(spm_unit_data), weights="spm_unit_weight") + tax_unit_df = MicroDataFrame(pd.DataFrame(tax_unit_data), weights="tax_unit_weight") # Create temporary dataset tmpdir = tempfile.mkdtemp() diff --git a/src/policyengine/tax_benefit_models/us/datasets.py b/src/policyengine/tax_benefit_models/us/datasets.py index 121fffcd..1bbf78f3 100644 --- a/src/policyengine/tax_benefit_models/us/datasets.py +++ b/src/policyengine/tax_benefit_models/us/datasets.py @@ -70,21 +70,13 @@ def load(self) -> None: filepath = self.filepath with pd.HDFStore(filepath, mode="r") as store: self.data = USYearData( - person=MicroDataFrame( - store["person"], weights="person_weight" - ), + person=MicroDataFrame(store["person"], weights="person_weight"), marital_unit=MicroDataFrame( store["marital_unit"], weights="marital_unit_weight" ), - family=MicroDataFrame( - store["family"], weights="family_weight" - ), - spm_unit=MicroDataFrame( - store["spm_unit"], weights="spm_unit_weight" - ), - tax_unit=MicroDataFrame( - store["tax_unit"], weights="tax_unit_weight" - ), + family=MicroDataFrame(store["family"], weights="family_weight"), + spm_unit=MicroDataFrame(store["spm_unit"], weights="spm_unit_weight"), + tax_unit=MicroDataFrame(store["tax_unit"], weights="tax_unit_weight"), household=MicroDataFrame( store["household"], weights="household_weight" ), @@ -241,9 +233,7 @@ def create_datasets( how="left", ) entity_df = entity_df.rename( - columns={ - "household_weight": f"{entity_name}_weight" - } + columns={"household_weight": f"{entity_name}_weight"} ) entity_df = entity_df.drop( columns=[ @@ -272,19 +262,13 @@ def create_datasets( year=int(year), data=USYearData( person=MicroDataFrame(person_df, weights="person_weight"), - household=MicroDataFrame( - household_df, weights="household_weight" - ), + household=MicroDataFrame(household_df, weights="household_weight"), marital_unit=MicroDataFrame( marital_unit_df, weights="marital_unit_weight" ), family=MicroDataFrame(family_df, weights="family_weight"), - spm_unit=MicroDataFrame( - spm_unit_df, weights="spm_unit_weight" - ), - tax_unit=MicroDataFrame( - tax_unit_df, weights="tax_unit_weight" - ), + spm_unit=MicroDataFrame(spm_unit_df, weights="spm_unit_weight"), + tax_unit=MicroDataFrame(tax_unit_df, weights="tax_unit_weight"), ), ) us_dataset.save() @@ -351,9 +335,7 @@ def ensure_datasets( all_exist = True for dataset in datasets: for year in years: - filepath = Path( - f"{data_folder}/{Path(dataset).stem}_year_{year}.h5" - ) + filepath = Path(f"{data_folder}/{Path(dataset).stem}_year_{year}.h5") if not filepath.exists(): all_exist = False break @@ -361,10 +343,6 @@ def ensure_datasets( break if all_exist: - return load_datasets( - datasets=datasets, years=years, data_folder=data_folder - ) + return load_datasets(datasets=datasets, years=years, data_folder=data_folder) else: - return create_datasets( - datasets=datasets, years=years, data_folder=data_folder - ) + return create_datasets(datasets=datasets, years=years, data_folder=data_folder) diff --git a/src/policyengine/tax_benefit_models/us/model.py b/src/policyengine/tax_benefit_models/us/model.py index b80a4d3e..f13cdb9b 100644 --- a/src/policyengine/tax_benefit_models/us/model.py +++ b/src/policyengine/tax_benefit_models/us/model.py @@ -149,9 +149,7 @@ def __init__(self, **kwargs: dict): tax_benefit_model_version=self, entity=var_obj.entity.key, description=var_obj.documentation, - data_type=var_obj.value_type - if var_obj.value_type is not Enum - else str, + data_type=var_obj.value_type if var_obj.value_type is not Enum else str, default_value=default_val, value_type=var_obj.value_type, ) @@ -291,9 +289,7 @@ def run(self, simulation: "Simulation") -> "Simulation": if entity_id_col in input_df.columns: data[entity][entity_id_col] = input_df[entity_id_col].values if entity_weight_col in input_df.columns: - data[entity][entity_weight_col] = input_df[ - entity_weight_col - ].values + data[entity][entity_weight_col] = input_df[entity_weight_col].values # For person entity, also copy person-level group ID columns person_input_df = pd.DataFrame(dataset.data.person) @@ -312,21 +308,13 @@ def run(self, simulation: "Simulation") -> "Simulation": var, period=simulation.dataset.year, map_to=entity ).values - data["person"] = MicroDataFrame( - data["person"], weights="person_weight" - ) + data["person"] = MicroDataFrame(data["person"], weights="person_weight") data["marital_unit"] = MicroDataFrame( data["marital_unit"], weights="marital_unit_weight" ) - data["family"] = MicroDataFrame( - data["family"], weights="family_weight" - ) - data["spm_unit"] = MicroDataFrame( - data["spm_unit"], weights="spm_unit_weight" - ) - data["tax_unit"] = MicroDataFrame( - data["tax_unit"], weights="tax_unit_weight" - ) + data["family"] = MicroDataFrame(data["family"], weights="family_weight") + data["spm_unit"] = MicroDataFrame(data["spm_unit"], weights="spm_unit_weight") + data["tax_unit"] = MicroDataFrame(data["tax_unit"], weights="tax_unit_weight") data["household"] = MicroDataFrame( data["household"], weights="household_weight" ) @@ -336,8 +324,7 @@ def run(self, simulation: "Simulation") -> "Simulation": name=dataset.name, description=dataset.description, filepath=str( - Path(simulation.dataset.filepath).parent - / (simulation.id + ".h5") + Path(simulation.dataset.filepath).parent / (simulation.id + ".h5") ), year=simulation.dataset.year, is_output_dataset=True, @@ -433,18 +420,14 @@ def _build_simulation_from_dataset(self, microsim, dataset, system): ) # Declare entities - builder.declare_person_entity( - "person", person_data["person_id"].values - ) + builder.declare_person_entity("person", person_data["person_id"].values) builder.declare_entity( "household", np.unique(person_data[household_id_col].values) ) builder.declare_entity( "spm_unit", np.unique(person_data[spm_unit_id_col].values) ) - builder.declare_entity( - "family", np.unique(person_data[family_id_col].values) - ) + builder.declare_entity("family", np.unique(person_data[family_id_col].values)) builder.declare_entity( "tax_unit", np.unique(person_data[tax_unit_id_col].values) ) diff --git a/src/policyengine/utils/dates.py b/src/policyengine/utils/dates.py index 3bced81b..46cec198 100644 --- a/src/policyengine/utils/dates.py +++ b/src/policyengine/utils/dates.py @@ -40,6 +40,4 @@ def parse_safe_date(date_string: str) -> datetime: if date_obj.year < 1: return date_obj.replace(year=1) return date_obj - raise ValueError( - f"Invalid date format: {date_string}. Expected YYYY-MM-DD" - ) + raise ValueError(f"Invalid date format: {date_string}. Expected YYYY-MM-DD") diff --git a/src/policyengine/utils/entity_utils.py b/src/policyengine/utils/entity_utils.py index 864af79d..f06b5d59 100644 --- a/src/policyengine/utils/entity_utils.py +++ b/src/policyengine/utils/entity_utils.py @@ -8,9 +8,7 @@ logger = logging.getLogger(__name__) -def _resolve_id_column( - person_data: pd.DataFrame, entity_name: str -) -> str: +def _resolve_id_column(person_data: pd.DataFrame, entity_name: str) -> str: """Resolve the ID column name for a group entity in person data. Tries `person_{entity}_id` first (standard convention), falls back @@ -95,9 +93,7 @@ def filter_dataset_by_household_variable( hh_ids = household_data["household_id"].values if isinstance(variable_value, str): - hh_mask = (hh_values == variable_value) | ( - hh_values == variable_value.encode() - ) + hh_mask = (hh_values == variable_value) | (hh_values == variable_value.encode()) else: hh_mask = hh_values == variable_value diff --git a/src/policyengine/utils/parametric_reforms.py b/src/policyengine/utils/parametric_reforms.py index 99538b35..d0acc33d 100644 --- a/src/policyengine/utils/parametric_reforms.py +++ b/src/policyengine/utils/parametric_reforms.py @@ -67,14 +67,10 @@ def simulation_modifier_from_parameter_values( def modifier(simulation): for pv in parameter_values: - p = simulation.tax_benefit_system.parameters.get_child( - pv.parameter.name - ) + p = simulation.tax_benefit_system.parameters.get_child(pv.parameter.name) start_period = period(pv.start_date.strftime("%Y-%m-%d")) stop_period = ( - period(pv.end_date.strftime("%Y-%m-%d")) - if pv.end_date - else None + period(pv.end_date.strftime("%Y-%m-%d")) if pv.end_date else None ) p.update( value=pv.value, @@ -101,15 +97,11 @@ def build_reform_dict(policy_or_dynamic: Policy | Dynamic | None) -> dict | None if policy_or_dynamic is None: return None if policy_or_dynamic.parameter_values: - return reform_dict_from_parameter_values( - policy_or_dynamic.parameter_values - ) + return reform_dict_from_parameter_values(policy_or_dynamic.parameter_values) return None -def merge_reform_dicts( - base: dict | None, override: dict | None -) -> dict | None: +def merge_reform_dicts(base: dict | None, override: dict | None) -> dict | None: """Merge two reform dicts, with override values taking precedence. Either or both dicts can be None. When both have entries for the same diff --git a/src/policyengine/utils/plotting.py b/src/policyengine/utils/plotting.py index c3c0ff28..4478a02d 100644 --- a/src/policyengine/utils/plotting.py +++ b/src/policyengine/utils/plotting.py @@ -18,9 +18,7 @@ } # Typography -FONT_FAMILY = ( - "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" -) +FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" FONT_SIZE_LABEL = 12 FONT_SIZE_DEFAULT = 14 FONT_SIZE_TITLE = 16 diff --git a/tests/fixtures/filtering_fixtures.py b/tests/fixtures/filtering_fixtures.py index 074f6655..4534ad97 100644 --- a/tests/fixtures/filtering_fixtures.py +++ b/tests/fixtures/filtering_fixtures.py @@ -87,9 +87,7 @@ def create_us_test_dataset() -> PolicyEngineUSDataset: is_output_dataset=False, data=USYearData( person=MicroDataFrame(person_data, weights="person_weight"), - household=MicroDataFrame( - household_data, weights="household_weight" - ), + household=MicroDataFrame(household_data, weights="household_weight"), tax_unit=MicroDataFrame(tax_unit_data, weights="tax_unit_weight"), spm_unit=MicroDataFrame(spm_unit_data, weights="spm_unit_weight"), family=MicroDataFrame(family_data, weights="family_weight"), @@ -146,9 +144,7 @@ def create_uk_test_dataset() -> PolicyEngineUKDataset: data=UKYearData( person=MicroDataFrame(person_data, weights="person_weight"), benunit=MicroDataFrame(benunit_data, weights="benunit_weight"), - household=MicroDataFrame( - household_data, weights="household_weight" - ), + household=MicroDataFrame(household_data, weights="household_weight"), ), ) diff --git a/tests/fixtures/poverty_by_demographics_fixtures.py b/tests/fixtures/poverty_by_demographics_fixtures.py index aca3ceca..c19d453d 100644 --- a/tests/fixtures/poverty_by_demographics_fixtures.py +++ b/tests/fixtures/poverty_by_demographics_fixtures.py @@ -34,9 +34,7 @@ AGE_GROUP_NAMES = list(AGE_GROUPS.keys()) # ["child", "adult", "senior"] GENDER_GROUP_NAMES = list(GENDER_GROUPS.keys()) # ["male", "female"] -RACE_GROUP_NAMES = list( - RACE_GROUPS.keys() -) # ["white", "black", "hispanic", "other"] +RACE_GROUP_NAMES = list(RACE_GROUPS.keys()) # ["white", "black", "hispanic", "other"] EXPECTED_UK_BY_AGE_COUNT = AGE_GROUP_COUNT * UK_POVERTY_TYPE_COUNT # 12 EXPECTED_US_BY_AGE_COUNT = AGE_GROUP_COUNT * US_POVERTY_TYPE_COUNT # 6 diff --git a/tests/fixtures/region_fixtures.py b/tests/fixtures/region_fixtures.py index d08fb6d3..ca1adfe2 100644 --- a/tests/fixtures/region_fixtures.py +++ b/tests/fixtures/region_fixtures.py @@ -71,9 +71,7 @@ def create_sample_us_registry() -> RegionRegistry: create_national_region(), create_state_region("CA", "California"), create_state_region("NY", "New York"), - create_place_region( - "CA", "44000", "Los Angeles city", "California" - ), + create_place_region("CA", "44000", "Los Angeles city", "California"), ], ) @@ -86,9 +84,7 @@ def create_sample_us_registry() -> RegionRegistry: STATE_NEW_YORK = create_state_region("NY", "New York") -PLACE_LOS_ANGELES = create_place_region( - "CA", "44000", "Los Angeles city", "California" -) +PLACE_LOS_ANGELES = create_place_region("CA", "44000", "Los Angeles city", "California") SIMPLE_REGION = Region( code="state/ca", diff --git a/tests/test_constituency_impact.py b/tests/test_constituency_impact.py index fbe2c818..f29e24b6 100644 --- a/tests/test_constituency_impact.py +++ b/tests/test_constituency_impact.py @@ -26,7 +26,9 @@ def _make_sim(household_data: dict) -> MagicMock: return sim -def _make_weight_matrix_and_csv(tmpdir, n_constituencies, n_households, weights, csv_rows): +def _make_weight_matrix_and_csv( + tmpdir, n_constituencies, n_households, weights, csv_rows +): """Create a temp H5 weight matrix and CSV metadata file.""" h5_path = os.path.join(tmpdir, "weights.h5") with h5py.File(h5_path, "w") as f: @@ -66,9 +68,7 @@ def test_basic_constituency_reweighting(): h5_path, csv_path = _make_weight_matrix_and_csv( tmpdir, 2, n_hh, weight_matrix, csv_rows ) - impact = compute_uk_constituency_impacts( - baseline, reform, h5_path, csv_path - ) + impact = compute_uk_constituency_impacts(baseline, reform, h5_path, csv_path) assert impact.constituency_results is not None assert len(impact.constituency_results) == 2 @@ -113,9 +113,7 @@ def test_zero_weight_constituency_skipped(): h5_path, csv_path = _make_weight_matrix_and_csv( tmpdir, 2, 2, weight_matrix, csv_rows ) - impact = compute_uk_constituency_impacts( - baseline, reform, h5_path, csv_path - ) + impact = compute_uk_constituency_impacts(baseline, reform, h5_path, csv_path) assert len(impact.constituency_results) == 1 assert impact.constituency_results[0]["constituency_code"] == "C001" @@ -143,9 +141,10 @@ def test_relative_change(): h5_path, csv_path = _make_weight_matrix_and_csv( tmpdir, 1, 1, weight_matrix, csv_rows ) - impact = compute_uk_constituency_impacts( - baseline, reform, h5_path, csv_path - ) + impact = compute_uk_constituency_impacts(baseline, reform, h5_path, csv_path) # 10% increase - assert abs(impact.constituency_results[0]["relative_household_income_change"] - 0.1) < 1e-6 + assert ( + abs(impact.constituency_results[0]["relative_household_income_change"] - 0.1) + < 1e-6 + ) diff --git a/tests/test_entity_mapping.py b/tests/test_entity_mapping.py index 77babd44..6125af0e 100644 --- a/tests/test_entity_mapping.py +++ b/tests/test_entity_mapping.py @@ -30,9 +30,7 @@ def test_map_same_entity(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) # Test person -> person result = data.map_to_entity("person", "person") @@ -79,9 +77,7 @@ def test_map_person_to_benunit(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) result = data.map_to_entity("person", "benunit", columns=["income"]) @@ -131,9 +127,7 @@ def test_map_person_to_household(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) result = data.map_to_entity("person", "household", columns=["income"]) @@ -179,9 +173,7 @@ def test_map_benunit_to_person(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) result = data.map_to_entity("benunit", "person", columns=["total_benefit"]) @@ -229,13 +221,9 @@ def test_map_benunit_to_household(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) - result = data.map_to_entity( - "benunit", "household", columns=["total_benefit"] - ) + result = data.map_to_entity("benunit", "household", columns=["total_benefit"]) # Should have household data (aggregated) assert len(result) == 2 @@ -245,9 +233,7 @@ def test_map_benunit_to_household(): # Benefits should be aggregated at household level # Household 1 has benunit 1 (1000) # Household 2 has benunit 2 (500) and benunit 3 (300) = 800 - household_benefits = result.set_index("household_id")[ - "total_benefit" - ].to_dict() + household_benefits = result.set_index("household_id")["total_benefit"].to_dict() assert household_benefits[1] == 1000 assert household_benefits[2] == 800 @@ -282,9 +268,7 @@ def test_map_household_to_person(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) result = data.map_to_entity("household", "person") @@ -326,9 +310,7 @@ def test_map_household_to_benunit(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) result = data.map_to_entity("household", "benunit", columns=["rent"]) @@ -370,9 +352,7 @@ def test_map_with_column_selection(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) # Map only age to household (aggregated) result = data.map_to_entity("person", "household", columns=["age"]) @@ -409,9 +389,7 @@ def test_invalid_entity_names(): weights="household_weight", ) - data = UKYearData( - person=person_df, benunit=benunit_df, household=household_df - ) + data = UKYearData(person=person_df, benunit=benunit_df, household=household_df) with pytest.raises(ValueError, match="Invalid source entity"): data.map_to_entity("invalid", "person") diff --git a/tests/test_entity_utils.py b/tests/test_entity_utils.py index a4ac7735..f8846457 100644 --- a/tests/test_entity_utils.py +++ b/tests/test_entity_utils.py @@ -17,9 +17,7 @@ class TestBuildEntityRelationships: """Tests for the shared build_entity_relationships function.""" - def test__given_us_style_entities__then_returns_all_columns( - self, us_test_dataset - ): + def test__given_us_style_entities__then_returns_all_columns(self, us_test_dataset): """Given: Person data with 5 group entities (US style) When: Building entity relationships Then: DataFrame has person_id + all 5 entity ID columns @@ -45,9 +43,7 @@ def test__given_us_style_entities__then_returns_all_columns( } assert set(result.columns) == expected_columns - def test__given_uk_style_entities__then_returns_all_columns( - self, uk_test_dataset - ): + def test__given_uk_style_entities__then_returns_all_columns(self, uk_test_dataset): """Given: Person data with 2 group entities (UK style) When: Building entity relationships Then: DataFrame has person_id + 2 entity ID columns @@ -67,9 +63,7 @@ def test__given_6_persons__then_returns_6_rows(self, us_test_dataset): """ person_data = pd.DataFrame(us_test_dataset.data.person) - result = build_entity_relationships( - person_data, ["household", "tax_unit"] - ) + result = build_entity_relationships(person_data, ["household", "tax_unit"]) assert len(result) == 6 diff --git a/tests/test_filtering.py b/tests/test_filtering.py index 54c5c9af..3d64c035 100644 --- a/tests/test_filtering.py +++ b/tests/test_filtering.py @@ -241,9 +241,7 @@ def test__given_invalid_variable_name__then_raises_value_error( variable_value="value", ) - def test__given_filtered_dataset__then_has_updated_metadata( - self, us_test_dataset - ): + def test__given_filtered_dataset__then_has_updated_metadata(self, us_test_dataset): """Given: US dataset When: Filtering by place_fips Then: Filtered dataset has updated id and description @@ -413,9 +411,7 @@ def test__given_no_matching_households__then_raises_value_error( variable_value="WALES", ) - def test__given_filtered_dataset__then_has_updated_metadata( - self, uk_test_dataset - ): + def test__given_filtered_dataset__then_has_updated_metadata(self, uk_test_dataset): """Given: UK dataset When: Filtering by country Then: Filtered dataset has updated id and description diff --git a/tests/test_household_impact.py b/tests/test_household_impact.py index 86439134..54f6ac19 100644 --- a/tests/test_household_impact.py +++ b/tests/test_household_impact.py @@ -74,9 +74,7 @@ def test_output_contains_all_entity_variables(self): # Check all household variables are present for var in uk_latest.entity_variables["household"]: - assert var in result.household, ( - f"Missing household variable: {var}" - ) + assert var in result.household, f"Missing household variable: {var}" # Check all person variables are present for var in uk_latest.entity_variables["person"]: @@ -161,9 +159,7 @@ def test_output_contains_all_entity_variables(self): # Check all household variables are present for var in us_latest.entity_variables["household"]: - assert var in result.household, ( - f"Missing household variable: {var}" - ) + assert var in result.household, f"Missing household variable: {var}" # Check all person variables are present for var in us_latest.entity_variables["person"]: diff --git a/tests/test_intra_decile_impact.py b/tests/test_intra_decile_impact.py index de9a807e..d49c7cf8 100644 --- a/tests/test_intra_decile_impact.py +++ b/tests/test_intra_decile_impact.py @@ -182,8 +182,14 @@ def test_intra_decile_with_decile_variable(): decile_2 = next(r for r in results.outputs if r.decile == 2) # All gains are 10% → all in gain_more_than_5pct - assert decile_1.gain_more_than_5pct == 1.0 or abs(decile_1.gain_more_than_5pct - 1.0) < 1e-9 - assert decile_2.gain_more_than_5pct == 1.0 or abs(decile_2.gain_more_than_5pct - 1.0) < 1e-9 + assert ( + decile_1.gain_more_than_5pct == 1.0 + or abs(decile_1.gain_more_than_5pct - 1.0) < 1e-9 + ) + assert ( + decile_2.gain_more_than_5pct == 1.0 + or abs(decile_2.gain_more_than_5pct - 1.0) < 1e-9 + ) # --------------------------------------------------------------------------- diff --git a/tests/test_local_authority_impact.py b/tests/test_local_authority_impact.py index cbbcbe10..a872262e 100644 --- a/tests/test_local_authority_impact.py +++ b/tests/test_local_authority_impact.py @@ -60,12 +60,8 @@ def test_basic_local_authority_reweighting(): ] with tempfile.TemporaryDirectory() as tmpdir: - h5_path, csv_path = _make_weight_matrix_and_csv( - tmpdir, weight_matrix, csv_rows - ) - impact = compute_uk_local_authority_impacts( - baseline, reform, h5_path, csv_path - ) + h5_path, csv_path = _make_weight_matrix_and_csv(tmpdir, weight_matrix, csv_rows) + impact = compute_uk_local_authority_impacts(baseline, reform, h5_path, csv_path) assert impact.local_authority_results is not None assert len(impact.local_authority_results) == 2 @@ -105,12 +101,8 @@ def test_zero_weight_la_skipped(): ] with tempfile.TemporaryDirectory() as tmpdir: - h5_path, csv_path = _make_weight_matrix_and_csv( - tmpdir, weight_matrix, csv_rows - ) - impact = compute_uk_local_authority_impacts( - baseline, reform, h5_path, csv_path - ) + h5_path, csv_path = _make_weight_matrix_and_csv(tmpdir, weight_matrix, csv_rows) + impact = compute_uk_local_authority_impacts(baseline, reform, h5_path, csv_path) assert len(impact.local_authority_results) == 1 assert impact.local_authority_results[0]["local_authority_code"] == "LA001" diff --git a/tests/test_models.py b/tests/test_models.py index e5b4484e..eb509ea9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -167,11 +167,7 @@ def test__given_breakdown_label__then_includes_enum_value_in_parentheses( def test__given_bracket_label__then_follows_expected_format(self): """Bracket labels should follow the format 'Scale label (bracket N field)'.""" for p in us_latest.parameters: - if ( - "[0].rate" in p.name - and p.label - and "bracket" in p.label.lower() - ): + if "[0].rate" in p.name and p.label and "bracket" in p.label.lower(): assert re.search(r"\(bracket \d+ rate\)", p.label), ( f"Label '{p.label}' doesn't match expected bracket format" ) diff --git a/tests/test_parameter_labels.py b/tests/test_parameter_labels.py index 4e66fc0a..8d9a7e66 100644 --- a/tests/test_parameter_labels.py +++ b/tests/test_parameter_labels.py @@ -435,9 +435,7 @@ def test__given_single_level_breakdown_with_enum__then_generates_label_with_enum result = generate_label_for_parameter(param, system, scale_lookup) # Then: Label uses enum display value - assert ( - result == "Tax exemption by filing status (Married filing jointly)" - ) + assert result == "Tax exemption by filing status (Married filing jointly)" def test__given_single_level_breakdown_without_enum__then_generates_label_with_raw_key( self, @@ -645,8 +643,7 @@ def test__given_nested_breakdown_with_enum_and_range__then_generates_full_label( # Then # Without snap_region enum in system, uses breakdown_label for first dimension too assert ( - result - == "SNAP max allotment (SNAP region CONTIGUOUS_US, Household size 1)" + result == "SNAP max allotment (SNAP region CONTIGUOUS_US, Household size 1)" ) def test__given_breakdown_labels_for_range__then_includes_semantic_label( @@ -725,10 +722,7 @@ def test__given_three_level_nesting__then_generates_all_dimensions(self): result = generate_label_for_parameter(param, system, scale_lookup) # Then - assert ( - result - == "State sales tax (CA, Income bracket 3, Exemption count 5)" - ) + assert result == "State sales tax (CA, Income bracket 3, Exemption count 5)" def test__given_missing_breakdown_labels__then_uses_raw_values(self): # Given @@ -793,9 +787,7 @@ def test__given_enum_range_enum_nesting__then_formats_each_correctly(self): result = generate_label_for_parameter(param, system, scale_lookup) # Then: Enum values use display names, range uses breakdown_label - assert ( - result == "Earned income credit (CA, Number of children 2, Single)" - ) + assert result == "Earned income credit (CA, Number of children 2, Single)" def test__given_range_enum_range_nesting__then_formats_each_correctly( self, @@ -865,10 +857,7 @@ def test__given_partial_breakdown_labels__then_uses_labels_where_available( result = generate_label_for_parameter(param, system, scale_lookup) # Then: Uses breakdown_labels where available, raw value for missing label - assert ( - result - == "Utility allowance (Area AREA_1, Household size 3, RENTER)" - ) + assert result == "Utility allowance (Area AREA_1, Household size 3, RENTER)" def test__given_four_level_nesting_with_mixed_types__then_generates_all_dimensions( self, diff --git a/tests/test_parametric_reforms.py b/tests/test_parametric_reforms.py index 6e328aa3..418f2fce 100644 --- a/tests/test_parametric_reforms.py +++ b/tests/test_parametric_reforms.py @@ -233,10 +233,7 @@ def test__given_multiple_values__then_applies_all_updates(self): modifier(mock_simulation) # Then - assert ( - mock_simulation.tax_benefit_system.parameters.get_child.call_count - == 3 - ) + assert mock_simulation.tax_benefit_system.parameters.get_child.call_count == 3 assert mock_param_node.update.call_count == 3 def test__given_modifier__then_returns_simulation(self): diff --git a/tests/test_poverty.py b/tests/test_poverty.py index 64baad68..49108ca1 100644 --- a/tests/test_poverty.py +++ b/tests/test_poverty.py @@ -192,19 +192,11 @@ def test_poverty_variable_mappings(): # UK mappings assert UK_POVERTY_VARIABLES[UKPovertyType.ABSOLUTE_BHC] == "in_poverty_bhc" assert UK_POVERTY_VARIABLES[UKPovertyType.ABSOLUTE_AHC] == "in_poverty_ahc" - assert ( - UK_POVERTY_VARIABLES[UKPovertyType.RELATIVE_BHC] - == "in_relative_poverty_bhc" - ) - assert ( - UK_POVERTY_VARIABLES[UKPovertyType.RELATIVE_AHC] - == "in_relative_poverty_ahc" - ) + assert UK_POVERTY_VARIABLES[UKPovertyType.RELATIVE_BHC] == "in_relative_poverty_bhc" + assert UK_POVERTY_VARIABLES[UKPovertyType.RELATIVE_AHC] == "in_relative_poverty_ahc" # US mappings - assert ( - US_POVERTY_VARIABLES[USPovertyType.SPM] == "spm_unit_is_in_spm_poverty" - ) + assert US_POVERTY_VARIABLES[USPovertyType.SPM] == "spm_unit_is_in_spm_poverty" assert ( US_POVERTY_VARIABLES[USPovertyType.SPM_DEEP] == "spm_unit_is_in_deep_spm_poverty" diff --git a/tests/test_poverty_by_demographics.py b/tests/test_poverty_by_demographics.py index 5a18a300..3e27dc6d 100644 --- a/tests/test_poverty_by_demographics.py +++ b/tests/test_poverty_by_demographics.py @@ -52,9 +52,7 @@ def test__given_simulation__then_returns_12_records(self, mock_rates): assert len(result.outputs) == EXPECTED_UK_BY_AGE_COUNT @patch("policyengine.outputs.poverty.calculate_uk_poverty_rates") - def test__given_simulation__then_calls_base_once_per_age_group( - self, mock_rates - ): + def test__given_simulation__then_calls_base_once_per_age_group(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.return_value = make_uk_mock_collection(sim) @@ -66,9 +64,7 @@ def test__given_simulation__then_calls_base_once_per_age_group( assert mock_rates.call_count == len(AGE_GROUPS) @patch("policyengine.outputs.poverty.calculate_uk_poverty_rates") - def test__given_simulation__then_filter_group_set_to_group_name( - self, mock_rates - ): + def test__given_simulation__then_filter_group_set_to_group_name(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.side_effect = lambda *a, **kw: make_uk_mock_collection(sim) @@ -81,9 +77,7 @@ def test__given_simulation__then_filter_group_set_to_group_name( assert filter_groups == set(AGE_GROUP_NAMES) @patch("policyengine.outputs.poverty.calculate_uk_poverty_rates") - def test__given_simulation__then_passes_correct_filter_kwargs( - self, mock_rates - ): + def test__given_simulation__then_passes_correct_filter_kwargs(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.return_value = make_uk_mock_collection(sim) @@ -119,9 +113,7 @@ def test__given_simulation__then_returns_6_records(self, mock_rates): assert len(result.outputs) == EXPECTED_US_BY_AGE_COUNT @patch("policyengine.outputs.poverty.calculate_us_poverty_rates") - def test__given_simulation__then_filter_group_set_to_group_name( - self, mock_rates - ): + def test__given_simulation__then_filter_group_set_to_group_name(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.side_effect = lambda *a, **kw: make_us_mock_collection(sim) @@ -155,9 +147,7 @@ def test__given_simulation__then_returns_8_records(self, mock_rates): assert len(result.outputs) == EXPECTED_UK_BY_GENDER_COUNT @patch("policyengine.outputs.poverty.calculate_uk_poverty_rates") - def test__given_simulation__then_filter_group_set_to_gender_names( - self, mock_rates - ): + def test__given_simulation__then_filter_group_set_to_gender_names(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.side_effect = lambda *a, **kw: make_uk_mock_collection(sim) @@ -205,9 +195,7 @@ def test__given_simulation__then_returns_4_records(self, mock_rates): assert len(result.outputs) == EXPECTED_US_BY_GENDER_COUNT @patch("policyengine.outputs.poverty.calculate_us_poverty_rates") - def test__given_simulation__then_filter_group_set_to_gender_names( - self, mock_rates - ): + def test__given_simulation__then_filter_group_set_to_gender_names(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.side_effect = lambda *a, **kw: make_us_mock_collection(sim) @@ -241,9 +229,7 @@ def test__given_simulation__then_returns_8_records(self, mock_rates): assert len(result.outputs) == EXPECTED_US_BY_RACE_COUNT @patch("policyengine.outputs.poverty.calculate_us_poverty_rates") - def test__given_simulation__then_calls_base_once_per_race_group( - self, mock_rates - ): + def test__given_simulation__then_calls_base_once_per_race_group(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.return_value = make_us_mock_collection(sim) @@ -255,9 +241,7 @@ def test__given_simulation__then_calls_base_once_per_race_group( assert mock_rates.call_count == len(RACE_GROUPS) @patch("policyengine.outputs.poverty.calculate_us_poverty_rates") - def test__given_simulation__then_filter_group_set_to_race_names( - self, mock_rates - ): + def test__given_simulation__then_filter_group_set_to_race_names(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.side_effect = lambda *a, **kw: make_us_mock_collection(sim) @@ -286,9 +270,7 @@ def test__given_simulation__then_passes_race_filter_with_correct_eq_value( assert white_call.kwargs["filter_variable_eq"] == "WHITE" @patch("policyengine.outputs.poverty.calculate_us_poverty_rates") - def test__given_simulation__then_dataframe_has_correct_row_count( - self, mock_rates - ): + def test__given_simulation__then_dataframe_has_correct_row_count(self, mock_rates): # Given sim = make_mock_simulation() mock_rates.return_value = make_us_mock_collection(sim) diff --git a/tests/test_region.py b/tests/test_region.py index bc7ee0f6..e13d5b5e 100644 --- a/tests/test_region.py +++ b/tests/test_region.py @@ -62,17 +62,13 @@ def test__given_same_codes__then_regions_are_equal(self): Then: They are equal regardless of other fields """ # Given - region1 = Region( - code="state/ca", label="California", region_type="state" - ) + region1 = Region(code="state/ca", label="California", region_type="state") region2 = Region( code="state/ca", label="California (different)", region_type="state", ) - region3 = Region( - code="state/ny", label="New York", region_type="state" - ) + region3 = Region(code="state/ny", label="New York", region_type="state") # Then assert region1 == region2 @@ -84,17 +80,13 @@ def test__given_region__then_can_use_as_dict_key_or_in_set(self): Then: Regions with same code are deduplicated """ # Given - region1 = Region( - code="state/ca", label="California", region_type="state" - ) + region1 = Region(code="state/ca", label="California", region_type="state") region2 = Region( code="state/ca", label="California (duplicate)", region_type="state", ) - region3 = Region( - code="state/ny", label="New York", region_type="state" - ) + region3 = Region(code="state/ny", label="New York", region_type="state") # When region_set = {region1, region2, region3} @@ -118,9 +110,7 @@ def test__given_registry_with_regions__then_length_is_correct( # Then assert len(sample_registry) == 4 - def test__given_registry__then_can_iterate_over_regions( - self, sample_registry - ): + def test__given_registry__then_can_iterate_over_regions(self, sample_registry): """Given: Registry with regions When: Iterating Then: All region codes are accessible @@ -133,9 +123,7 @@ def test__given_registry__then_can_iterate_over_regions( assert "state/ca" in codes assert "place/CA-44000" in codes - def test__given_existing_code__then_code_is_in_registry( - self, sample_registry - ): + def test__given_existing_code__then_code_is_in_registry(self, sample_registry): """Given: Registry with state/ca When: Checking if code exists Then: Returns True for existing, False for missing @@ -236,9 +224,7 @@ def test__given_registry__then_get_filter_regions_returns_regions_requiring_filt assert len(filter_regions) == 1 assert filter_regions[0].code == "place/CA-44000" - def test__given_registry__then_can_add_region_dynamically( - self, sample_registry - ): + def test__given_registry__then_can_add_region_dynamically(self, sample_registry): """Given: Registry with 4 regions When: Adding a new region Then: Registry contains 5 regions and new region is indexed diff --git a/tests/test_uk_regions.py b/tests/test_uk_regions.py index cbaa5328..09e4611d 100644 --- a/tests/test_uk_regions.py +++ b/tests/test_uk_regions.py @@ -66,10 +66,7 @@ def test__given_uk_registry__then_has_national_region(self): assert national.code == "uk" assert national.label == "United Kingdom" assert national.region_type == "national" - assert ( - national.dataset_path - == f"{UK_DATA_BUCKET}/enhanced_frs_2023_24.h5" - ) + assert national.dataset_path == f"{UK_DATA_BUCKET}/enhanced_frs_2023_24.h5" assert not national.requires_filter def test__given_uk_registry__then_has_four_country_regions(self): diff --git a/tests/test_us_reform_application.py b/tests/test_us_reform_application.py index 42657499..21b9d01c 100644 --- a/tests/test_us_reform_application.py +++ b/tests/test_us_reform_application.py @@ -5,7 +5,6 @@ fixing the p.update() bug that exists in the US country package. """ - from policyengine.tax_benefit_models.us import ( calculate_household_impact as calculate_us_household_impact, ) diff --git a/tests/test_us_regions.py b/tests/test_us_regions.py index 54149305..247e9135 100644 --- a/tests/test_us_regions.py +++ b/tests/test_us_regions.py @@ -49,9 +49,7 @@ def test__given_district_counts__then_every_state_has_count(self): """ # When/Then for state in US_STATES: - assert state in DISTRICT_COUNTS, ( - f"Missing district count for {state}" - ) + assert state in DISTRICT_COUNTS, f"Missing district count for {state}" def test__given_district_counts__then_total_is_436(self): """Given: DISTRICT_COUNTS dictionary @@ -106,9 +104,7 @@ def test__given_us_registry__then_has_national_region(self): assert national.code == "us" assert national.label == "United States" assert national.region_type == "national" - assert ( - national.dataset_path == f"{US_DATA_BUCKET}/enhanced_cps_2024.h5" - ) + assert national.dataset_path == f"{US_DATA_BUCKET}/enhanced_cps_2024.h5" def test__given_us_registry__then_has_51_states(self): """Given: US region registry @@ -178,10 +174,7 @@ def test__given_dc_district__then_is_at_large(self): # Then assert dc_al is not None - assert ( - dc_al.label - == "District of Columbia's at-large congressional district" - ) + assert dc_al.label == "District of Columbia's at-large congressional district" assert dc_al.parent_code == "state/dc" def test__given_us_registry__then_has_places(self): diff --git a/uv.lock b/uv.lock index 8cf942d2..482c547d 100644 --- a/uv.lock +++ b/uv.lock @@ -103,26 +103,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, ] -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, -] - [[package]] name = "blosc2" version = "3.7.2" @@ -804,15 +784,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "myst-nb" version = "1.3.0" @@ -1026,15 +997,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363, upload-time = "2022-05-04T13:37:20.585Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - [[package]] name = "pexpect" version = "4.9.0" @@ -1080,7 +1042,7 @@ wheels = [ [[package]] name = "policyengine" -version = "3.1.16" +version = "3.2.0" source = { editable = "." } dependencies = [ { name = "microdf-python" }, @@ -1094,7 +1056,6 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "autodoc-pydantic" }, - { name = "black" }, { name = "build" }, { name = "furo" }, { name = "itables" }, @@ -1105,6 +1066,7 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, + { name = "towncrier" }, { name = "yaml-changelog" }, ] uk = [ @@ -1119,7 +1081,6 @@ us = [ [package.metadata] requires-dist = [ { name = "autodoc-pydantic", marker = "extra == 'dev'" }, - { name = "black", marker = "extra == 'dev'" }, { name = "build", marker = "extra == 'dev'" }, { name = "furo", marker = "extra == 'dev'" }, { name = "itables", marker = "extra == 'dev'" }, @@ -1139,7 +1100,8 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.26.0" }, { name = "requests", specifier = ">=2.31.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.5.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" }, + { name = "towncrier", marker = "extra == 'dev'", specifier = ">=24.8.0" }, { name = "yaml-changelog", marker = "extra == 'dev'", specifier = ">=0.1.7" }, ] provides-extras = ["uk", "us", "dev"] @@ -2018,6 +1980,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, ] +[[package]] +name = "towncrier" +version = "25.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/eb/5bf25a34123698d3bbab39c5bc5375f8f8bcbcc5a136964ade66935b8b9d/towncrier-25.8.0.tar.gz", hash = "sha256:eef16d29f831ad57abb3ae32a0565739866219f1ebfbdd297d32894eb9940eb1", size = 76322, upload-time = "2025-08-30T11:41:55.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl", hash = "sha256:b953d133d98f9aeae9084b56a3563fd2519dfc6ec33f61c9cd2c61ff243fb513", size = 65101, upload-time = "2025-08-30T11:41:53.644Z" }, +] + [[package]] name = "tqdm" version = "4.67.1"