Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/refresh-snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
# Who gets told, per AGENTS.md "Refresh, break, or schema change": a break is
# an issue HERE; a merged refresh reaches consumers per the manifest's
# `on_refresh` (the fan-out is not wired yet — the PR body lists what it would
# do, and today no snapshot has a consumer).
# do; the first consumer, lecture-wasm's business_cycle, reads raw/main in
# the reader's browser and needs no dispatch, PLAN Phase 5).
#
# Token: the PR is opened with QUANTECON_SERVICES_PAT when the org secret is
# available to this repo, falling back to the workflow token. The fallback
Expand Down
10 changes: 5 additions & 5 deletions CATALOG.md

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ Full automation:
- [x] Retrofit `builders/business_cycle.py` to the four-stage builder contract — **done 2026-09-01**, with the two provenance dumps moved out of the published tree to `provenance/` ([#13](https://github.com/QuantEcon/data-lectures/issues/13)). Its `validate()` is the first to face a *revised* upstream: it bounds the overlap window (5 pp) and prints the revision summary rather than asserting equality, which is the review surface the refresh-as-PR workflow below will use. It previously had fetch/transform/write but no validate stage. Builder architecture and a copy-able template: [#14](https://github.com/QuantEcon/data-lectures/issues/14)
- [x] Scheduled refresh workflow for dynamic datasets — **landed 2026-09-01** as `.github/workflows/refresh-snapshots.yml`, manifest-driven rather than cron-per-class: a weekly run asks `scripts/snapshots.py due` which `dynamic-snapshot` datasets have their cadence elapsed (or are `diverged`, or were never refreshed), runs each builder in place, stamps the manifest (`retrieved`, `sha256`, `integrity.upstream: verified`, `date_range.end`), regenerates the catalog, and opens a PR on `refresh/<stem>` whose body is the builder's overlap summary. Nothing auto-merges; the first consumer is `business_cycle_data.csv`, not UNRATE — the pilot's order inverted once the World Bank file turned out to be the one already here
- [x] Weekly sources-alive canary: fetch + validate, no commit, opens an issue on failure — **landed 2026-09-01** as the `canary` job of the same workflow: every dynamic snapshot's builder runs with `--out-dir`, and a failure opens or updates one `upstream-break` issue classified by exit code (2 = the data broke the contract, a human; anything else = the fetch, a retry). Covers the live APIs only as their snapshot twins land here — the 23 live-API lectures without a twin are still guarded by nothing but their own CI
- [ ] Consumer fan-out: a merged refresh or in-place correction dispatches rebuilds of the repos in the dataset's machine-readable `consumers` list — **policy recorded 2026-09-01** (AGENTS.md "Refresh, break, or schema change": per-consumer `on_refresh: rebuild | review`), and the refresh PR body already lists what the fan-out would do; the dispatch itself waits for the first snapshot with a consumer
- [ ] Consumer fan-out: a merged refresh or in-place correction dispatches rebuilds of the repos in the dataset's machine-readable `consumers` list — **policy recorded 2026-09-01** (AGENTS.md "Refresh, break, or schema change": per-consumer `on_refresh: rebuild | review`), and the refresh PR body already lists what the fan-out would do; the dispatch itself waits for the first consumer that executes at build time. **The first snapshot consumer needs no dispatch** (P4, [#118](https://github.com/QuantEcon/data-lectures/issues/118), "Not in scope"): `lecture-wasm` runs `business_cycle` in the reader's browser and reads `raw/main` when the page runs, so a merged refresh reaches its readers with no rebuild. Its CI never executes code and has no dispatch path that builds, so a dispatched rebuild would change nothing a reader sees
- [ ] Package the refresh job as a reusable workflow (`quantecon/actions`) once it stabilizes

### Phase 6 — Metadata backfill for existing holdings
Expand Down Expand Up @@ -340,7 +340,13 @@ The first end-to-end deployment: one dataset per hosting pattern, each the harde
**The flip was the acceptance test, and it measured as one.** Dry-run locally in both directions before pushing: `landed` → exit 1 with 6 warnings; `repointed` → exit 0. So the red window was real, opened when the last consuming PR merged, and closed with the flip. **Do this both-directions dry-run on every future wave** — it converts "same-day, trust me" into a measurement.

Five things P3 proved that were not on its test list. A `constructed` dataset's builder must land in the **same** PR as the data, because `check_consumed_files.py` asserts the `builder:` path resolves. `builders/README.md`'s coverage table is a real coverage report and goes stale silently. The plain-git decision costs ~10 MB of packed history for 110 MB of working tree, since CSV compresses 5-22×. The **C0 → C1 → C2 ordering worked and proved less than it looks like** — the sync PR it was designed to defuse (QuantEcon/lecture-intro.zh-cn#293) touched zero data-read lines, zero `# i18n` markers and zero protected localisations, but nothing ever asked the model to rewrite those cells, so the markers remain unexercised, prompt-level protection and **the hand-diff is what protects a localisation**. And the translation sync is **`.md`-only**, so no hand-localised `_static` asset can be created, updated or repaired by it — every `data.ipynb` copy had to be repointed by hand in all four repos, filed upstream as QuantEcon/action-translation#271
- [ ] **P4 — dynamic snapshot twin**: originally `UNRATE` alone; **reframed 2026-09-01** as the `business_cycle` set, because the lecture that needs a twin is excluded from `lecture-wasm` for want of one and a partial twin buys it nothing. Done so far: `business_cycle_data.csv` (renamed `gdp_growth_annual.csv` 2026-09-07 under the naming policy, [#113](https://github.com/QuantEcon/data-lectures/issues/113)) manifested and its builder retrofitted ([#109](https://github.com/QuantEcon/data-lectures/pull/109)); the refresh-as-PR and canary workflow ([#110](https://github.com/QuantEcon/data-lectures/pull/110)); the first real refresh ([#112](https://github.com/QuantEcon/data-lectures/pull/112)); the World Bank set extended to three tables and the FRED half landed as one composite monthly file on a shared `builders/_fred.py` library ([#114](https://github.com/QuantEcon/data-lectures/pull/114)). Remaining: the `lecture-wasm` adoption ([QuantEcon/lecture-wasm#70](https://github.com/QuantEcon/lecture-wasm/issues/70) — intro keeps its live calls as the lesson), the flip with `on_refresh: rebuild`, and a canary run catching an induced failure
- [x] **P4 — dynamic snapshot twin**: originally `UNRATE` alone; **reframed 2026-09-01** as the `business_cycle` set, because the lecture that needed a twin was excluded from `lecture-wasm` for want of one and a partial twin would have bought it nothing. **Complete B_MERGE_DATE**, run from 2026-09-29 as the `wasm-dynamic-snapshots` project ([#118](https://github.com/QuantEcon/data-lectures/issues/118)).

**Data half** — `business_cycle_data.csv` (renamed `gdp_growth_annual.csv` 2026-09-07 under the naming policy, [#113](https://github.com/QuantEcon/data-lectures/issues/113)) manifested and its builder retrofitted ([#109](https://github.com/QuantEcon/data-lectures/pull/109)); the refresh-as-PR and canary workflow ([#110](https://github.com/QuantEcon/data-lectures/pull/110)); the first real refresh ([#112](https://github.com/QuantEcon/data-lectures/pull/112)); the World Bank set extended to three tables and the FRED half landed as one composite monthly file on a shared `builders/_fred.py` library ([#114](https://github.com/QuantEcon/data-lectures/pull/114)).

**Consumer half and the flip, B_MERGE_DATE** — `lecture-wasm` re-enabled `business_cycle`, reading all four files from `raw/main` ([QuantEcon/lecture-wasm#85](https://github.com/QuantEcon/lecture-wasm/pull/85), for [QuantEcon/lecture-wasm#70](https://github.com/QuantEcon/lecture-wasm/issues/70)); intro keeps its live calls as the lesson. The same day the flip ([#146](https://github.com/QuantEcon/data-lectures/issues/146)) recorded `lecture-wasm` as the consumer of all four, with `on_refresh: rebuild`, and moved their `migration.yml` records to `repointed`. Dry-run in both directions first, as P3 asks: with `lecture-wasm` at the adoption branch, `landed` → exit 1 with 4 warnings, one per record, and `repointed` → exit 0; with `lecture-wasm` at its `main`, `repointed` → exit 1.

**Canary proof complete 2026-09-30** ([#147](https://github.com/QuantEcon/data-lectures/issues/147)): on a throwaway branch that narrowed `UNRATE`'s band in `builders/business_cycle_fred.py`, two dispatched runs of `refresh-snapshots` exited 2 in the canary and opened, then updated, the `upstream-break` issue. The second, [36654357802](https://github.com/QuantEcon/data-lectures/actions/runs/36654357802), forced the file due, so it also shows a failing canary keeping the refresh job from running; the first is [36654189187](https://github.com/QuantEcon/data-lectures/actions/runs/36654189187). Nothing reached `main`
- [ ] Verify each migrated URL with a pyodide/JupyterLite fetch (CORS, meta#143)
- [ ] Fold every validated decision into the draft `styleguide/datasets.md` (manual#108) as it is proven

Expand Down
66 changes: 36 additions & 30 deletions lectures/gdp_growth_annual.csv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ source:
url: https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG
doi: null
version: >
Unknown for the committed bytes — WDI carries a database-level
`lastupdated` stamp but the export recorded none (see `retrieved`). The
live database at the 2026-09-01 check reported lastupdated 2026-07-13 and
had moved on from these bytes (see integrity.upstream).
Live WDI at each refresh; the vintage is pinned by `retrieved` and
integrity.upstream, not by a source-side edition (WDI carries only a
database-level lastupdated stamp).
citation: >
World Bank, World Development Indicators, series NY.GDP.MKTP.KD.ZG (GDP
growth, annual %), from World Bank national accounts data and OECD
Expand All @@ -70,36 +69,30 @@ license:
is the model case for this repo's cache-with-attribution policy
(AGENTS.md, "Licensing and attribution").

# `retrieved` is stamped by scripts/snapshots.py on every merged refresh; null
# until the first one. The inherited bytes were produced by the committed
# builder in QuantEcon/data commit b857c5c of 2025-02-16 — but that is when
# QuantEcon ran the script and committed the output, and AGENTS.md says not to
# reconstruct a retrieval date from git history. The vintage is pinned by
# content instead: YR2023 is the last column and is populated for all five
# economies, which places the export after the WDI release that first carried
# 2023 growth, and integrity.upstream records how far the live series has
# moved since.
# Stamped by scripts/snapshots.py on every merged refresh, so it dates the
# bytes committed now. It was null until the first refresh (#112) replaced the
# inherited bytes (QuantEcon/data b857c5c, 2025-02-16), which carried no
# retrieval date; AGENTS.md says not to reconstruct one from git history.
retrieved: 2026-09-01
maintainer: QuantEcon

# ---------------------------------------------------------------------------
# Integrity (PLAN Phase 7)
# ---------------------------------------------------------------------------
# No migration check applies: no lecture has ever read this file (it was
# "never adopted" — PLAN "The audit (2026-07-15)"), so there is no prior copy
# to byte-match and no figure a change here could alter today.
# No migration check applied: no lecture read this file before lecture-wasm
# adopted it in P4 (it was "never adopted" — PLAN "The audit (2026-07-15)"),
# so there was no prior copy to byte-match.

integrity:
sha256: 41df23233ea238e4166c5c21cc383791015d4c9af6868b840901a8ce083c9da8
upstream:
# `diverged`, not `failing`: the builder was re-run in full against live
# WDI and its output differs from these bytes in the way a revised
# aggregate is expected to. For a dynamic snapshot this is the normal
# state between refreshes, not a defect — the refresh is the resolution,
# and it is a deliberate PR, not something this manifest asks for.
# status / date / against / note are stamped by scripts/snapshots.py on
# every merged refresh (the delta block below is dropped at the same time,
# since a fresh snapshot is `verified` by construction).
# every merged refresh: a fresh snapshot is `verified` by construction,
# since these bytes are the builder's validated output from the live
# source that day. The World Bank goes on revising the series, so a
# re-run between refreshes is expected to differ from these bytes. For a
# dynamic snapshot that is the normal state, not a defect: the next
# refresh is the resolution, and it is a deliberate PR.
status: verified
date: 2026-09-01
against: builders/business_cycle.py
Expand Down Expand Up @@ -143,13 +136,26 @@ schema:
# ---------------------------------------------------------------------------
# Consumers — how a correction knows what to rebuild
# ---------------------------------------------------------------------------
# None yet. Decided 2026-09-01: lecture-python-intro KEEPS its live wbgapi
# calls (the API is the lesson there), and lecture-wasm — where business_cycle
# is currently excluded from the build because pyodide cannot reach the API —
# will read this file and its two siblings plus us_business_cycle_monthly.csv
# (QuantEcon/lecture-wasm#70). When that repoint lands, record it here with
# `on_refresh: rebuild`, and `cadence` below becomes load-bearing.
consumers: []
# lecture-wasm's business_cycle reads this file, its two World Bank siblings
# and us_business_cycle_monthly.csv from raw/main (QuantEcon/lecture-wasm#70).
# The snapshots are what let lecture-wasm re-enable the lecture, which it had
# excluded because its live reads could not run in the browser: neither
# wbgapi nor pandas_datareader ships with Pyodide, and FRED's fredgraph.csv
# sends no CORS header. Decided 2026-09-01:
# lecture-python-intro KEEPS its live wbgapi calls (the API is the lesson
# there), so it is not a consumer. With a reader, `cadence` below is
# load-bearing: the page shows whatever vintage is committed.
#
# `on_refresh: rebuild`, because no prose in the lecture quotes a value a
# refresh can move: its numbers are the years of historical events, and its
# ranges run "to the present". The value dispatches nothing today. The page
# runs in the reader's browser and reads raw/main when it runs, so a merged
# refresh reaches its readers with no rebuild (PLAN Phase 5, consumer
# fan-out).
consumers:
- repo: QuantEcon/lecture-wasm
file: lectures/business_cycle.md
on_refresh: rebuild

builder: builders/business_cycle.py
builder_status: committed # four-stage since 2026-09-01 (PLAN Phase 5);
Expand Down
9 changes: 7 additions & 2 deletions lectures/private_credit_to_gdp.csv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ schema:
known_nulls: {}
nulls: {along: columns, leading: true, recent: 2, ended: [], inner: {}}

# None yet — see gdp_growth_annual.csv.yml (QuantEcon/lecture-wasm#70).
consumers: []
# lecture-wasm's business_cycle reads this file (QuantEcon/lecture-wasm#70);
# lecture-python-intro keeps its live call. See gdp_growth_annual.csv.yml for
# the decision and for why `on_refresh` is `rebuild`.
consumers:
- repo: QuantEcon/lecture-wasm
file: lectures/business_cycle.md
on_refresh: rebuild

builder: builders/business_cycle.py
builder_status: committed
Expand Down
11 changes: 7 additions & 4 deletions lectures/unemployment_rate_annual.csv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,13 @@ schema:
known_nulls: {}
nulls: {along: columns, leading: true, recent: 2, ended: [], inner: {}}

# None yet — see gdp_growth_annual.csv.yml: lecture-wasm will read this
# file once business_cycle is re-enabled there (QuantEcon/lecture-wasm#70);
# lecture-python-intro keeps its live call.
consumers: []
# lecture-wasm's business_cycle reads this file (QuantEcon/lecture-wasm#70);
# lecture-python-intro keeps its live call. See gdp_growth_annual.csv.yml for
# the decision and for why `on_refresh` is `rebuild`.
consumers:
- repo: QuantEcon/lecture-wasm
file: lectures/business_cycle.md
on_refresh: rebuild

builder: builders/business_cycle.py
builder_status: committed
Expand Down
17 changes: 10 additions & 7 deletions lectures/us_business_cycle_monthly.csv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,16 @@ schema:
CPILFESL: [2025-10-01]
UMCSENT: {sparse_until: 1978-01-01}

# None yet. Decided 2026-09-01: lecture-python-intro keeps its live
# pandas_datareader calls (the API is the lesson there); lecture-wasm will
# read this file once business_cycle is re-enabled in its build
# (QuantEcon/lecture-wasm#70) — record it here with `on_refresh: rebuild`.
# The python.myst unemployment_linear and unemployment_shocks lectures read
# UNRATE live over the same window and are candidates for a later adoption.
consumers: []
# lecture-wasm's business_cycle reads this file (QuantEcon/lecture-wasm#70).
# Decided 2026-09-01: lecture-python-intro keeps its live pandas_datareader
# calls (the API is the lesson there). See gdp_growth_annual.csv.yml for why
# `on_refresh` is `rebuild`. The python.myst unemployment_linear and
# unemployment_shocks lectures read UNRATE live over the same window and are
# candidates for a later adoption.
consumers:
- repo: QuantEcon/lecture-wasm
file: lectures/business_cycle.md
on_refresh: rebuild

builder: builders/business_cycle_fred.py
builder_status: committed
Expand Down
Loading
Loading