diff --git a/migration.yml b/migration.yml index 61fcc0a..6b68dfb 100644 --- a/migration.yml +++ b/migration.yml @@ -82,8 +82,11 @@ datasets: # Planned waves that have not landed anything here yet. `datasets` names the # files as the audit sees them today, so the dashboard can join the two views. +# `title` is the reader-facing milestone name (the dashboard is read by people +# who are not doing the migration); `scope` carries the insider detail. pending: - pilot: P3 + title: Large data files, and the external data repository scope: the LFS case — heavy_tails Forbes/cities set + SCF extracts (fold in high_dim_data) datasets: - forbes-global2000.csv @@ -96,6 +99,7 @@ pending: - QuantEcon/data-lectures#2 - QuantEcon/meta#337 - pilot: P4 + title: Maintained snapshots for lectures that fetch live API data scope: the dynamic-snapshot case — a maintained UNRATE twin for the incidental FRED consumers datasets: [] # produces a NEW file here; no existing static file moves tracking: diff --git a/scripts/render_audit.py b/scripts/render_audit.py index c84a251..e24a6ea 100644 --- a/scripts/render_audit.py +++ b/scripts/render_audit.py @@ -205,6 +205,7 @@ .meter .seg + .seg { margin-left: 2px; } .meter .seg.a { background: var(--meter-a); } .meter .seg.b { background: var(--meter-b); } +.meter.mini { height: 8px; max-width: 340px; margin: 6px 0 2px; border-radius: 4px; } /* bar list — one measure, single hue, labeled rows */ .barlist { display: grid; grid-template-columns: max-content 1fr max-content; gap: 6px 12px; align-items: center; margin: 14px 0; } .barlist .lbl { font-size: 13px; } @@ -360,13 +361,13 @@ def migration_meter(audit: dict) -> str:
Denominator: the {n_total} distinct static files lectures read today. The interim→final
-URL cutover ({issue_link("QuantEcon/data-lectures#15")}) advances datasets from repointed to
-final once data.quantecon.org is live; no dataset is final yet.
Denominator: the {n_total} distinct static files lectures read today. The full +per-series manifest and the milestone plan are on the +migration tracker.
""" @@ -400,11 +401,12 @@ def what_changed() -> str:The audit was first taken by hand on {PREV_SNAPSHOT}. Regenerating from live repo state two days later, the picture has already changed — which is why this dashboard is generated.
-P1 moved lingcod_msy_recovery.csv (was a Colab-breaking local path);
-P2 moved the pandas_panel trio realwage.csv / countries.csv /
-employ.csv (5 of their 6 reads pointed at the retired legacy repo). All consumers
-now read data-lectures. Details on the migration tracker.
The first two migration waves moved lingcod_msy_recovery.csv (was a
+Colab-breaking local path) and the pandas_panel trio realwage.csv /
+countries.csv / employ.csv (5 of their 6 reads pointed at the retired
+legacy repo). Every consuming lecture now reads the central copy. Details on the
+migration tracker.
The pre-MyST QuantEcon/lecture-python repo was renamed to
lecture-python.rst and archived; the P2 repoints plus the ols
@@ -471,6 +473,88 @@ def render_index(audit: dict) -> str:
# Page: migration tracker
# ---------------------------------------------------------------------------
+# Reader-facing status labels — the dashboard is read by people who are not
+# doing the migration, so the manifest speaks plainly; the lifecycle terms
+# (pending/landed/repointed/final) appear only in the explained stepper detail.
+STATUS_META = {
+ # status → (pill css, label)
+ "final": ("p-local", "✓ migrated"),
+ "repointed": ("p-local", "✓ migrated"),
+ "landed": ("p-own", "● copied, not yet switched"),
+ "queued": ("p-legacy", "queued"),
+ "unscheduled": ("p-embed", "not scheduled"),
+}
+
+
+def dataset_status(fname: str, migration: dict) -> tuple[str, str]:
+ """→ (status, pilot) for any static dataset the audit sees."""
+ rec = (migration.get("datasets") or {}).get(fname)
+ if rec:
+ return rec.get("status", "pending"), rec.get("pilot", "")
+ for wave in migration.get("pending") or []:
+ if fname in (wave.get("datasets") or []):
+ return "queued", wave.get("pilot", "")
+ return "unscheduled", ""
+
+
+def series_manifest(audit: dict) -> str:
+ """Every static dataset, grouped by the lecture series that owns the bytes
+ today, with its migration status — the how-far-along view."""
+ migration = audit["migration"] or {}
+ groups: dict[str, list] = {}
+ for d in audit["datasets"]:
+ groups.setdefault(repo_of_record(d), []).append(d)
+ order = ["lecture-python-intro", "lecture-python-programming",
+ "lecture-python.myst", "lecture-python-advanced.myst",
+ "data-lectures"]
+ rows = ""
+ for repo in [r for r in order if r in groups] + sorted(set(groups) - set(order)):
+ ds = sorted(groups[repo], key=lambda x: x["file"])
+ statuses = [dataset_status(d["file"], migration)[0] for d in ds]
+ n = len(ds)
+ n_done = sum(1 for s in statuses if s in ("repointed", "final"))
+ n_landed = statuses.count("landed")
+ n_queued = statuses.count("queued")
+ counts = [f"{n_done} migrated" if n_done else "",
+ f"{n_landed} copied, not switched" if n_landed else "",
+ f"{n_queued} queued" if n_queued else "",
+ f"{n - n_done - n_landed - n_queued} not scheduled"
+ if n - n_done - n_landed - n_queued else ""]
+ pct = lambda k: max(0.8, 100 * k / n) if k else 0
+ segs = ""
+ if n_done:
+ segs += f'
Every static file the lectures read today, grouped by the series that owns the +bytes, with its migration status — the how-far-along view. Not scheduled means no +milestone has claimed the file yet; the broad sweep (see the milestones below) eventually +covers them all. Data written by the lectures themselves and live-API reads are not migration +targets and are tracked on the audit page.
+| File | Contents | Hosting today | Status |
|---|
{check}
' +def milestones(audit: dict) -> str: + """The migration programme as reader-facing milestones — completed waves + (derived from migration.yml records), upcoming waves, the broad sweep, and + the final-URL switch. This section is what lets the rest of the dashboard + stay plan-agnostic: wave codes like P3 mean something only because they + are presented here.""" + mig = audit["migration"] or {} + recs = mig.get("datasets") or {} + manifests = audit["manifests"] + + by_pilot: dict[str, list] = {} + for fname, rec in recs.items(): + by_pilot.setdefault(rec.get("pilot", "?"), []).append((fname, rec)) + items = "" + for pilot in sorted(by_pilot): + entries = by_pilot[pilot] + files = sorted(f for f, _ in entries) + done = all(r.get("status") in ("repointed", "final") for _, r in entries) + dates = [str((r.get("repoints") or [{}])[-1].get("date", "")) for _, r in entries] + series = {c["repo"] for f, _ in entries + for c in (manifests.get(f, {}).get("consumers") or [])} + n_series = len(series) + mark, cls = ("✓", "ok") if done else ("○", "") + items += f""" +{" · ".join(f"{esc(f)}" for f in files)}
{esc(f)}" for f in sorted(files))
+ if files else "produces new snapshot files here; no existing file moves")
+ tracking = " · ".join(issue_link(t) for t in w.get("tracking") or [])
+ items += f"""
+{flist}
+Tracking: {tracking}
+The {n_unsched} not-yet-scheduled files move once the waves above have proven the +process for each kind of data. Mechanical from there: one data PR here, one switch PR +per consuming lecture repo.
+data.quantecon.org
+Migrated lectures currently read this repository's GitHub URL. Once the custom domain
+is live, every migrated dataset switches to its permanent
+data.quantecon.org/lectures/… address in a single sweep
+({issue_link("QuantEcon/data-lectures#15")}). No dataset has made this step yet.
The migration proceeds in small waves, each proving the process for a harder +kind of data before the broad sweep moves the rest.
+{items} +""" + + def render_migration(audit: dict) -> str: mig = audit["migration"] or {} recs = mig.get("datasets") or {} @@ -518,7 +669,7 @@ def render_migration(audit: dict) -> str:{esc(m.get("title", ""))} — consumed by {consumers or "—"}
{stepper(fname, rec, verified)}{esc(f)} — {esc((by_name.get(f) or {}).get("description", ""))}Tracking: {tracking}
-migration.yml, the manifests and today\'s scan of every consumer repo agree.
The tracker, the dataset manifests and today\'s scan of every lecture repo agree.
Migration tracker · source of truth: migration.yml + lectures/*.yml
Migration tracker · generated and verified on every build
+pending — identified for migration; nothing has moved yet.
+landed — the file and its metadata are merged into the central repo; lectures unchanged.
+repointed — every consuming lecture now reads the central copy (shown as
+✓ migrated in the table below).
+final — the lecture reads the permanent data.quantecon.org address
+(the last milestone below).
The full provenance for each migrated dataset: which pull requests landed the +data and switched each consuming lecture, and where it sits in the lifecycle.
{pipelines} -Scoped but not landed. Files listed here still appear under their current -hosting pattern in the audit.
-{waves} -pending — identified for migration; nothing in data-lectures yet.
-landed — file + manifest merged here; consumers unchanged.
-repointed — every consumer reads this repo's interim raw URL
-(github.com/QuantEcon/data-lectures/raw/main/lectures/…).
-final — every consumer reads https://data.quantecon.org/lectures/…;
-the cutover is one sweep tracked in {issue_link("QuantEcon/data-lectures#15")}, gated on Phase 4 DNS.