66 branches :
77 - main
88 paths :
9+ - pyproject.toml
10+ - uv.lock
11+ - modal_app/**
912 - policyengine_us_data/**
1013 - tests/**
1114 - .github/workflows/**
1215 - Makefile
1316
17+ concurrency :
18+ group : pr-code-changes-${{ github.event.pull_request.number }}
19+ cancel-in-progress : true
20+
1421jobs :
1522 check-fork :
1623 runs-on : ubuntu-latest
3037 fi
3138 echo "✅ PR is from the correct repository"
3239
40+ decide-test-scope :
41+ name : Decide PR test scope
42+ runs-on : ubuntu-latest
43+ needs : check-fork
44+ outputs :
45+ full_suite : ${{ steps.decide.outputs.full_suite }}
46+ reason : ${{ steps.decide.outputs.reason }}
47+ steps :
48+ - uses : actions/checkout@v4
49+ with :
50+ fetch-depth : 0
51+
52+ - id : decide
53+ env :
54+ BASE_SHA : ${{ github.event.pull_request.base.sha }}
55+ HEAD_SHA : ${{ github.event.pull_request.head.sha }}
56+ PR_LABELS_JSON : ${{ toJson(github.event.pull_request.labels.*.name) }}
57+ run : |
58+ python - <<'PY'
59+ import fnmatch
60+ import json
61+ import os
62+ import subprocess
63+
64+ labels = set(json.loads(os.environ["PR_LABELS_JSON"]))
65+ changed_files = subprocess.check_output(
66+ [
67+ "git",
68+ "diff",
69+ "--name-only",
70+ os.environ["BASE_SHA"],
71+ os.environ["HEAD_SHA"],
72+ ],
73+ text=True,
74+ ).splitlines()
75+
76+ full_suite_label = "full-data-ci"
77+ critical_patterns = [
78+ ".github/workflows/pr_code_changes.yaml",
79+ ".github/workflows/reusable_test.yaml",
80+ "modal_app/**",
81+ "policyengine_us_data/calibration/**",
82+ "policyengine_us_data/datasets/**",
83+ "policyengine_us_data/db/**",
84+ "policyengine_us_data/storage/download_private_prerequisites.py",
85+ "policyengine_us_data/utils/loss.py",
86+ "policyengine_us_data/utils/mortgage_interest.py",
87+ "policyengine_us_data/utils/soi.py",
88+ "policyengine_us_data/utils/uprating.py",
89+ ]
90+
91+ matched_files = [
92+ path
93+ for path in changed_files
94+ if any(fnmatch.fnmatch(path, pattern) for pattern in critical_patterns)
95+ ]
96+
97+ if full_suite_label in labels:
98+ full_suite = True
99+ reason = f"label:{full_suite_label}"
100+ elif matched_files:
101+ full_suite = True
102+ reason = f"critical-path:{matched_files[0]}"
103+ else:
104+ full_suite = False
105+ reason = "basic-pytest-only"
106+
107+ with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
108+ output.write(f"full_suite={'true' if full_suite else 'false'}\n")
109+ output.write(f"reason={reason}\n")
110+
111+ summary = [
112+ "### PR test scope",
113+ f"- full suite: `{'true' if full_suite else 'false'}`",
114+ f"- reason: `{reason}`",
115+ ]
116+ if matched_files:
117+ summary.append(f"- first matching file: `{matched_files[0]}`")
118+ with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as out:
119+ out.write("\n".join(summary) + "\n")
120+ PY
121+
33122 check-lock-freshness :
34123 name : Check uv.lock freshness
35124 runs-on : ubuntu-latest
@@ -81,10 +170,10 @@ jobs:
81170 run : python -c "from policyengine_core.data import Dataset; print('Core import OK')"
82171
83172 Test :
84- needs : [check-fork, Lint]
173+ needs : [check-fork, Lint, decide-test-scope ]
85174 uses : ./.github/workflows/reusable_test.yaml
86175 with :
87- full_suite : true
176+ full_suite : ${{ needs.decide-test-scope.outputs.full_suite == ' true' }}
88177 upload_data : false
89178 deploy_docs : false
90- secrets : inherit
179+ secrets : inherit
0 commit comments