Skip to content
This repository was archived by the owner on Jun 14, 2026. It is now read-only.

Commit b627e16

Browse files
committed
Add mp-300k artifact gates
1 parent 1a9a1eb commit b627e16

5 files changed

Lines changed: 1188 additions & 0 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
name: mp-300k Artifact Gates
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
inputs:
10+
artifact_archive_url:
11+
description: URL to a .zip, .tar, or .tar.gz artifact bundle containing manifest.json.
12+
required: true
13+
type: string
14+
ecps_comparison_url:
15+
description: Optional URL to precomputed PE-native eCPS comparison JSON.
16+
required: false
17+
type: string
18+
runtime_smoke_url:
19+
description: Optional URL to runtime smoke benchmark JSON.
20+
required: false
21+
type: string
22+
benchmark_manifest_url:
23+
description: Optional URL to the frozen microsimulation benchmark manifest.
24+
required: false
25+
type: string
26+
target_period:
27+
description: PolicyEngine period to validate.
28+
required: false
29+
default: "2024"
30+
type: string
31+
runtime_ratio_threshold:
32+
description: Maximum candidate/baseline runtime ratio.
33+
required: false
34+
default: "1.25"
35+
type: string
36+
artifact_size_ratio_threshold:
37+
description: Maximum candidate/baseline H5 size ratio.
38+
required: false
39+
default: "2.0"
40+
type: string
41+
require_ecps_comparison:
42+
description: Keep the eCPS comparison as a blocking gate.
43+
required: false
44+
default: true
45+
type: boolean
46+
workflow_call:
47+
inputs:
48+
artifact_archive_url:
49+
required: true
50+
type: string
51+
ecps_comparison_url:
52+
required: false
53+
type: string
54+
runtime_smoke_url:
55+
required: false
56+
type: string
57+
benchmark_manifest_url:
58+
required: false
59+
type: string
60+
target_period:
61+
required: false
62+
default: "2024"
63+
type: string
64+
runtime_ratio_threshold:
65+
required: false
66+
default: "1.25"
67+
type: string
68+
artifact_size_ratio_threshold:
69+
required: false
70+
default: "2.0"
71+
type: string
72+
require_ecps_comparison:
73+
required: false
74+
default: true
75+
type: boolean
76+
77+
permissions:
78+
contents: read
79+
80+
jobs:
81+
implementation-tests:
82+
if: github.event_name == 'pull_request' || github.event_name == 'push'
83+
runs-on: ubuntu-latest
84+
defaults:
85+
run:
86+
working-directory: microplex-us
87+
steps:
88+
- name: Check out microplex-us
89+
uses: actions/checkout@v4
90+
with:
91+
path: microplex-us
92+
93+
- name: Check out core microplex
94+
uses: actions/checkout@v4
95+
with:
96+
repository: PolicyEngine/microplex
97+
ref: main
98+
path: microplex
99+
100+
- name: Set up Python
101+
uses: actions/setup-python@v5
102+
with:
103+
python-version: "3.13"
104+
105+
- name: Set up uv
106+
uses: astral-sh/setup-uv@v6
107+
108+
- name: Test artifact gate implementation
109+
run: |
110+
uv run --python 3.13 --extra dev --with pydantic --with-editable ../microplex pytest -q \
111+
tests/pipelines/test_mp300k_artifact_gates.py
112+
113+
artifact-gates:
114+
if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call'
115+
runs-on: ubuntu-latest
116+
defaults:
117+
run:
118+
working-directory: microplex-us
119+
steps:
120+
- name: Check out microplex-us
121+
uses: actions/checkout@v4
122+
with:
123+
path: microplex-us
124+
125+
- name: Check out core microplex
126+
uses: actions/checkout@v4
127+
with:
128+
repository: PolicyEngine/microplex
129+
ref: main
130+
path: microplex
131+
132+
- name: Set up Python
133+
uses: actions/setup-python@v5
134+
with:
135+
python-version: "3.13"
136+
137+
- name: Set up uv
138+
uses: astral-sh/setup-uv@v6
139+
140+
- name: Download artifact and evidence
141+
run: |
142+
mkdir -p ../gate-inputs/evidence
143+
curl --fail --location "${{ inputs.artifact_archive_url }}" --output ../gate-inputs/artifact-archive
144+
145+
if [ -n "${{ inputs.ecps_comparison_url }}" ]; then
146+
curl --fail --location "${{ inputs.ecps_comparison_url }}" --output ../gate-inputs/evidence/ecps_comparison.json
147+
fi
148+
if [ -n "${{ inputs.runtime_smoke_url }}" ]; then
149+
curl --fail --location "${{ inputs.runtime_smoke_url }}" --output ../gate-inputs/evidence/runtime_smoke.json
150+
fi
151+
if [ -n "${{ inputs.benchmark_manifest_url }}" ]; then
152+
curl --fail --location "${{ inputs.benchmark_manifest_url }}" --output ../gate-inputs/evidence/benchmark_manifest.json
153+
fi
154+
155+
- name: Resolve artifact directory
156+
run: |
157+
uv run --python 3.13 python - <<'PY'
158+
import tarfile
159+
import zipfile
160+
from pathlib import Path
161+
162+
archive = Path("../gate-inputs/artifact-archive")
163+
extract_root = Path("../gate-inputs/artifact-root")
164+
extract_root.mkdir(parents=True, exist_ok=True)
165+
166+
if tarfile.is_tarfile(archive):
167+
with tarfile.open(archive) as handle:
168+
handle.extractall(extract_root, filter="data")
169+
elif zipfile.is_zipfile(archive):
170+
with zipfile.ZipFile(archive) as handle:
171+
handle.extractall(extract_root)
172+
else:
173+
raise SystemExit("artifact_archive_url must point to a tar or zip archive")
174+
175+
manifests = sorted(
176+
extract_root.rglob("manifest.json"),
177+
key=lambda path: len(path.relative_to(extract_root).parts),
178+
)
179+
if not manifests:
180+
raise SystemExit("artifact archive does not contain manifest.json")
181+
Path("../gate-inputs/artifact_dir.txt").write_text(str(manifests[0].parent.resolve()))
182+
PY
183+
184+
- name: Run artifact gates
185+
run: |
186+
artifact_dir="$(cat ../gate-inputs/artifact_dir.txt)"
187+
args=(
188+
--artifact-dir "$artifact_dir"
189+
--target-period "${{ inputs.target_period }}"
190+
--artifact-size-ratio-threshold "${{ inputs.artifact_size_ratio_threshold }}"
191+
--runtime-ratio-threshold "${{ inputs.runtime_ratio_threshold }}"
192+
--output-json ../gate-inputs/mp300k_artifact_gates.json
193+
--no-update-manifest
194+
)
195+
196+
if [ -f ../gate-inputs/evidence/ecps_comparison.json ]; then
197+
args+=(--ecps-comparison-json ../gate-inputs/evidence/ecps_comparison.json)
198+
else
199+
args+=(--skip-ecps-computation)
200+
fi
201+
if [ -f ../gate-inputs/evidence/runtime_smoke.json ]; then
202+
args+=(--runtime-smoke-json ../gate-inputs/evidence/runtime_smoke.json)
203+
fi
204+
if [ -f ../gate-inputs/evidence/benchmark_manifest.json ]; then
205+
args+=(--benchmark-manifest ../gate-inputs/evidence/benchmark_manifest.json)
206+
fi
207+
if [ "${{ inputs.require_ecps_comparison }}" != "true" ]; then
208+
args+=(--no-require-ecps-comparison)
209+
fi
210+
211+
uv run --python 3.13 --extra dev --with pydantic --with-editable ../microplex \
212+
microplex-us-mp300k-artifact-gates "${args[@]}"
213+
214+
- name: Upload gate report
215+
if: always()
216+
uses: actions/upload-artifact@v4
217+
with:
218+
name: mp300k-artifact-gates
219+
path: gate-inputs/mp300k_artifact_gates.json

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ requires-python = ">=3.13"
1515
dependencies = [
1616
"microplex[calibrate]",
1717
"duckdb>=1.2",
18+
"h5py>=3.10",
1819
"requests>=2.31",
1920
]
2021

@@ -35,6 +36,7 @@ Repository = "https://github.com/PolicyEngine/microplex-us"
3536
microplex-us-backfill-pe-native-audit = "microplex_us.pipelines.backfill_pe_native_audit:main"
3637
microplex-us-backfill-pe-native-scores = "microplex_us.pipelines.backfill_pe_native_scores:main"
3738
microplex-us-check-site-snapshot = "microplex_us.pipelines.check_site_snapshot:main"
39+
microplex-us-mp300k-artifact-gates = "microplex_us.pipelines.mp300k_artifact_gates:main"
3840
microplex-us-pe-native-target-diagnostics = "microplex_us.pipelines.pe_native_scores:main_target_diagnostics"
3941
microplex-us-score-pe-native-loss = "microplex_us.pipelines.pe_native_scores:main"
4042
microplex-us-version-bump-benchmark = "microplex_us.pipelines.version_benchmark:main"

0 commit comments

Comments
 (0)