Skip to content

Scheduled regen (1 specs) #38

Scheduled regen (1 specs)

Scheduled regen (1 specs) #38

Workflow file for this run

name: "Scheduled: Regen oldest specs"
run-name: "Scheduled regen (${{ github.event.inputs.count || '1' }} specs)"
# Picks the N oldest specs (by most-recent implementation `updated` timestamp)
# and re-dispatches `bulk-generate.yml` for each. Default N=1 per cron tick.
#
# Schedule: every 2h, skipping the 20:00–24:00 Berlin (CEST) evening window.
# Berlin CEST run hours: 00, 02, 04, 06, 08, 10, 12, 14, 16, 18 → UTC 22, 00,
# 02, 04, 06, 08, 10, 12, 14, 16. The 20:00 and 22:00 Berlin slots (UTC 18, 20)
# are intentionally skipped so runs never start during the user's evening.
#
# bulk-generate is serialised via its own concurrency group. With Sonnet +
# reduced bulk-generate pace, a single spec completes well within the 2h slot,
# leaving the user window clean.
#
# Triggers:
# - schedule: 10× daily (UTC, every 2h except 18:00 and 20:00 UTC)
# - workflow_dispatch: manual, with inputs for count + dry-run
on:
schedule:
- cron: '0 0,2,4,6,8,10,12,14,16,22 * * *'
workflow_dispatch:
inputs:
count:
description: "How many of the oldest specs to regen (default 1)"
required: false
default: '1'
min_age_hours:
description: "Skip specs regen'd within this many hours (default 20)"
required: false
default: '20'
dry_run:
description: "Just print picks, do not trigger bulk-generate"
type: boolean
default: false
permissions:
contents: read
actions: write
concurrency:
group: daily-regen
cancel-in-progress: false
jobs:
pick:
runs-on: ubuntu-latest
outputs:
specs: ${{ steps.pick.outputs.specs }}
count: ${{ steps.pick.outputs.count }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.13'
- name: Install PyYAML
run: pip install pyyaml
- name: Pick oldest spec(s)
id: pick
env:
COUNT: ${{ inputs.count || '1' }}
MIN_AGE_HOURS: ${{ inputs.min_age_hours || '20' }}
run: |
python3 <<'PY'
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
import yaml
COUNT = int(os.environ["COUNT"])
MIN_AGE = timedelta(hours=int(os.environ["MIN_AGE_HOURS"]))
NOW = datetime.now(timezone.utc)
specs_dir = Path("plots")
candidates: list[tuple[datetime, str]] = []
for spec_dir in sorted(specs_dir.iterdir()):
if not spec_dir.is_dir() or spec_dir.name.startswith("."):
continue
meta_dir = spec_dir / "metadata" / "python"
if not meta_dir.is_dir():
continue
latest_updated: str | None = None
for yaml_file in meta_dir.glob("*.yaml"):
try:
data = yaml.safe_load(yaml_file.read_text(encoding="utf-8")) or {}
except Exception:
continue
updated = data.get("updated") or data.get("created")
if not updated:
continue
s = str(updated)
if latest_updated is None or s > latest_updated:
latest_updated = s
if not latest_updated:
# No metadata yet → treat as ancient, candidate for regen
candidates.append((datetime.min.replace(tzinfo=timezone.utc), spec_dir.name))
continue
try:
dt = datetime.fromisoformat(latest_updated.replace("Z", "+00:00"))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
except Exception:
continue
if NOW - dt < MIN_AGE:
continue # too fresh to re-roll
candidates.append((dt, spec_dir.name))
candidates.sort() # oldest first
picks = [name for _, name in candidates[:COUNT]]
print(f"::notice::Eligible specs: {len(candidates)} picked: {picks}")
for dt, name in candidates[:COUNT]:
print(f" - {name:40s} latest_updated={dt.isoformat()}")
github_output = os.environ["GITHUB_OUTPUT"]
with open(github_output, "a", encoding="utf-8") as f:
f.write(f"specs={' '.join(picks)}\n")
f.write(f"count={len(picks)}\n")
PY
dispatch:
needs: pick
if: ${{ needs.pick.outputs.count != '0' && !inputs.dry_run }}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Trigger bulk-generate for each picked spec
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SPECS: ${{ needs.pick.outputs.specs }}
run: |
for spec in $SPECS; do
echo "::notice::Dispatching bulk-generate for $spec (all 9 libs)"
gh workflow run bulk-generate.yml \
--repo "${{ github.repository }}" \
-f specification_id="$spec" \
-f library=all
# Small pause between dispatches so GitHub's webhook processing has a moment.
sleep 5
done