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+ "modal_app/**",
79+ "policyengine_us_data/calibration/**",
80+ "policyengine_us_data/datasets/**",
81+ "policyengine_us_data/db/**",
82+ "policyengine_us_data/storage/download_private_prerequisites.py",
83+ "policyengine_us_data/utils/loss.py",
84+ "policyengine_us_data/utils/mortgage_interest.py",
85+ "policyengine_us_data/utils/soi.py",
86+ "policyengine_us_data/utils/uprating.py",
87+ ]
88+
89+ matched_files = [
90+ path
91+ for path in changed_files
92+ if any(fnmatch.fnmatch(path, pattern) for pattern in critical_patterns)
93+ ]
94+
95+ if full_suite_label in labels:
96+ full_suite = True
97+ reason = f"label:{full_suite_label}"
98+ elif matched_files:
99+ full_suite = True
100+ reason = f"critical-path:{matched_files[0]}"
101+ else:
102+ full_suite = False
103+ reason = "basic-pytest-only"
104+
105+ with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
106+ output.write(f"full_suite={'true' if full_suite else 'false'}\n")
107+ output.write(f"reason={reason}\n")
108+
109+ summary = [
110+ "### PR test scope",
111+ f"- full suite: `{'true' if full_suite else 'false'}`",
112+ f"- reason: `{reason}`",
113+ ]
114+ if matched_files:
115+ summary.append(f"- first matching file: `{matched_files[0]}`")
116+ with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as out:
117+ out.write("\n".join(summary) + "\n")
118+ PY
119+
33120 check-lock-freshness :
34121 name : Check uv.lock freshness
35122 runs-on : ubuntu-latest
@@ -81,10 +168,10 @@ jobs:
81168 run : python -c "from policyengine_core.data import Dataset; print('Core import OK')"
82169
83170 Test :
84- needs : [check-fork, Lint]
171+ needs : [check-fork, Lint, decide-test-scope ]
85172 uses : ./.github/workflows/reusable_test.yaml
86173 with :
87- full_suite : true
174+ full_suite : ${{ needs.decide-test-scope.outputs.full_suite == ' true' }}
88175 upload_data : false
89176 deploy_docs : false
90- secrets : inherit
177+ secrets : inherit
0 commit comments