Skip to content

Commit 8a04862

Browse files
mmckyclaude
andauthored
Add the generated data-audit dashboard (gh-pages): audit + migration tracker (#21)
* Add the generated data-audit dashboard: scanner, tracker, 3-page site scripts/build_audit.py scans the 8 synced Python-family lecture repos on origin/main (clone + grep — never gh search code, which cannot find URLs), classifies every data reference into the hosting-pattern taxonomy, and renders a 3-page static dashboard (overview / migration tracker / full audit) via scripts/render_audit.py. Three sources of truth, reconciled on every build: - lectures/*.yml manifests — the migrated datasets - migration.yml (new) — the migration lifecycle + PR provenance, kept separate from the manifests so it can be archived when the migration programme completes without touching the permanent dataset records - scripts/audit_annotations.yml (new) — curated judgment (descriptions, provenance classes, live-API pedagogy) for not-yet-migrated references The strict scan fails on an unannotated data reference or a migration.yml status that disagrees with what the lectures actually read, so the dashboard cannot silently rot — the failure mode that killed the hand-built 2026-07-15 audit artifact within two days. Part of #20; taxonomy and design system carried over from that artifact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Deploy the dashboard and the published tree to GitHub Pages .github/workflows/audit-dashboard.yml builds the dashboard from shallow clones of the 8 lecture repos and deploys the default Pages site: dashboard at /, the published lectures/ tree at /lectures/, audit.json alongside for reuse. Triggers: push to main, weekly cron, manual dispatch; on PRs touching the audit inputs it runs the strict build as a guardrail without deploying. Serving lectures/ on Pages pre-stages PLAN Phase 4: when the data.quantecon.org DNS question is resolved, only the custom domain changes. Checkout uses lfs: true so LFS-tracked files publish as bytes, never pointers. Part of #20. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs: record the dashboard, its update rules, and P2 completion README gains the audit-dashboard section; AGENTS.md gains the keep-it-truthful rules (migration.yml updated in the same PR as a landing/repoint; annotations for new references; never commit site/ or audit.json) and the expanded repo map; PLAN.md checks off the Pages deploy (default domain — custom domain stays open), adds the Phase 5 workflow entry, and marks pilot P2 complete (#17, QuantEcon/lecture-python-programming#578, QuantEcon/lecture-python.myst#973). Part of #20. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address Copilot review: taxonomy gap, status validation, notebook guard - render_audit.py: add the external-web pattern to PATTERN_META so a non-GitHub data URL would surface in the hosting-pattern table and distribution chart instead of being silently omitted (no current ref triggers it; the failure mode was the point) - build_audit.py: validate migration.yml status values in the scan-stage consistency check (an unknown status fails --strict with a clear message); render_audit.py's stepper raises a named error as backstop - build_audit.py: flag any .ipynb under lectures/ outside _static/ as an unscanned_notebooks problem. Every lecture source today is MyST .md and the only notebooks are builder/figure helpers (deliberately not consumers); if a notebook-format lecture ever appears, the build now refuses to miss it silently Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent bcbe5d0 commit 8a04862

10 files changed

Lines changed: 2067 additions & 4 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# The generated data-audit dashboard (data-lectures#20): scan the 8 Python-
2+
# family lecture repos, verify migration.yml + the manifests against what the
3+
# lectures actually read, render the dashboard, and deploy it to GitHub Pages
4+
# together with the published lectures/ tree.
5+
#
6+
# The scan is strict: an unannotated data reference or a migration.yml status
7+
# that disagrees with reality fails the build — the dashboard cannot rot into
8+
# a hand-tended snapshot. On PRs the build runs as a guardrail; deploys happen
9+
# only from main (push / weekly schedule / manual dispatch).
10+
11+
name: audit-dashboard
12+
13+
on:
14+
push:
15+
branches: [main]
16+
schedule:
17+
- cron: "17 5 * * 1" # weekly — lecture repos move under us
18+
workflow_dispatch:
19+
pull_request:
20+
paths:
21+
- migration.yml
22+
- lectures/*.yml
23+
- scripts/build_audit.py
24+
- scripts/render_audit.py
25+
- scripts/audit_annotations.yml
26+
- .github/workflows/audit-dashboard.yml
27+
28+
permissions:
29+
contents: read
30+
31+
concurrency:
32+
group: pages
33+
cancel-in-progress: false
34+
35+
jobs:
36+
build:
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@v4
40+
with:
41+
lfs: true # published files must be bytes, never LFS pointers
42+
- uses: actions/setup-python@v5
43+
with:
44+
python-version: "3.12"
45+
- run: pip install pyyaml
46+
- name: Clone the 8 lecture repos (shallow, main only)
47+
run: |
48+
mkdir -p repos
49+
for repo in lecture-python-intro lecture-python-programming \
50+
lecture-python.myst lecture-python-advanced.myst \
51+
lecture-jax lecture-dp lecture-wasm continuous_time_mcs; do
52+
git clone --quiet --depth 1 --branch main \
53+
"https://github.com/QuantEcon/$repo" "repos/$repo"
54+
done
55+
- name: Build the dashboard (strict — fails on unannotated refs or migration drift)
56+
run: python scripts/build_audit.py all --strict --repos-dir repos -o site
57+
- name: Assemble the Pages tree (dashboard at /, data at /lectures/)
58+
run: |
59+
mkdir -p _site
60+
cp -r site/. _site/
61+
cp -r lectures _site/lectures
62+
cp audit.json _site/audit.json
63+
- uses: actions/upload-pages-artifact@v3
64+
with:
65+
path: _site
66+
67+
deploy:
68+
if: github.event_name != 'pull_request'
69+
needs: build
70+
runs-on: ubuntu-latest
71+
permissions:
72+
pages: write
73+
id-token: write
74+
environment:
75+
name: github-pages
76+
url: ${{ steps.deployment.outputs.page_url }}
77+
steps:
78+
- uses: actions/configure-pages@v5
79+
with:
80+
enablement: true
81+
- id: deployment
82+
uses: actions/deploy-pages@v4

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# generated by scripts/build_audit.py — built fresh in CI, never committed
2+
site/
3+
audit.json
4+
5+
__pycache__/

‎AGENTS.md‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ For the public data sources most snapshots come from (World Bank, FRED, Eurostat
7878

7979
One exception: a `restricted` file the lectures have already served publicly may be cached here if it is marked `redistribution: restricted` with a `note` and logged for licence review in the migration-licensing tracker ([workspace-lectures#20](https://github.com/QuantEcon/workspace-lectures/issues/20)) — resolve it (permission, an open replacement, or removal) before `data.quantecon.org` is promoted as a public open-data host.
8080

81+
### The audit dashboard stays truthful
82+
83+
The generated dashboard (`scripts/build_audit.py`, [#20](https://github.com/QuantEcon/data-lectures/issues/20)) verifies its three inputs against a fresh scan of the lecture repos, and the strict build **fails** when they drift. Keep them current in the same PR as the change that moves reality:
84+
85+
- **Landing or repointing a dataset** → update its `migration.yml` record (status, PR refs, dates). A dataset marked `repointed` whose consumers still read an old URL — or the reverse — is a build failure, by design.
86+
- **A new manifest** (`lectures/*.yml`) → add its `migration.yml` record; delete any stale entry for the same file in `scripts/audit_annotations.yml` (manifested datasets must not be annotated there).
87+
- **The weekly scan flags an unannotated reference** (a lecture repo started reading a new file) → classify it and add an entry to `scripts/audit_annotations.yml`; that file holds judgment (description, provenance, why-live), never facts the scan can derive.
88+
89+
`site/` and `audit.json` are generated — never commit them; CI rebuilds and deploys on every push to `main`.
90+
8191
## Cross-repo hygiene
8292

8393
- Changes here often pair with PRs in lecture repos and issues in `QuantEcon/meta`. In commit messages and PR bodies, **never place a GitHub closing keyword (`fixes`, `closes`, `resolves`, …) immediately before a cross-repo reference** like `QuantEcon/meta#336` — GitHub will auto-close the referenced issue when the commit lands on the default branch. Write "See QuantEcon/meta#336" or "Part of QuantEcon/meta#336".
@@ -89,8 +99,13 @@ One exception: a `restricted` file the lectures have already served publicly may
8999
lectures/ # the published tree — flat, served at data.quantecon.org/lectures/
90100
# 9 datasets + business_cycle's upstream metadata dumps
91101
# manifests live here as sidecars: <filename>.yml
92-
scripts/ # builders — NOT published
102+
scripts/ # builders + generators — NOT published
93103
business_cycle.py # writes business_cycle_data.csv into lectures/
104+
build_catalog.py # generates CATALOG.md from the manifests
105+
build_audit.py # the audit dashboard: scan lecture repos → audit.json → site/
106+
render_audit.py # its render stage
107+
audit_annotations.yml # curated judgment for not-yet-migrated data refs
108+
migration.yml # migration lifecycle tracker (status + PR provenance per dataset)
94109
manifest-schema.yml # per-dataset manifest schema (strawman)
95110
requirements.txt
96111
PLAN.md # roadmap — start here

‎PLAN.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ The sidecar naming uses the **full filename** (`mpd2020.xlsx.yml`, not `mpd2020.
7070

7171
### Phase 4 — Publishing
7272

73-
- [ ] GitHub Pages deploy of the published tree, **`lfs: true` at checkout** (else pointer files publish)
73+
- [x] GitHub Pages deploy of the published tree, **`lfs: true` at checkout** (else pointer files publish) — landed 2026-07-17 with the audit dashboard (`.github/workflows/audit-dashboard.yml`, [#20](https://github.com/QuantEcon/data-lectures/issues/20)): the default `quantecon.github.io/data-lectures/` site serves the dashboard at `/` and the published tree at `/lectures/`. The custom domain below stays open
7474
- [ ] `data.quantecon.org` DNS + custom domain (an old NestJS box on AWS Sydney currently answers this name — investigate before repointing)
7575
- [ ] Verify `access-control-allow-origin: *` on served files (pyodide/JupyterLite, meta#143)
7676
- [ ] Monitor Pages soft limits (~1 GB site, 100 GB/month)
@@ -84,6 +84,7 @@ The sidecar naming uses the **full filename** (`mpd2020.xlsx.yml`, not `mpd2020.
8484

8585
Full automation:
8686

87+
- [x] Audit dashboard workflow ([#20](https://github.com/QuantEcon/data-lectures/issues/20), added 2026-07-17): `.github/workflows/audit-dashboard.yml` rebuilds the full-universe data audit + migration tracker from the 8 lecture repos' `main` (push to main / weekly / dispatch) and deploys it with the published tree to Pages. Strict mode fails the build on an unannotated data reference or a `migration.yml` status the scan contradicts
8788
- [ ] PR validation: manifest schema check + per-dataset invariant tests (expected columns/dtypes, row-count floor, date-range recency, no all-NaN columns, overlap-window agreement with the previous vintage) on every PR touching data. The schema decisions these tests force — column patterns for wide files, `known_nulls` exact-vs-ceiling, a canonical dtype vocabulary — are researched in [#14](https://github.com/QuantEcon/data-lectures/issues/14)
8889
- [ ] Retrofit `scripts/business_cycle.py` to the four-stage builder contract — it has fetch/transform/write today but **no validate stage**. Builder architecture and a copy-able template: [#14](https://github.com/QuantEcon/data-lectures/issues/14)
8990
- [ ] Scheduled refresh workflow for dynamic datasets — cron per cadence class, runs the builder (fetch → pre-process → validate → write), lands the result as a PR whose diff summary (rows added, date-range delta, overlap-window changes) is the review surface; low-risk series may auto-merge on green (first consumer: the UNRATE pilot, meta#338 P4)
@@ -116,7 +117,7 @@ Verify that what this repo holds is actually the data it claims to be — agains
116117
The first end-to-end deployment: one dataset per hosting pattern, each the hardest representative of its class, carried through the full chain — layout, manifest, integrity check, publish, lecture repoint. Validates the convention empirically before anything is written into a standard. Sequence P1 → P2 → P3 → P4, each a small PR set (data repo + consuming lecture repos).
117118

118119
- [x] **P1 — local-path static**: `lingcod_msy_recovery.csv` (`msy_fishery`, intro). Tests: single-PR green build under `-nW`, Colab-unchanged download, catalog metadata for an author-assembled file. **Complete 2026-07-17** — data half #12, repoint QuantEcon/lecture-python-intro#792 (lecture build green in the single repoint PR); served URL verified byte-identical to the manifest `sha256`; Colab holds by construction (the lecture now reads a public URL, where the old relative path was exactly what broke downloaded notebooks); metadata findings recorded in meta#338. **The repo is live from this merge** — the pyodide/CORS check below remains open
119-
- [ ] **P2 — cross-series shared static**: the `pandas_panel` trio (`realwage.csv`, `countries.csv`, `employ.csv`), consumed by programming **and** python.myst. Tests: flat namespace with two consuming series, one data PR updating two lecture repos; retires 5 of the 8 legacy-repo references as a side effect
120+
- [x] **P2 — cross-series shared static**: the `pandas_panel` trio (`realwage.csv`, `countries.csv`, `employ.csv`), consumed by programming **and** python.myst. Tests: flat namespace with two consuming series, one data PR updating two lecture repos; retires 5 of the 8 legacy-repo references as a side effect. **Complete 2026-07-17** — data half #17, repoints QuantEcon/lecture-python-programming#578 and QuantEcon/lecture-python.myst#973; both lecture repos' own stale copies deleted in the repoint PRs. Lifecycle recorded in `migration.yml`
120121
- [ ] **P3 — external-repo static with LFS**: the `heavy_tails` set (Forbes ×2, cities ×2) plus the SCF pair from `high_dim_data`. Tests: served URL makes the raw-vs-media LFS trap invisible, Pages handles LFS objects (`lfs: true`), builders (`webscrape_forbes.ipynb`, `generating_mini.md`) migrate alongside their data
121122
- [ ] **P4 — dynamic snapshot twin**: `UNRATE`, consumed today by 4 lectures across 3 repos via 2 access methods. Tests: the full dynamic template — manifest, four-stage builder, refresh-as-PR, canary catching an induced failure — plus the documented live-call ↔ snapshot switch mechanism
122123
- [ ] Verify each migrated URL with a pyodide/JupyterLite fetch (CORS, meta#143)

‎README.md‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,31 @@ See the [draft convention](https://github.com/QuantEcon/QuantEcon.manual/pull/10
4141
| Path | What | Published |
4242
| --- | --- | --- |
4343
| `lectures/` | the published tree — flat. Every dataset lives here, directly. No folder implies ownership by a lecture series: any lecture may consume any file | yes |
44-
| `scripts/` | builders for constructed and dynamic datasets | no |
44+
| `scripts/` | builders for constructed and dynamic datasets, plus the audit-dashboard generator | no |
4545
| `manifest-schema.yml` | the per-dataset manifest schema (strawman — see [`PLAN.md`](PLAN.md) Phase 2) | no |
46+
| `migration.yml` | the migration lifecycle tracker — which PRs landed and repointed each dataset (transitional; archivable when the migration programme completes) | rendered |
4647

4748
The tree is flat because the URL is the interface: `lectures/<filename>` maps to
4849
`data.quantecon.org/lectures/<filename>`, so a file can never be re-filed under a
4950
new owner and break its consumers. Anything outside `lectures/` is not served.
51+
52+
## The audit dashboard
53+
54+
A **generated** dashboard covering all data referenced by the 8 synced
55+
Python-family lecture repos — the full-universe audit plus a per-dataset
56+
migration tracker — deploys to this repo's GitHub Pages site alongside the
57+
published `lectures/` tree ([data-lectures#20](https://github.com/QuantEcon/data-lectures/issues/20)).
58+
59+
```
60+
python scripts/build_audit.py all --strict # scan + render into site/
61+
```
62+
63+
The scan greps each lecture repo's `main` (clones under `--repos-dir`; defaults
64+
to this repo's parent, matching the workspace-lectures layout), classifies every
65+
data reference, and reconciles three sources of truth: the manifests
66+
(`lectures/*.yml`, migrated datasets), `migration.yml` (lifecycle + PR
67+
provenance), and `scripts/audit_annotations.yml` (curated judgment for
68+
not-yet-migrated references). A new data reference with no annotation, or a
69+
migration status the scan contradicts, **fails the build** — the dashboard
70+
cannot silently rot. CI rebuilds it on push to `main`, weekly, and on demand
71+
(`.github/workflows/audit-dashboard.yml`).

‎migration.yml‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# migration.yml — the migration lifecycle tracker for data-lectures.
2+
#
3+
# One record per dataset moving into this repo, holding the PROCESS facts the
4+
# manifests deliberately do not: which PRs landed the file, which PRs repointed
5+
# each consumer, and where the dataset sits in the lifecycle. Manifests
6+
# (lectures/*.yml) are the permanent record of a dataset; this file documents
7+
# a transition and can be archived once the migration programme completes,
8+
# without touching a single manifest.
9+
#
10+
# The audit dashboard (scripts/build_audit.py) renders this file and VERIFIES
11+
# it against reality on every build: a dataset marked `repointed` whose
12+
# consumers still read an old URL (or vice versa) fails the consistency check.
13+
#
14+
# Lifecycle (status):
15+
# pending identified for migration; nothing landed here yet
16+
# landed file + manifest merged in data-lectures; consumers unchanged
17+
# repointed every consumer reads this repo's interim raw URL
18+
# final every consumer reads https://data.quantecon.org/lectures/…
19+
# (gated on the Phase 4 DNS work — data-lectures#15)
20+
21+
datasets:
22+
lingcod_msy_recovery.csv:
23+
pilot: P1
24+
status: repointed
25+
prior_pattern: local-path
26+
landed:
27+
pr: QuantEcon/data-lectures#12
28+
date: 2026-07-15
29+
repoints:
30+
- repo: QuantEcon/lecture-python-intro
31+
pr: QuantEcon/lecture-python-intro#792
32+
date: 2026-07-17
33+
cutover: null
34+
35+
realwage.csv:
36+
pilot: P2
37+
status: repointed
38+
prior_pattern: legacy
39+
landed:
40+
pr: QuantEcon/data-lectures#17
41+
date: 2026-07-17
42+
repoints:
43+
- repo: QuantEcon/lecture-python-programming
44+
pr: QuantEcon/lecture-python-programming#578
45+
date: 2026-07-17
46+
- repo: QuantEcon/lecture-python.myst
47+
pr: QuantEcon/lecture-python.myst#973
48+
date: 2026-07-17
49+
cutover: null
50+
51+
countries.csv:
52+
pilot: P2
53+
status: repointed
54+
prior_pattern: legacy
55+
landed:
56+
pr: QuantEcon/data-lectures#17
57+
date: 2026-07-17
58+
repoints:
59+
- repo: QuantEcon/lecture-python-programming
60+
pr: QuantEcon/lecture-python-programming#578
61+
date: 2026-07-17
62+
- repo: QuantEcon/lecture-python.myst
63+
pr: QuantEcon/lecture-python.myst#973
64+
date: 2026-07-17
65+
cutover: null
66+
67+
employ.csv:
68+
pilot: P2
69+
status: repointed
70+
prior_pattern: legacy # programming read the legacy repo; python.myst its own copy
71+
landed:
72+
pr: QuantEcon/data-lectures#17
73+
date: 2026-07-17
74+
repoints:
75+
- repo: QuantEcon/lecture-python-programming
76+
pr: QuantEcon/lecture-python-programming#578
77+
date: 2026-07-17
78+
- repo: QuantEcon/lecture-python.myst
79+
pr: QuantEcon/lecture-python.myst#973
80+
date: 2026-07-17
81+
cutover: null
82+
83+
# Planned waves that have not landed anything here yet. `datasets` names the
84+
# files as the audit sees them today, so the dashboard can join the two views.
85+
pending:
86+
- pilot: P3
87+
scope: the LFS case — heavy_tails Forbes/cities set + SCF extracts (fold in high_dim_data)
88+
datasets:
89+
- forbes-global2000.csv
90+
- forbes-billionaires.csv
91+
- cities_us.csv
92+
- cities_brazil.csv
93+
- SCF_plus_mini.csv
94+
- SCF_plus_mini_no_weights.csv
95+
tracking:
96+
- QuantEcon/data-lectures#2
97+
- QuantEcon/meta#337
98+
- pilot: P4
99+
scope: the dynamic-snapshot case — a maintained UNRATE twin for the incidental FRED consumers
100+
datasets: [] # produces a NEW file here; no existing static file moves
101+
tracking:
102+
- QuantEcon/meta#338
103+
104+
# Interim → final URL cutover (status: repointed → final) is tracked as one
105+
# sweep, not per dataset: QuantEcon/data-lectures#15, gated on Phase 4 DNS.

‎scripts/README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ bug.
1414
`business_cycle.py` is run by hand today and has no validate stage; retrofitting
1515
it to the four-stage contract (fetch → pre-process → validate → write) is
1616
PLAN Phase 5.
17+
18+
## Not builders
19+
20+
| Script | What |
21+
| --- | --- |
22+
| `build_catalog.py` | generates `CATALOG.md` from the manifests (`lectures/*.yml`) |
23+
| `build_audit.py` | the audit dashboard: scans the 8 lecture repos, writes `audit.json`, renders `site/` |
24+
| `render_audit.py` | the render stage of `build_audit.py` (HTML generation) |
25+
| `audit_annotations.yml` | curated judgment for not-yet-migrated data references — the strict scan fails when a new reference has no entry here and no manifest |

0 commit comments

Comments
 (0)