Skip to content

Add compact PolicyEngine dataset export #7

Add compact PolicyEngine dataset export

Add compact PolicyEngine dataset export #7

name: mp-300k Artifact Gates
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
inputs:
gate_inputs_artifact:
description: Optional Actions artifact name containing artifact.tar.gz and evidence JSONs.
required: false
default: ""
type: string
artifact_archive_url:
description: URL to a .zip, .tar, or .tar.gz artifact bundle containing manifest.json.
required: false
default: ""
type: string
ecps_comparison_url:
description: Optional URL to precomputed PE-native eCPS comparison JSON.
required: false
type: string
runtime_smoke_url:
description: Optional URL to runtime smoke benchmark JSON.
required: false
type: string
benchmark_manifest_url:
description: Optional URL to the frozen microsimulation benchmark manifest.
required: false
type: string
target_period:
description: PolicyEngine period to validate.
required: false
default: "2024"
type: string
runtime_ratio_threshold:
description: Maximum candidate/baseline runtime ratio.
required: false
default: "1.25"
type: string
artifact_size_ratio_threshold:
description: Maximum candidate/baseline H5 size ratio.
required: false
default: "2.0"
type: string
require_ecps_comparison:
description: Keep the eCPS comparison as a blocking gate.
required: false
default: true
type: boolean
workflow_call:
inputs:
gate_inputs_artifact:
required: false
default: ""
type: string
artifact_archive_url:
required: false
default: ""
type: string
ecps_comparison_url:
required: false
type: string
runtime_smoke_url:
required: false
type: string
benchmark_manifest_url:
required: false
type: string
target_period:
required: false
default: "2024"
type: string
runtime_ratio_threshold:
required: false
default: "1.25"
type: string
artifact_size_ratio_threshold:
required: false
default: "2.0"
type: string
require_ecps_comparison:
required: false
default: true
type: boolean
permissions:
actions: read
contents: read
jobs:
implementation-tests:
if: github.event_name == 'pull_request' || github.event_name == 'push'
runs-on: ubuntu-latest
defaults:
run:
working-directory: microplex-us
steps:
- name: Check out microplex-us
uses: actions/checkout@v4
with:
path: microplex-us
- name: Check out core microplex
uses: actions/checkout@v4
with:
repository: PolicyEngine/microplex
ref: main
path: microplex
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Set up uv
uses: astral-sh/setup-uv@v6
- name: Test artifact gate implementation
run: |
uv run --python 3.13 --extra dev --with pydantic --with-editable ../microplex pytest -q \
tests/pipelines/test_mp300k_artifact_gates.py \
tests/pipelines/test_mp300k_gate_inputs.py
artifact-gates:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call'
runs-on: ubuntu-latest
defaults:
run:
working-directory: microplex-us
steps:
- name: Check out microplex-us
uses: actions/checkout@v4
with:
path: microplex-us
- name: Check out core microplex
uses: actions/checkout@v4
with:
repository: PolicyEngine/microplex
ref: main
path: microplex
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Set up uv
uses: astral-sh/setup-uv@v6
- name: Download packaged gate inputs
if: inputs.gate_inputs_artifact != ''
uses: actions/download-artifact@v4
with:
name: ${{ inputs.gate_inputs_artifact }}
path: gate-inputs
- name: Download artifact and evidence from URLs
if: inputs.gate_inputs_artifact == ''
run: |
mkdir -p ../gate-inputs/evidence
if [ -z "${{ inputs.artifact_archive_url }}" ]; then
echo "Either gate_inputs_artifact or artifact_archive_url is required."
exit 1
fi
curl --fail --location "${{ inputs.artifact_archive_url }}" --output ../gate-inputs/artifact-archive
if [ -n "${{ inputs.ecps_comparison_url }}" ]; then
curl --fail --location "${{ inputs.ecps_comparison_url }}" --output ../gate-inputs/evidence/ecps_comparison.json
fi
if [ -n "${{ inputs.runtime_smoke_url }}" ]; then
curl --fail --location "${{ inputs.runtime_smoke_url }}" --output ../gate-inputs/evidence/runtime_smoke.json
fi
if [ -n "${{ inputs.benchmark_manifest_url }}" ]; then
curl --fail --location "${{ inputs.benchmark_manifest_url }}" --output ../gate-inputs/evidence/benchmark_manifest.json
fi
- name: Normalize gate inputs
run: |
uv run --python 3.13 python - <<'PY'
import shutil
from pathlib import Path
root = Path("../gate-inputs")
evidence_dir = root / "evidence"
evidence_dir.mkdir(parents=True, exist_ok=True)
archive_target = root / "artifact-archive"
if not archive_target.exists():
archive_candidates = []
for pattern in ("artifact.tar.gz", "artifact.tgz", "artifact.tar", "artifact.zip", "*.tar.gz", "*.tgz", "*.tar", "*.zip"):
archive_candidates.extend(root.glob(pattern))
archive_candidates = [
path for path in archive_candidates if path.is_file()
]
if not archive_candidates:
raise SystemExit(
"Packaged gate inputs did not contain artifact.tar.gz, "
"artifact.tgz, artifact.tar, or artifact.zip"
)
shutil.copyfile(archive_candidates[0], archive_target)
for name in ("ecps_comparison", "runtime_smoke", "benchmark_manifest"):
source = root / f"{name}.json"
destination = evidence_dir / f"{name}.json"
if source.exists() and not destination.exists():
shutil.copyfile(source, destination)
PY
- name: Resolve artifact directory
run: |
uv run --python 3.13 python - <<'PY'
import tarfile
import zipfile
from pathlib import Path
archive = Path("../gate-inputs/artifact-archive")
extract_root = Path("../gate-inputs/artifact-root")
extract_root.mkdir(parents=True, exist_ok=True)
if tarfile.is_tarfile(archive):
with tarfile.open(archive) as handle:
handle.extractall(extract_root, filter="data")
elif zipfile.is_zipfile(archive):
with zipfile.ZipFile(archive) as handle:
for member in handle.infolist():
destination = (extract_root / member.filename).resolve()
if not destination.is_relative_to(extract_root.resolve()):
raise SystemExit(
f"zip archive member escapes artifact root: {member.filename}"
)
handle.extract(member, extract_root)
else:
raise SystemExit("artifact_archive_url must point to a tar or zip archive")
manifests = sorted(
extract_root.rglob("manifest.json"),
key=lambda path: len(path.relative_to(extract_root).parts),
)
if not manifests:
raise SystemExit("artifact archive does not contain manifest.json")
Path("../gate-inputs/artifact_dir.txt").write_text(str(manifests[0].parent.resolve()))
PY
- name: Run artifact gates
run: |
artifact_dir="$(cat ../gate-inputs/artifact_dir.txt)"
args=(
--artifact-dir "$artifact_dir"
--target-period "${{ inputs.target_period }}"
--artifact-size-ratio-threshold "${{ inputs.artifact_size_ratio_threshold }}"
--runtime-ratio-threshold "${{ inputs.runtime_ratio_threshold }}"
--output-json ../gate-inputs/mp300k_artifact_gates.json
--no-update-manifest
)
if [ -f ../gate-inputs/evidence/ecps_comparison.json ]; then
args+=(--ecps-comparison-json ../gate-inputs/evidence/ecps_comparison.json)
else
args+=(--skip-ecps-computation)
fi
if [ -f ../gate-inputs/evidence/runtime_smoke.json ]; then
args+=(--runtime-smoke-json ../gate-inputs/evidence/runtime_smoke.json)
fi
if [ -f ../gate-inputs/evidence/benchmark_manifest.json ]; then
args+=(--benchmark-manifest ../gate-inputs/evidence/benchmark_manifest.json)
fi
if [ "${{ inputs.require_ecps_comparison }}" != "true" ]; then
args+=(--no-require-ecps-comparison)
fi
uv run --python 3.13 --extra dev --with pydantic --with-editable ../microplex \
microplex-us-mp300k-artifact-gates "${args[@]}"
- name: Upload gate report
if: always()
uses: actions/upload-artifact@v4
with:
name: mp300k-artifact-gates
path: gate-inputs/mp300k_artifact_gates.json