Add compact PolicyEngine dataset export #7
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |