Skip to content

Commit 10af713

Browse files
committed
fix: package simulation runtime for integration
1 parent c84ea3b commit 10af713

42 files changed

Lines changed: 2557 additions & 1394 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/scripts/modal-deploy-app.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
#!/bin/bash
22
# Deploy simulation API to Modal
33
# Usage: ./modal-deploy-app.sh <modal-environment>
4-
# Required env vars: POLICYENGINE_US_VERSION, POLICYENGINE_UK_VERSION
4+
# Required env vars: POLICYENGINE_VERSION, POLICYENGINE_US_VERSION, POLICYENGINE_UK_VERSION
55
# These should come from the bundled policyengine.py release manifest.
66
#
77
# Deploys two apps:
88
# 1. policyengine-simulation-gateway - Stable gateway with fixed URL
9-
# 2. policyengine-simulation-us{X}-uk{Y} - Versioned simulation app
9+
# 2. policyengine-simulation-py{X} - Versioned simulation app
1010

1111
set -euo pipefail
1212

1313
MODAL_ENV="${1:?Modal environment required}"
1414

1515
# Generate versioned simulation app name (dots replaced with dashes for URL safety)
16-
US_VERSION_SAFE="${POLICYENGINE_US_VERSION//./-}"
17-
UK_VERSION_SAFE="${POLICYENGINE_UK_VERSION//./-}"
18-
SIMULATION_APP_NAME="policyengine-simulation-us${US_VERSION_SAFE}-uk${UK_VERSION_SAFE}"
16+
POLICYENGINE_VERSION_SAFE="${POLICYENGINE_VERSION//./-}"
17+
SIMULATION_APP_NAME="policyengine-simulation-py${POLICYENGINE_VERSION_SAFE}"
1918

2019
echo "========================================"
2120
echo "Deploying to Modal environment: $MODAL_ENV"
21+
echo " policyengine.py version: ${POLICYENGINE_VERSION}"
2222
echo " US version: ${POLICYENGINE_US_VERSION}"
2323
echo " UK version: ${POLICYENGINE_UK_VERSION}"
2424
echo "========================================"
@@ -41,6 +41,7 @@ echo ""
4141
echo "Step 3: Updating version registries..."
4242
uv run python -m src.modal.utils.update_version_registry \
4343
--app-name "$SIMULATION_APP_NAME" \
44+
--policyengine-version "${POLICYENGINE_VERSION}" \
4445
--us-version "${POLICYENGINE_US_VERSION}" \
4546
--uk-version "${POLICYENGINE_UK_VERSION}" \
4647
--environment "$MODAL_ENV"

.github/workflows/modal-deploy.reusable.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ on:
1515
simulation_api_url:
1616
description: 'The deployed simulation API URL'
1717
value: ${{ jobs.deploy.outputs.simulation_api_url }}
18+
policyengine_version:
19+
description: 'The deployed policyengine.py package version'
20+
value: ${{ jobs.deploy.outputs.policyengine_version }}
1821
us_version:
1922
description: 'The deployed policyengine-us package version'
2023
value: ${{ jobs.deploy.outputs.us_version }}
@@ -35,6 +38,7 @@ jobs:
3538
environment: ${{ inputs.environment }}
3639
outputs:
3740
simulation_api_url: ${{ steps.get-url.outputs.simulation_api_url }}
41+
policyengine_version: ${{ steps.versions.outputs.policyengine_version }}
3842
us_version: ${{ steps.versions.outputs.us_version }}
3943
us_data_version: ${{ steps.versions.outputs.us_data_version }}
4044
uk_version: ${{ steps.versions.outputs.uk_version }}
@@ -82,9 +86,10 @@ jobs:
8286
env:
8387
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
8488
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
89+
POLICYENGINE_VERSION: ${{ steps.versions.outputs.policyengine_version }}
8590
POLICYENGINE_US_VERSION: ${{ steps.versions.outputs.us_version }}
8691
POLICYENGINE_UK_VERSION: ${{ steps.versions.outputs.uk_version }}
87-
run: ../../.github/scripts/modal-deploy-app.sh "${{ inputs.modal_environment }}" src/modal/app.py
92+
run: ../../.github/scripts/modal-deploy-app.sh "${{ inputs.modal_environment }}"
8893

8994
- name: Get deployed URL
9095
id: get-url

projects/policyengine-api-simulation/fixtures/gateway/test_endpoints.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,69 @@
22

33
import pytest
44

5+
TEST_APP_RELEASE_BUNDLE = {
6+
"app_name": "policyengine-simulation-py4-10-0",
7+
"policyengine_version": "4.10.0",
8+
"us": {
9+
"model_version": "1.500.0",
10+
"data_version": "1.110.12",
11+
"default_dataset": "enhanced_cps_2024",
12+
"default_dataset_uri": "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@1.110.12",
13+
"dataset_uris": {
14+
"enhanced_cps_2024": "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5@1.110.12",
15+
"cps_2023": "hf://policyengine/policyengine-us-data/cps_2023.h5@1.110.12",
16+
"pooled_3_year_cps_2023": "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5@1.110.12",
17+
},
18+
"dataset_aliases": {
19+
"enhanced_cps": "enhanced_cps_2024",
20+
"enhanced_cps_2024": "enhanced_cps_2024",
21+
"cps": "cps_2023",
22+
"cps_2023": "cps_2023",
23+
"pooled_cps": "pooled_3_year_cps_2023",
24+
"pooled_3_year_cps_2023": "pooled_3_year_cps_2023",
25+
},
26+
},
27+
"uk": {
28+
"model_version": "2.66.0",
29+
"data_version": "1.40.3",
30+
"default_dataset": "enhanced_frs_2023_24",
31+
"default_dataset_uri": "hf://policyengine/policyengine-uk-data-private/enhanced_frs_2023_24.h5@1.40.3",
32+
"dataset_uris": {
33+
"enhanced_frs_2023_24": "hf://policyengine/policyengine-uk-data-private/enhanced_frs_2023_24.h5@1.40.3",
34+
"frs_2023_24": "hf://policyengine/policyengine-uk-data-private/frs_2023_24.h5@1.40.3",
35+
},
36+
"dataset_aliases": {
37+
"enhanced_frs": "enhanced_frs_2023_24",
38+
"enhanced_frs_2023_24": "enhanced_frs_2023_24",
39+
"frs": "frs_2023_24",
40+
"frs_2023_24": "frs_2023_24",
41+
},
42+
},
43+
}
44+
45+
TEST_APP_NAMES = (
46+
"policyengine-simulation-py4-10-0",
47+
"policyengine-simulation-py3-9-0",
48+
)
49+
50+
51+
def resolve_test_dataset_uri(country: str, dataset: str | None) -> str | None:
52+
if dataset is None:
53+
return None
54+
if "://" in dataset:
55+
return dataset
56+
country_bundle = TEST_APP_RELEASE_BUNDLE[country]
57+
dataset_name, revision = (
58+
dataset.rsplit("@", maxsplit=1) if "@" in dataset else (dataset, None)
59+
)
60+
dataset_name = country_bundle["dataset_aliases"].get(dataset_name, dataset_name)
61+
dataset_uri = country_bundle["dataset_uris"].get(dataset_name, dataset_name)
62+
if revision is not None and dataset_uri == dataset_name:
63+
return dataset
64+
if revision is not None and dataset_uri.startswith("hf://"):
65+
dataset_uri = f"{dataset_uri.rsplit('@', maxsplit=1)[0]}@{revision}"
66+
return dataset_uri
67+
568

669
class MockDict:
770
"""Mock for Modal.Dict to simulate version registry."""
@@ -107,7 +170,11 @@ def mock_modal(monkeypatch):
107170
from src.modal.gateway import endpoints
108171

109172
mock_func = MockFunction()
110-
mock_dicts = {}
173+
mock_dicts = {
174+
"simulation-api-app-release-bundles": {
175+
app_name: TEST_APP_RELEASE_BUNDLE for app_name in TEST_APP_NAMES
176+
}
177+
}
111178
MockFunctionCall.registry = {}
112179
MockFunctionCall.from_id_errors = {}
113180

@@ -134,6 +201,20 @@ class MockModal:
134201

135202
monkeypatch.setattr(endpoints, "modal", MockModal)
136203
monkeypatch.setattr(budget_window_state, "modal", MockModal)
204+
monkeypatch.setattr(
205+
endpoints,
206+
"with_hf_revision",
207+
lambda dataset_uri, revision: (
208+
f"{dataset_uri.rsplit('@', maxsplit=1)[0]}@{revision}"
209+
if dataset_uri.startswith("hf://")
210+
else dataset_uri
211+
),
212+
)
213+
monkeypatch.setattr(
214+
endpoints,
215+
"validate_hf_dataset_uri",
216+
lambda dataset_uri: dataset_uri,
217+
)
137218

138219
return {
139220
"func": mock_func,

projects/policyengine-api-simulation/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
authors = [
1010
{name = "PolicyEngine", email = "hello@policyengine.org"},
1111
]
12-
license = {file = "../../LICENSE"}
12+
license = "AGPL-3.0-only"
1313
requires-python = ">=3.13,<3.14"
1414
dependencies = [
1515
"opentelemetry-instrumentation-sqlalchemy (>=0.51b0,<0.52)",

projects/policyengine-api-simulation/src/modal/app.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
PolicyEngine Simulation - Versioned Modal App
33
44
This app contains the heavy simulation workload with snapshotted models.
5-
Each deployment creates a versioned app (e.g., policyengine-simulation-us1-459-0-uk2-65-9).
5+
Each deployment creates a versioned app (e.g., policyengine-simulation-py4-10-0).
66
77
The gateway app (policyengine-simulation-gateway) routes requests to these versioned apps.
88
"""
@@ -13,7 +13,7 @@
1313
from src.modal._image_setup import snapshot_models
1414
from src.modal.dependency_pins import project_dependency_pin
1515
from src.modal.logging_redaction import redact_params_for_logging
16-
from src.modal.release_bundle import get_bundled_country_model_version
16+
from policyengine_api_simulation.release_bundle import get_bundled_country_model_version
1717

1818
POLICYENGINE_VERSION = os.environ.get("POLICYENGINE_VERSION") or project_dependency_pin(
1919
"policyengine"
@@ -31,20 +31,19 @@
3131
) or get_bundled_country_model_version("uk")
3232

3333

34-
def get_app_name(us_version: str, uk_version: str) -> str:
34+
def get_app_name(policyengine_version: str) -> str:
3535
"""
36-
Generate versioned app name from package versions.
36+
Generate versioned app name from the policyengine.py package version.
3737
3838
Replaces dots with dashes for URL safety.
39-
Example: us1.459.0, uk2.65.9 -> policyengine-simulation-us1-459-0-uk2-65-9
39+
Example: 4.10.0 -> policyengine-simulation-py4-10-0
4040
"""
41-
us_safe = us_version.replace(".", "-")
42-
uk_safe = uk_version.replace(".", "-")
43-
return f"policyengine-simulation-us{us_safe}-uk{uk_safe}"
41+
policyengine_safe = policyengine_version.replace(".", "-")
42+
return f"policyengine-simulation-py{policyengine_safe}"
4443

4544

4645
# App name can be overridden via environment variable, otherwise generated from versions
47-
APP_NAME = os.environ.get("MODAL_APP_NAME", get_app_name(US_VERSION, UK_VERSION))
46+
APP_NAME = os.environ.get("MODAL_APP_NAME", get_app_name(POLICYENGINE_VERSION))
4847

4948
# App definition with versioned name
5049
app = modal.App(APP_NAME)
@@ -67,7 +66,11 @@ def get_app_name(us_version: str, uk_version: str) -> str:
6766
"tables>=3.10.2",
6867
"logfire",
6968
)
70-
.add_local_python_source("src.modal", copy=True)
69+
.add_local_python_source(
70+
"src.modal",
71+
"policyengine_api_simulation",
72+
copy=True,
73+
)
7174
.run_function(snapshot_models)
7275
)
7376

@@ -106,7 +109,7 @@ def run_simulation(params: dict) -> dict:
106109
"""
107110
import logfire
108111

109-
from src.modal.simulation import run_simulation_impl
112+
from policyengine_api_simulation.simulation_runtime import run_simulation_impl
110113

111114
configure_logfire()
112115

projects/policyengine-api-simulation/src/modal/gateway/app.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
# the auth module at runtime here.
2727
"cryptography>=41.0.0",
2828
)
29-
.add_local_python_source("src.modal", copy=True)
29+
.add_local_python_source(
30+
"src.modal",
31+
"policyengine_api_simulation",
32+
copy=True,
33+
)
3034
.add_local_python_source("policyengine_fastapi", copy=True)
3135
)
3236

0 commit comments

Comments
 (0)