diff --git a/.github/scripts/modal-sync-secrets.sh b/.github/scripts/modal-sync-secrets.sh index 99233e221..cca3b9b59 100755 --- a/.github/scripts/modal-sync-secrets.sh +++ b/.github/scripts/modal-sync-secrets.sh @@ -62,11 +62,6 @@ if [ -n "${GCP_CREDENTIALS_JSON:-}" ]; then --force || true fi -uv run modal secret create policyengine-data-credentials \ - "HUGGING_FACE_TOKEN=${HUGGING_FACE_TOKEN:-}" \ - --env="$MODAL_ENV" \ - --force || true - # Sync gateway auth config. The gateway runtime only needs issuer/audience and # the explicit requirement flag; client credentials stay on the GitHub side and # are only used to mint integration-test tokens. diff --git a/.github/workflows/modal-deploy.reusable.yml b/.github/workflows/modal-deploy.reusable.yml index 1890aa400..ef2de18d0 100644 --- a/.github/workflows/modal-deploy.reusable.yml +++ b/.github/workflows/modal-deploy.reusable.yml @@ -62,7 +62,6 @@ jobs: MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }} GCP_CREDENTIALS_JSON: ${{ secrets.GCP_CREDENTIALS_JSON }} - HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }} GATEWAY_AUTH_ISSUER: ${{ secrets.GATEWAY_AUTH_ISSUER }} GATEWAY_AUTH_AUDIENCE: ${{ secrets.GATEWAY_AUTH_AUDIENCE }} GATEWAY_AUTH_CLIENT_ID: ${{ secrets.GATEWAY_AUTH_CLIENT_ID }} diff --git a/projects/policyengine-api-simulation/pyproject.toml b/projects/policyengine-api-simulation/pyproject.toml index 3ae010703..e83224ad3 100644 --- a/projects/policyengine-api-simulation/pyproject.toml +++ b/projects/policyengine-api-simulation/pyproject.toml @@ -16,8 +16,8 @@ dependencies = [ "pydantic-settings (>=2.7.1,<3.0.0)", "opentelemetry-instrumentation-fastapi (>=0.51b0,<0.52)", "policyengine-fastapi", - "policyengine==4.4.3", - "policyengine-core>=3.26.1", + "policyengine==0.13.0", + "policyengine-core>=3.23.5", "policyengine-uk==2.88.14", "policyengine-us==1.690.7", "tables>=3.10.2", diff --git a/projects/policyengine-api-simulation/src/modal/app.py b/projects/policyengine-api-simulation/src/modal/app.py index 2c80acba0..b0929793c 100644 --- a/projects/policyengine-api-simulation/src/modal/app.py +++ b/projects/policyengine-api-simulation/src/modal/app.py @@ -15,7 +15,7 @@ # Get versions from environment or use defaults US_VERSION = os.environ.get("POLICYENGINE_US_VERSION", "1.690.7") -UK_VERSION = os.environ.get("POLICYENGINE_UK_VERSION", "2.88.0") +UK_VERSION = os.environ.get("POLICYENGINE_UK_VERSION", "2.88.14") def get_app_name(us_version: str, uk_version: str) -> str: @@ -49,7 +49,7 @@ def get_app_name(us_version: str, uk_version: str) -> str: .pip_install( f"policyengine-us=={US_VERSION}", f"policyengine-uk=={UK_VERSION}", - "policyengine==4.4.3", + "policyengine==0.13.0", "tables>=3.10.2", "logfire", ) diff --git a/projects/policyengine-api-simulation/src/modal/gateway/endpoints.py b/projects/policyengine-api-simulation/src/modal/gateway/endpoints.py index cec9fd478..be42b648e 100644 --- a/projects/policyengine-api-simulation/src/modal/gateway/endpoints.py +++ b/projects/policyengine-api-simulation/src/modal/gateway/endpoints.py @@ -43,10 +43,10 @@ "us": { "enhanced_cps": "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@1.110.12", "enhanced_cps_2024": "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@1.110.12", - "cps": "hf://policyengine/policyengine-us-data/cps_2023.h5@1.77.0", - "cps_2023": "hf://policyengine/policyengine-us-data/cps_2023.h5@1.77.0", - "pooled_cps": "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5@1.77.0", - "pooled_3_year_cps_2023": "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5@1.77.0", + "cps": "hf://policyengine/policyengine-us-data/cps_2023.h5@1.110.12", + "cps_2023": "hf://policyengine/policyengine-us-data/cps_2023.h5@1.110.12", + "pooled_cps": "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5@1.110.12", + "pooled_3_year_cps_2023": "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5@1.110.12", }, "uk": { "enhanced_frs": "hf://policyengine/policyengine-uk-data-private/enhanced_frs_2023_24.h5@1.40.3", diff --git a/projects/policyengine-api-simulation/src/modal/simulation.py b/projects/policyengine-api-simulation/src/modal/simulation.py index d9a082416..549ea127c 100644 --- a/projects/policyengine-api-simulation/src/modal/simulation.py +++ b/projects/policyengine-api-simulation/src/modal/simulation.py @@ -10,41 +10,16 @@ import logging import os import tempfile -import importlib -from typing import Any, Iterator +from typing import Iterator -# policyengine.core is imported for every simulation. Without this guard, -# importing the package pulls both country modules into the process; a US run -# can then fail before it starts if UK private-data credentials are absent. -os.environ.setdefault("POLICYENGINE_SKIP_COUNTRY_IMPORTS", "1") +# Module-level imports - these are SNAPSHOTTED at image build time +from policyengine.simulation import Simulation, SimulationOptions -try: - from src.modal.telemetry import split_internal_payload -except ModuleNotFoundError: - from modal.telemetry import split_internal_payload +from src.modal.telemetry import split_internal_payload logger = logging.getLogger(__name__) -DEFAULT_YEAR = 2026 -DATASET_ALIASES = { - "us": { - "enhanced_cps": "enhanced_cps_2024", - "enhanced_cps_2024": "enhanced_cps_2024", - "gs://policyengine-us-data/enhanced_cps_2024.h5": "enhanced_cps_2024", - "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5": "enhanced_cps_2024", - "cps_small": "cps_small_2024", - "cps_small_2024": "cps_small_2024", - }, - "uk": { - "enhanced_frs": "enhanced_frs_2023_24", - "enhanced_frs_2023_24": "enhanced_frs_2023_24", - "frs": "frs_2023_24", - "frs_2023_24": "frs_2023_24", - }, -} - - def _normalize_credentials_blob(creds_json: str) -> str: """Return the raw JSON blob, decoding the outer escape if present. @@ -140,237 +115,6 @@ def run_simulation_impl(params: dict) -> dict: return _run_simulation_impl_core(params) -def _parse_year(params: dict[str, Any]) -> int: - value = params.get("time_period") or params.get("year") or DEFAULT_YEAR - return int(value) - - -def _normalise_period_key(period_key: Any) -> str: - """Convert legacy ``start.stop`` period keys to v4 effective dates.""" - text = str(period_key) - parts = text.split(".") - if len(parts) > 1 and len(parts[0]) == 10: - return parts[0] - return text - - -def _normalise_reform(reform: dict[str, Any] | None) -> dict[str, Any] | None: - if not reform: - return None - normalised: dict[str, Any] = {} - for parameter, value in reform.items(): - if isinstance(value, dict): - normalised[parameter] = { - _normalise_period_key(period): period_value - for period, period_value in value.items() - } - else: - normalised[parameter] = value - return normalised - - -def _resolve_dataset_name( - country: str, requested_data: str | None, subsample: int | None -) -> str: - if requested_data is None: - return "enhanced_cps_2024" if country == "us" else "enhanced_frs_2023_24" - - requested = requested_data.split("@", maxsplit=1)[0] - return DATASET_ALIASES.get(country, {}).get(requested, requested_data) - - -def _microframe_like(frame, weights: str): - from microdf import MicroDataFrame - - return MicroDataFrame(frame.copy(), weights=weights) - - -def _person_group_column(person, entity: str) -> str: - prefixed = f"person_{entity}_id" - if prefixed in person.columns: - return prefixed - return f"{entity}_id" - - -def _subsample_us_dataset(dataset, subsample: int | None): - if not subsample: - return dataset - - from policyengine.tax_benefit_models.us.datasets import ( - PolicyEngineUSDataset, - USYearData, - ) - - dataset.load() - data = dataset.data - household = data.household.head(int(subsample)).copy() - household_ids = set(household["household_id"]) - - person_household_col = _person_group_column(data.person, "household") - person = data.person[data.person[person_household_col].isin(household_ids)].copy() - - def group_subset(entity: str): - person_col = _person_group_column(person, entity) - entity_id_col = f"{entity}_id" - ids = set(person[person_col]) - frame = getattr(data, entity) - return frame[frame[entity_id_col].isin(ids)].copy() - - subset_data = USYearData( - person=_microframe_like(person, "person_weight"), - marital_unit=_microframe_like( - group_subset("marital_unit"), "marital_unit_weight" - ), - family=_microframe_like(group_subset("family"), "family_weight"), - spm_unit=_microframe_like(group_subset("spm_unit"), "spm_unit_weight"), - tax_unit=_microframe_like(group_subset("tax_unit"), "tax_unit_weight"), - household=_microframe_like(household, "household_weight"), - ) - subset_path = os.path.join( - os.environ.get("POLICYENGINE_DATA_FOLDER", "/tmp/policyengine-data"), - f"{dataset.id}_subsample_{subsample}.h5", - ) - return PolicyEngineUSDataset( - id=f"{dataset.id}_subsample_{subsample}", - name=f"{dataset.name} subsample {subsample}", - description=dataset.description, - filepath=subset_path, - year=dataset.year, - is_output_dataset=dataset.is_output_dataset, - data=subset_data, - ) - - -def _country_module(country: str): - country = country.lower() - if country == "us": - return importlib.import_module("policyengine.tax_benefit_models.us") - if country == "uk": - return importlib.import_module("policyengine.tax_benefit_models.uk") - raise ValueError(f"Unsupported country: {country}") - - -def _load_dataset(params: dict[str, Any]): - country = params.get("country", "us").lower() - year = _parse_year(params) - country_module = _country_module(country) - dataset_name = _resolve_dataset_name( - country, params.get("data"), params.get("subsample") - ) - datasets = country_module.ensure_datasets( - datasets=[dataset_name], - years=[year], - data_folder=os.environ.get( - "POLICYENGINE_DATA_FOLDER", "/tmp/policyengine-data" - ), - ) - dataset = next(iter(datasets.values())) - if country == "us": - return _subsample_us_dataset(dataset, params.get("subsample")) - return dataset - - -def _build_simulation(params: dict[str, Any], policy: dict[str, Any] | None): - from policyengine.core import Simulation - - country = params.get("country", "us").lower() - country_module = _country_module(country) - dataset = _load_dataset(params) - return Simulation( - dataset=dataset, - tax_benefit_model_version=country_module.model, - policy=policy, - ) - - -def _change_sum(baseline, reform, variable: str, entity: str | None = None) -> float: - from policyengine.outputs import ChangeAggregate, ChangeAggregateType - - output = ChangeAggregate( - baseline_simulation=baseline, - reform_simulation=reform, - variable=variable, - entity=entity, - aggregate_type=ChangeAggregateType.SUM, - ) - output.run() - return float(output.result) - - -def _try_change_sum( - baseline, reform, variable: str, entity: str | None = None -) -> float: - try: - return _change_sum(baseline, reform, variable, entity) - except Exception: - logger.warning("Unable to calculate change for %s", variable, exc_info=True) - return 0.0 - - -def _budget_result(country: str, baseline, reform) -> dict[str, float]: - tax_revenue_impact = _try_change_sum( - baseline, reform, "household_tax", entity="household" - ) - benefit_spending_impact = _try_change_sum( - baseline, reform, "household_benefits", entity="household" - ) - budgetary_impact = tax_revenue_impact - benefit_spending_impact - result = { - "tax_revenue_impact": tax_revenue_impact, - "benefit_spending_impact": benefit_spending_impact, - "budgetary_impact": budgetary_impact, - } - if country == "us": - result["state_tax_revenue_impact"] = _try_change_sum( - baseline, - reform, - "household_state_income_tax", - entity="tax_unit", - ) - return result - - -def _poverty_result(country: str, baseline, reform) -> dict[str, list[dict[str, Any]]]: - country_module = _country_module(country) - impact = country_module.economic_impact_analysis(baseline, reform) - baseline_poverty = impact.baseline_poverty - reform_poverty = impact.reform_poverty - - return { - "baseline": baseline_poverty.dataframe.to_dict("records"), - "reform": reform_poverty.dataframe.to_dict("records"), - } - - -def _analysis_result(country: str, baseline, reform) -> dict[str, Any]: - country_module = _country_module(country) - analysis = country_module.economic_impact_analysis(baseline, reform) - - return { - "decile_impacts": analysis.decile_impacts.dataframe.to_dict("records"), - "program_statistics": analysis.program_statistics.dataframe.to_dict("records"), - "poverty": { - "baseline": analysis.baseline_poverty.dataframe.to_dict("records"), - "reform": analysis.reform_poverty.dataframe.to_dict("records"), - }, - "inequality": { - "baseline": _inequality_summary(analysis.baseline_inequality), - "reform": _inequality_summary(analysis.reform_inequality), - }, - } - - -def _inequality_summary(inequality) -> dict[str, Any]: - return { - "income_variable": inequality.income_variable, - "entity": inequality.entity, - "gini": inequality.gini, - "top_10_share": inequality.top_10_share, - "top_1_share": inequality.top_1_share, - "bottom_50_share": inequality.bottom_50_share, - } - - def _run_simulation_impl_core(params: dict) -> dict: simulation_params, telemetry, metadata = split_internal_payload(params) @@ -383,21 +127,17 @@ def _run_simulation_impl_core(params: dict) -> dict: if metadata: logger.info("Received simulation metadata keys: %s", sorted(metadata)) - country = simulation_params.get("country", "us").lower() - baseline_policy = _normalise_reform(simulation_params.get("baseline")) - reform_policy = _normalise_reform(simulation_params.get("reform")) + # Validate and create simulation options + options = SimulationOptions.model_validate(simulation_params) + logger.info("Initialising simulation from input") - logger.info("Initialising baseline and reform simulations") - baseline = _build_simulation(simulation_params, baseline_policy) - reform = _build_simulation(simulation_params, reform_policy) + # Create simulation instance + simulation = Simulation(**options.model_dump()) + logger.info("Calculating comparison") - logger.info("Calculating economic impact") - analysis = _analysis_result(country, baseline, reform) - analysis["budget"] = _budget_result(country, baseline, reform) - analysis["metadata"] = { - "country": country, - "year": _parse_year(simulation_params), - "dataset": getattr(baseline.dataset, "filepath", None), - } + # Run the economy comparison calculation + result = simulation.calculate_economy_comparison() logger.info("Comparison complete") - return analysis + + # Use mode='json' to ensure numpy arrays are converted to lists + return result.model_dump(mode="json") diff --git a/projects/policyengine-api-simulation/src/policyengine_api_simulation/simulation.py b/projects/policyengine-api-simulation/src/policyengine_api_simulation/simulation.py index a57064cc0..cc81f6be4 100644 --- a/projects/policyengine-api-simulation/src/policyengine_api_simulation/simulation.py +++ b/projects/policyengine-api-simulation/src/policyengine_api_simulation/simulation.py @@ -1,23 +1,31 @@ from typing import Annotated import os from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel +from policyengine.simulation import SimulationOptions, Simulation +from policyengine.outputs.macro.comparison.calculate_economy_comparison import ( + EconomyComparison, +) from pathlib import Path import logging -try: - from modal.simulation import run_simulation_impl -except ModuleNotFoundError: - from src.modal.simulation import run_simulation_impl - logger = logging.getLogger(__file__) def create_router(): router = APIRouter() - @router.post("/simulate/economy/comparison", response_model=dict) - async def simulate(parameters: dict) -> dict: + @router.post("/simulate/economy/comparison", response_model=EconomyComparison) + async def simulate(parameters: SimulationOptions) -> EconomyComparison: + model = SimulationOptions.model_validate(parameters) + logger.info("Initialising simulation from input") + simulation = Simulation(**model.model_dump()) logger.info("Calculating comparison") - return run_simulation_impl(parameters) + result = ( + simulation.calculate_economy_comparison() # pyright: ignore [reportAttributeAccessIssue] + ) + logger.info("Comparison complete") + + return result return router diff --git a/projects/policyengine-api-simulation/uv.lock b/projects/policyengine-api-simulation/uv.lock index d48d1e9be..d24da0e81 100644 --- a/projects/policyengine-api-simulation/uv.lock +++ b/projects/policyengine-api-simulation/uv.lock @@ -161,6 +161,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/e2/d5b09cec0383381026c41fd071ae6a9342dfd70d0584aeae672e77dda82f/blosc2-4.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a72cc1fdc74744723092ccb63d03cf49c64f911450d2c9296182ce7bcda45d04", size = 3147727, upload-time = "2026-03-03T11:04:57.506Z" }, ] +[[package]] +name = "caugetch" +version = "0.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/ec/519cb37e3e58e23a5b02a74049128f6e701ccd8892b0cebecf701fac6177/caugetch-0.0.1.tar.gz", hash = "sha256:6f6ddb3b928fa272071b02aabb3342941cd99992f27413ba8c189eb4dc3e33b0", size = 2071, upload-time = "2019-10-15T22:39:49.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/33/64fee4626ec943c2d0c4eee31c784dab8452dfe014916190730880d4ea62/caugetch-0.0.1-py3-none-any.whl", hash = "sha256:ee743dcbb513409cd24cfc42435418073683ba2f4bb7ee9f8440088a47d59277", size = 3439, upload-time = "2019-10-15T22:39:47.122Z" }, +] + [[package]] name = "cbor2" version = "5.9.0" @@ -258,6 +267,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] +[[package]] +name = "clipboard" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyperclip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/38/17f3885713d0f39994563029942b1d31c93d4e56d80da505abfbfb3a3bc4/clipboard-0.0.4.tar.gz", hash = "sha256:a72a78e9c9bf68da1c3f29ee022417d13ec9e3824b511559fd2b702b1dd5b817", size = 1713, upload-time = "2014-05-22T12:49:08.683Z" } + [[package]] name = "colorama" version = "0.4.6" @@ -366,6 +384,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -561,6 +588,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" }, ] +[[package]] +name = "getpass4" +version = "0.0.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "caugetch" }, + { name = "clipboard" }, + { name = "colorama" }, + { name = "pyperclip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f9/312f84afc384f693d02eb4ff7306a7268577a8b808aa08f0124c9abba683/getpass4-0.0.14.1.tar.gz", hash = "sha256:80aa4e3a665f2eccc6cda3ee22125eeb5c6338e91c40c4fd010b3c94c7aa4d3a", size = 5078, upload-time = "2021-11-28T17:08:47.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/d3/ea114aba31f76418b2162e811793cde2e822c9d9ea8ca98d67f9e1f1bde6/getpass4-0.0.14.1-py3-none-any.whl", hash = "sha256:6642c11fb99db1bec90b963e863ec71cdb0b8888000f5089c6377bfbf833f8a9", size = 8683, upload-time = "2021-11-28T17:08:45.468Z" }, +] + [[package]] name = "google-api-core" version = "2.30.3" @@ -596,6 +638,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" }, ] +[[package]] +name = "google-cloud-core" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/24/6ca08b0a03c7b0c620427503ab00353a4ae806b848b93bcea18b6b76fde6/google_cloud_core-2.5.1.tar.gz", hash = "sha256:3dc94bdec9d05a31d9f355045ed0f369fbc0d8c665076c734f065d729800f811", size = 36078, upload-time = "2026-03-30T22:50:08.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/d9/5bb050cb32826466aa9b25f79e2ca2879fe66cb76782d4ed798dd7506151/google_cloud_core-2.5.1-py3-none-any.whl", hash = "sha256:ea62cdf502c20e3e14be8a32c05ed02113d7bef454e40ff3fab6fe1ec9f1f4e7", size = 29452, upload-time = "2026-03-30T22:48:31.567Z" }, +] + [[package]] name = "google-cloud-monitoring" version = "2.30.0" @@ -612,6 +667,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/c8/666c21c470b9d6fd62ac9ee74dc265419975228f9b16f8ad72ec22e8d98b/google_cloud_monitoring-2.30.0-py3-none-any.whl", hash = "sha256:2729f3b88a4798b7757b1d9d31b6cb562bb3544e8173765e4e5cd44d8685b1ed", size = 391367, upload-time = "2026-03-26T22:15:04.088Z" }, ] +[[package]] +name = "google-cloud-storage" +version = "3.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/47/205eb8e9a1739b5345843e5a425775cbdc472cc38e7eda082ba5b8d02450/google_cloud_storage-3.10.1.tar.gz", hash = "sha256:97db9aa4460727982040edd2bd13ff3d5e2260b5331ad22895802da1fc2a5286", size = 17309950, upload-time = "2026-03-23T09:35:23.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/ff/ca9ab2417fa913d75aae38bf40bf856bb2749a604b2e0f701b37cfcd23cc/google_cloud_storage-3.10.1-py3-none-any.whl", hash = "sha256:a72f656759b7b99bda700f901adcb3425a828d4a29f911bc26b3ea79c5b1217f", size = 324453, upload-time = "2026-03-23T09:35:21.368Z" }, +] + [[package]] name = "google-cloud-trace" version = "1.19.0" @@ -628,6 +700,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/91/0090acafa7d2caf1bf0d7222d42935e118164a539f9f9a00a814afa63fa1/google_cloud_trace-1.19.0-py3-none-any.whl", hash = "sha256:59604c4c775c40af31b367df6bada0af34518cc35ac8cfedecd43898a120c51d", size = 108454, upload-time = "2026-03-26T22:14:32.631Z" }, ] +[[package]] +name = "google-crc32c" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/d1/b1ea14b93b6b78f57fc580125de44e9f593ab88dd2460f1a8a8d18f74754/google_resumable_media-2.8.2.tar.gz", hash = "sha256:f3354a182ebd193ae3f42e3ef95e6c9b10f128320de23ac7637236713b1acd70", size = 2164510, upload-time = "2026-03-30T23:34:25.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/f8/50bfaf4658431ff9de45c5c3935af7ab01157a4903c603cd0eee6e78e087/google_resumable_media-2.8.2-py3-none-any.whl", hash = "sha256:82b6d8ccd11765268cdd2a2123f417ec806b8eef3000a9a38dfe3033da5fb220", size = 81511, upload-time = "2026-03-30T23:34:09.671Z" }, +] + [[package]] name = "googleapis-common-protos" version = "1.74.0" @@ -953,33 +1050,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/73/04df8a6fa66d43a9fd45c30f283cc4afff17da671886e451d52af60bdc7e/jsonpickle-4.1.1-py3-none-any.whl", hash = "sha256:bb141da6057898aa2438ff268362b126826c812a1721e31cf08a6e142910dc91", size = 47125, upload-time = "2025-06-02T20:36:08.647Z" }, ] -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, -] - [[package]] name = "logfire" version = "4.6.0" @@ -1613,21 +1683,19 @@ wheels = [ [[package]] name = "policyengine" -version = "4.4.3" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jsonschema" }, + { name = "diskcache" }, + { name = "getpass4" }, + { name = "google-cloud-storage" }, { name = "microdf-python" }, - { name = "packaging" }, - { name = "pandas" }, - { name = "psutil" }, + { name = "policyengine-core" }, + { name = "policyengine-uk" }, + { name = "policyengine-us" }, { name = "pydantic" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/f1/1164557ced4db47a9fddf7e5d301a30578de0cb1fb284c5803c238916df7/policyengine-4.4.3.tar.gz", hash = "sha256:555f1461b11ba5d934d5e432e806fcf6d31d2e41a742cd010e9c3c46acd0652e", size = 522387, upload-time = "2026-05-11T19:53:39.493Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/16/f42e21772742c594194b53302e798c83065c45f34df9b04578540488149d/policyengine-4.4.3-py3-none-any.whl", hash = "sha256:a12e8ed43a4b0f205d853853ccc678b2b8857381b8111336e711e35d8e6b1336", size = 160790, upload-time = "2026-05-11T19:53:37.569Z" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/a0/f3/eeea7dab690e46cd91533691eef41097f1c2e9eb729d4f70408b865c750e/policyengine-0.13.0.tar.gz", hash = "sha256:15cf9f0ff0801c8cf12fbaeabe5e03e3cc2822cd3436b08553cf2ef0e00673ba", size = 230501, upload-time = "2026-04-08T13:41:59.62Z" } [[package]] name = "policyengine-core" @@ -1733,8 +1801,8 @@ requires-dist = [ { name = "openapi-python-client", marker = "extra == 'build'", specifier = ">=0.21.6" }, { name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.51b0,<0.52" }, { name = "opentelemetry-instrumentation-sqlalchemy", specifier = ">=0.51b0,<0.52" }, - { name = "policyengine", specifier = "==4.4.3" }, - { name = "policyengine-core", specifier = ">=3.26.1" }, + { name = "policyengine", specifier = "==0.13.0" }, + { name = "policyengine-core", specifier = ">=3.23.5" }, { name = "policyengine-fastapi", editable = "../../libs/policyengine-fastapi" }, { name = "policyengine-uk", specifier = "==2.88.14" }, { name = "policyengine-us", specifier = "==1.690.7" }, @@ -2006,6 +2074,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + [[package]] name = "pyright" version = "1.1.409" @@ -2146,19 +2223,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, ] -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, -] - [[package]] name = "requests" version = "2.33.1" @@ -2224,43 +2288,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184, upload-time = "2025-11-05T21:41:27.396Z" }, ] -[[package]] -name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, -] - [[package]] name = "ruamel-yaml" version = "0.19.1" diff --git a/projects/policyengine-apis-integ/tests/simulation/conftest.py b/projects/policyengine-apis-integ/tests/simulation/conftest.py index fa2aac776..f842b755b 100644 --- a/projects/policyengine-apis-integ/tests/simulation/conftest.py +++ b/projects/policyengine-apis-integ/tests/simulation/conftest.py @@ -33,7 +33,7 @@ class Settings(BaseSettings): gateway_auth_required: bool = False timeout_in_millis: int = 600_000 # 10 minutes for full simulations poll_interval_seconds: float = 5.0 - us_model_version: str = "1.562.3" + us_model_version: str = "1.690.7" uk_model_version: str = "2.88.14" model_config = SettingsConfigDict( diff --git a/projects/policyengine-apis-integ/tests/simulation/test_budget_window.py b/projects/policyengine-apis-integ/tests/simulation/test_budget_window.py index 4e79dc96d..d1fedd1a6 100644 --- a/projects/policyengine-apis-integ/tests/simulation/test_budget_window.py +++ b/projects/policyengine-apis-integ/tests/simulation/test_budget_window.py @@ -14,9 +14,28 @@ from policyengine_api_simulation_client.models import ( BudgetWindowBatchSubmitResponse, BudgetWindowResult, + SingleYearMacroOutput, ) from policyengine_api_simulation_client.types import Unset +SINGLE_YEAR_MACRO_OUTPUT_KEYS = { + "budget", + "detailed_budget", + "decile", + "inequality", + "poverty", + "poverty_by_gender", + "poverty_by_race", + "intra_decile", + "wealth_decile", + "intra_wealth_decile", + "labor_supply_response", + "constituency_impact", + "local_authority_impact", + "congressional_district_impact", + "cliff_impact", +} + @pytest.mark.beta_only def test_budget_window_multi_year_batch_completes( @@ -61,11 +80,24 @@ def test_budget_window_multi_year_batch_completes( assert result.end_year == budget_window_years[-1] assert result.window_size == len(budget_window_years) assert result.years == budget_window_years + result_payload = result.to_dict() + assert "annualImpacts" not in result_payload + assert "outputsByYear" in result_payload + outputs_by_year = result.outputs_by_year assert not isinstance(outputs_by_year, Unset) assert outputs_by_year.additional_keys == budget_window_years - assert all( - outputs_by_year[year].budget.budgetary_impact is not None - for year in budget_window_years - ) + for year in budget_window_years: + output = outputs_by_year[year] + assert isinstance(output, SingleYearMacroOutput) + output_payload = output.to_dict() + assert SINGLE_YEAR_MACRO_OUTPUT_KEYS <= set(output_payload) + assert output.budget.budgetary_impact is not None + assert isinstance(output_payload["decile"], dict) + assert isinstance(output_payload["inequality"], dict) + assert isinstance(output_payload["poverty"], dict) + assert isinstance(output_payload["poverty_by_gender"], dict) + assert isinstance(output_payload["intra_decile"], dict) + assert isinstance(output_payload["labor_supply_response"], dict) + assert isinstance(result.totals.budgetary_impact, int | float)