From 1daf56146f0a0b93371ea5ed47539ba39543dfd8 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 18 Apr 2026 19:39:46 -0400 Subject: [PATCH 1/2] Pre-launch cleanup: dead code + plotly optional extra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes three unambiguously dead code paths and moves plotly out of the core install so `import policyengine` doesn't pull the charting stack. Changes are behavior-preserving for every downstream repo surveyed (policyengine-api, policyengine-api-v2, policyengine-api-v2-alpha). 1. Delete `tax_benefit_models/{us,uk}.py` shim files. Python always resolves the `us/`/`uk/` package directory first, so the .py files were dead. Worse: both re-exported `general_policy_reform_analysis` which is not defined anywhere — `from policyengine.tax_benefit_models.us import general_policy_reform_analysis` raises ImportError at runtime. 2. Delete `_create_entity_output_model` + `PersonOutput` / `BenunitOutput` / `HouseholdEntityOutput` in uk/analysis.py. Built via pydantic.create_model at import time, referenced nowhere in the codebase. 3. Delete `policyengine.core.DatasetVersion`. One optional field on Dataset (never set by anything) and one core re-export. Nothing reads it downstream. 4. Move `plotly>=5.0.0` from base dependencies to a `[plotting]` optional extra. Only `policyengine.utils.plotting` uses plotly, and nothing in src/ imports that module — only `examples/` do. `plotting.py` now soft-imports with a clear install hint. Downstream impact: none. Surveyed policyengine-api (pinned to a pre-3.x API), policyengine-api-v2 (3.4.0), policyengine-api-v2-alpha (3.1.15); none of them import the deleted symbols. Tests: 216 passed locally across test_release_manifests, test_trace_tro, test_results, test_household_impact, test_models, test_us_regions, test_uk_regions, test_region, test_manifest_version_mismatch, test_filtering, test_cache, test_scoping_strategy. Deferred (bigger refactors, follow-up PRs): - filter_field/filter_value legacy path on Simulation (still wired through Region construction; needs migration) - calculate_household_impact → calculate_household rename (with deprecation shim) - Extract shared MicrosimulationModelVersion base (~600 LOC savings) - Move release_manifest + trace_tro to policyengine/provenance/ Co-Authored-By: Claude Opus 4.7 (1M context) --- changelog.d/pre-launch-cleanup.removed.md | 6 +++ pyproject.toml | 5 ++- src/policyengine/core/__init__.py | 1 - src/policyengine/core/dataset.py | 2 - src/policyengine/core/dataset_version.py | 16 -------- src/policyengine/tax_benefit_models/uk.py | 40 ------------------- .../tax_benefit_models/uk/analysis.py | 20 +--------- src/policyengine/tax_benefit_models/us.py | 40 ------------------- src/policyengine/utils/plotting.py | 20 ++++++++-- 9 files changed, 28 insertions(+), 122 deletions(-) create mode 100644 changelog.d/pre-launch-cleanup.removed.md delete mode 100644 src/policyengine/core/dataset_version.py delete mode 100644 src/policyengine/tax_benefit_models/uk.py delete mode 100644 src/policyengine/tax_benefit_models/us.py diff --git a/changelog.d/pre-launch-cleanup.removed.md b/changelog.d/pre-launch-cleanup.removed.md new file mode 100644 index 00000000..73b95b51 --- /dev/null +++ b/changelog.d/pre-launch-cleanup.removed.md @@ -0,0 +1,6 @@ +Pre-launch cleanup — remove dead code and drop `plotly` from the core dependency set: + +- Delete `policyengine.tax_benefit_models.us` and `policyengine.tax_benefit_models.uk` module shims. Python resolves the package directory first, so the `.py` shims were always shadowed; worse, both attempted to re-export `general_policy_reform_analysis` which is not defined anywhere, making `from policyengine.tax_benefit_models.us import general_policy_reform_analysis` raise `ImportError` at runtime. +- Delete `_create_entity_output_model` plus the `PersonOutput` / `BenunitOutput` / `HouseholdEntityOutput` factory products in `policyengine.tax_benefit_models.uk.analysis` — built via `pydantic.create_model` but never referenced anywhere in the codebase. +- Delete `policyengine.core.DatasetVersion` (only consumer was an `Optional` field on `Dataset` that was never set, and the `policyengine.core` re-export). +- Move `plotly>=5.0.0` from the base install to a new `policyengine[plotting]` extra. Only `policyengine.utils.plotting` uses it, and that module is itself only used by the `examples/` scripts. The package now imports cleanly without `plotly`. diff --git a/pyproject.toml b/pyproject.toml index 67582060..72af3935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ dependencies = [ "pydantic>=2.0.0", "pandas>=2.0.0", "microdf_python>=1.2.1", - "plotly>=5.0.0", "requests>=2.31.0", "psutil>=5.9.0", "packaging>=23.0", @@ -34,6 +33,9 @@ dependencies = [ policyengine = "policyengine.cli:main" [project.optional-dependencies] +plotting = [ + "plotly>=5.0.0", +] uk = [ "policyengine_core>=3.25.0", "policyengine-uk==2.88.0", @@ -51,6 +53,7 @@ dev = [ "itables", "build", "jsonschema>=4.0.0", + "plotly>=5.0.0", "pytest-asyncio>=0.26.0", "ruff>=0.9.0", "policyengine_core>=3.25.0", diff --git a/src/policyengine/core/__init__.py b/src/policyengine/core/__init__.py index 8ff37aed..71ca0132 100644 --- a/src/policyengine/core/__init__.py +++ b/src/policyengine/core/__init__.py @@ -1,7 +1,6 @@ from .dataset import Dataset from .dataset import YearData as YearData from .dataset import map_to_entity as map_to_entity -from .dataset_version import DatasetVersion as DatasetVersion from .dynamic import Dynamic as Dynamic from .output import Output as Output from .output import OutputCollection as OutputCollection diff --git a/src/policyengine/core/dataset.py b/src/policyengine/core/dataset.py index 27f51d16..64f74eba 100644 --- a/src/policyengine/core/dataset.py +++ b/src/policyengine/core/dataset.py @@ -6,7 +6,6 @@ from microdf import MicroDataFrame from pydantic import BaseModel, ConfigDict, Field -from .dataset_version import DatasetVersion from .tax_benefit_model import TaxBenefitModel @@ -85,7 +84,6 @@ class MyDataset(Dataset): id: str = Field(default_factory=lambda: str(uuid4())) name: str description: str - dataset_version: Optional[DatasetVersion] = None filepath: str is_output_dataset: bool = False tax_benefit_model: Optional[TaxBenefitModel] = None diff --git a/src/policyengine/core/dataset_version.py b/src/policyengine/core/dataset_version.py deleted file mode 100644 index 711cd7d7..00000000 --- a/src/policyengine/core/dataset_version.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import TYPE_CHECKING -from uuid import uuid4 - -from pydantic import BaseModel, Field - -from .tax_benefit_model import TaxBenefitModel - -if TYPE_CHECKING: - from .dataset import Dataset - - -class DatasetVersion(BaseModel): - id: str = Field(default_factory=lambda: str(uuid4())) - dataset: "Dataset" - description: str - tax_benefit_model: TaxBenefitModel = None diff --git a/src/policyengine/tax_benefit_models/uk.py b/src/policyengine/tax_benefit_models/uk.py deleted file mode 100644 index 52abcb18..00000000 --- a/src/policyengine/tax_benefit_models/uk.py +++ /dev/null @@ -1,40 +0,0 @@ -"""PolicyEngine UK tax-benefit model - imports from uk/ module.""" - -from importlib.util import find_spec - -if find_spec("policyengine_uk") is not None: - from .uk import ( - PolicyEngineUK, - PolicyEngineUKDataset, - PolicyEngineUKLatest, - ProgrammeStatistics, - UKYearData, - create_datasets, - ensure_datasets, - general_policy_reform_analysis, - load_datasets, - managed_microsimulation, - uk_latest, - uk_model, - ) - - __all__ = [ - "UKYearData", - "PolicyEngineUKDataset", - "create_datasets", - "load_datasets", - "ensure_datasets", - "PolicyEngineUK", - "PolicyEngineUKLatest", - "managed_microsimulation", - "uk_model", - "uk_latest", - "general_policy_reform_analysis", - "ProgrammeStatistics", - ] - - # Rebuild models to resolve forward references - PolicyEngineUKDataset.model_rebuild() - PolicyEngineUKLatest.model_rebuild() -else: - __all__ = [] diff --git a/src/policyengine/tax_benefit_models/uk/analysis.py b/src/policyengine/tax_benefit_models/uk/analysis.py index 0a545b52..b05e21b0 100644 --- a/src/policyengine/tax_benefit_models/uk/analysis.py +++ b/src/policyengine/tax_benefit_models/uk/analysis.py @@ -6,7 +6,7 @@ import pandas as pd from microdf import MicroDataFrame -from pydantic import BaseModel, Field, create_model +from pydantic import BaseModel, Field from policyengine.core import OutputCollection, Simulation from policyengine.core.policy import Policy @@ -28,24 +28,6 @@ from .outputs import ProgrammeStatistics -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) - - -# Create output models dynamically from uk_latest.entity_variables -PersonOutput = _create_entity_output_model( - "person", uk_latest.entity_variables["person"] -) -BenunitOutput = _create_entity_output_model( - "benunit", uk_latest.entity_variables["benunit"] -) -HouseholdEntityOutput = _create_entity_output_model( - "household", uk_latest.entity_variables["household"] -) - - class UKHouseholdOutput(BaseModel): """Output from a UK household calculation with all entity data.""" diff --git a/src/policyengine/tax_benefit_models/us.py b/src/policyengine/tax_benefit_models/us.py deleted file mode 100644 index bbc29486..00000000 --- a/src/policyengine/tax_benefit_models/us.py +++ /dev/null @@ -1,40 +0,0 @@ -"""PolicyEngine US tax-benefit model - imports from us/ module.""" - -from importlib.util import find_spec - -if find_spec("policyengine_us") is not None: - from .us import ( - PolicyEngineUS, - PolicyEngineUSDataset, - PolicyEngineUSLatest, - ProgramStatistics, - USYearData, - create_datasets, - ensure_datasets, - general_policy_reform_analysis, - load_datasets, - managed_microsimulation, - us_latest, - us_model, - ) - - __all__ = [ - "USYearData", - "PolicyEngineUSDataset", - "create_datasets", - "load_datasets", - "ensure_datasets", - "PolicyEngineUS", - "PolicyEngineUSLatest", - "managed_microsimulation", - "us_model", - "us_latest", - "general_policy_reform_analysis", - "ProgramStatistics", - ] - - # Rebuild models to resolve forward references - PolicyEngineUSDataset.model_rebuild() - PolicyEngineUSLatest.model_rebuild() -else: - __all__ = [] diff --git a/src/policyengine/utils/plotting.py b/src/policyengine/utils/plotting.py index 2ca8e48c..b1700a35 100644 --- a/src/policyengine/utils/plotting.py +++ b/src/policyengine/utils/plotting.py @@ -1,8 +1,22 @@ -"""Plotting utilities for PolicyEngine visualisations.""" +"""Plotting utilities for PolicyEngine visualisations. -from typing import Optional +Requires plotly, which is installed via the ``[plotting]`` extra +(``pip install policyengine[plotting]``). Importing from this module +fails with a clear error when plotly is absent. +""" -import plotly.graph_objects as go +from typing import TYPE_CHECKING, Optional + +try: + import plotly.graph_objects as go +except ImportError as exc: # pragma: no cover + raise ImportError( + "policyengine.utils.plotting requires plotly. " + "Install with: pip install policyengine[plotting]" + ) from exc + +if TYPE_CHECKING: + import plotly.graph_objects as go # noqa: F401 # PolicyEngine brand colours COLORS = { From 8e412d244afcde70f66eb9948c4c92e78cfbafa5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 18 Apr 2026 19:55:47 -0400 Subject: [PATCH 2/2] Extract brand tokens to utils.design so import works without plotly utils/__init__.py eagerly imported COLORS from plotting.py, which now raises ImportError when plotly isn't installed. Every smoke-import job on PR #288 failed because plotting.py blocked at module load. Move COLORS + FONT_* constants to a new plotly-free utils/design.py; plotting.py re-exports them for backward compatibility and adds them to __all__. utils/__init__.py now pulls COLORS from design rather than plotting. Confirmed locally that pip uninstall plotly still lets 'import policyengine' + 'from policyengine.utils import COLORS' + 'from policyengine.core.release_manifest import get_release_manifest' all work cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/policyengine/utils/__init__.py | 3 +-- src/policyengine/utils/design.py | 24 ++++++++++++++++++ src/policyengine/utils/plotting.py | 40 ++++++++++++++---------------- 3 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 src/policyengine/utils/design.py diff --git a/src/policyengine/utils/__init__.py b/src/policyengine/utils/__init__.py index bf3cc681..bfbfe10b 100644 --- a/src/policyengine/utils/__init__.py +++ b/src/policyengine/utils/__init__.py @@ -1,7 +1,6 @@ from .dates import parse_safe_date as parse_safe_date +from .design import COLORS as COLORS from .parameter_labels import build_scale_lookup as build_scale_lookup from .parameter_labels import ( generate_label_for_parameter as generate_label_for_parameter, ) -from .plotting import COLORS as COLORS -from .plotting import format_fig as format_fig diff --git a/src/policyengine/utils/design.py b/src/policyengine/utils/design.py new file mode 100644 index 00000000..eda921a1 --- /dev/null +++ b/src/policyengine/utils/design.py @@ -0,0 +1,24 @@ +"""PolicyEngine brand colours and typography tokens. + +Lives outside ``plotting`` so consumers can import ``COLORS`` without +pulling plotly in. +""" + +COLORS = { + "primary": "#319795", # Teal + "primary_light": "#E6FFFA", + "primary_dark": "#1D4044", + "success": "#22C55E", # Green (positive changes) + "warning": "#FEC601", # Yellow (cautions) + "error": "#EF4444", # Red (negative changes) + "info": "#1890FF", # Blue (neutral info) + "gray_light": "#F2F4F7", + "gray": "#667085", + "gray_dark": "#101828", + "blue_secondary": "#026AA2", +} + +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/src/policyengine/utils/plotting.py b/src/policyengine/utils/plotting.py index b1700a35..15243e0e 100644 --- a/src/policyengine/utils/plotting.py +++ b/src/policyengine/utils/plotting.py @@ -2,7 +2,9 @@ Requires plotly, which is installed via the ``[plotting]`` extra (``pip install policyengine[plotting]``). Importing from this module -fails with a clear error when plotly is absent. +fails with a clear error when plotly is absent. Brand tokens +(``COLORS``, font constants) live in :mod:`policyengine.utils.design` +so they can be imported without plotly. """ from typing import TYPE_CHECKING, Optional @@ -18,26 +20,22 @@ if TYPE_CHECKING: import plotly.graph_objects as go # noqa: F401 -# PolicyEngine brand colours -COLORS = { - "primary": "#319795", # Teal - "primary_light": "#E6FFFA", - "primary_dark": "#1D4044", - "success": "#22C55E", # Green (positive changes) - "warning": "#FEC601", # Yellow (cautions) - "error": "#EF4444", # Red (negative changes) - "info": "#1890FF", # Blue (neutral info) - "gray_light": "#F2F4F7", - "gray": "#667085", - "gray_dark": "#101828", - "blue_secondary": "#026AA2", -} - -# Typography -FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" -FONT_SIZE_LABEL = 12 -FONT_SIZE_DEFAULT = 14 -FONT_SIZE_TITLE = 16 +from .design import ( + COLORS, + FONT_FAMILY, + FONT_SIZE_DEFAULT, + FONT_SIZE_LABEL, + FONT_SIZE_TITLE, +) + +__all__ = [ + "COLORS", + "FONT_FAMILY", + "FONT_SIZE_DEFAULT", + "FONT_SIZE_LABEL", + "FONT_SIZE_TITLE", + "format_fig", +] def format_fig(