Bus Service Status #94
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: Bus Service Status | |
| on: | |
| schedule: | |
| # One primary scrape plus two catch-ups: the runner IP is occasionally | |
| # Cloudflare-blocked (415 on all routes), and the block can persist for | |
| # hours. Catch-up runs no-op in seconds when the primary already landed | |
| # today's data (freshness check below). | |
| - cron: '0 23 * * *' # UTC 23:00 = Macau 07:00 (UTC+8) — primary | |
| - cron: '0 4 * * *' # UTC 04:00 = Macau 12:00 — catch-up | |
| - cron: '0 10 * * *' # UTC 10:00 = Macau 18:00 — catch-up | |
| workflow_dispatch: | |
| jobs: | |
| update: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| # Consumed by the deploy job below. | |
| changed: ${{ steps.commit.outputs.changed }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Skip if today's data already landed | |
| id: fresh | |
| # Scheduled catch-up runs only re-scrape when an earlier slot failed. | |
| # Manual dispatches skip this check on purpose — they're the escape | |
| # hatch for re-scraping after a mid-day service change. | |
| if: github.event_name == 'schedule' | |
| run: | | |
| today=$(TZ=Asia/Macau date +%F) | |
| committed=$(jq -r '.date // empty' public/service-status.json 2>/dev/null || true) | |
| echo "Macau today: $today — committed: ${committed:-none}" | |
| if [ "$committed" = "$today" ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: astral-sh/setup-uv@v7 | |
| if: steps.fresh.outputs.skip != 'true' | |
| with: | |
| version: latest | |
| - name: Scrape today's service status | |
| if: steps.fresh.outputs.skip != 'true' | |
| working-directory: data | |
| run: uv run python scripts/fetch_service_status.py | |
| - name: Validate output before commit | |
| if: steps.fresh.outputs.skip != 'true' | |
| working-directory: data | |
| # Hard gate: a malformed scrape fails the run here, before the commit | |
| # step, so broken data can never auto-deploy to production. | |
| run: uv run python scripts/validate_output.py service-status | |
| - name: Commit and push if changed | |
| id: commit | |
| if: steps.fresh.outputs.skip != 'true' | |
| uses: ./.github/actions/commit-data | |
| with: | |
| file: public/service-status.json | |
| message: 'chore: update bus service status' | |
| # Pushes made with the default GITHUB_TOKEN don't fire `on: push` workflows | |
| # (anti-recursion), so the data commit above would never reach the live site | |
| # on its own. Invoke the deploy workflow explicitly when we committed. | |
| deploy: | |
| needs: update | |
| if: needs.update.outputs.changed == 'true' | |
| permissions: | |
| contents: read | |
| deployments: write | |
| uses: ./.github/workflows/deploy.yml | |
| with: | |
| ref: master | |
| secrets: inherit |