🎉 Charts as first-class citizens in ETL, addressed by chart config UUID - #6511
🎉 Charts as first-class citizens in ETL, addressed by chart config UUID#6511danyx23 wants to merge 40 commits into
Conversation
|
Quick links (staging server):
Login: chart-diff: ✅No charts for review.data-diff: ✅ no differencesNo differences found. Automatically updated datasets matching excess_mortality|covid|fluid|flunet|country_profile|garden/ihme_gbd/2019/gbd_risk are not included. Run locally with Edited: 2026-07-31 10:07:01 UTC |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Lines 203 to 206 in 5d174a2
Written by Claude GPT-5.6 Sol — @danyx23 at the wheel.
With --archive, UUID/catalog-path twins can have different source and target numeric IDs, but this target-session query still filters with the source IDs collected above. Consequently it does not collect the datasets currently used by the actual target charts, so datasets orphaned by their synchronization are omitted from the later archive pass. Build this filter from each diff's target_chart.id when a target twin exists.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Thanks @danyx23! While testing the scenario of creating a new chart from VSCode, I realised that the grapher chart editor was empty (showing a "No table loaded yet"). This looked like a bug, but it turned out to be an issue with the staging server. This was Fable's assessment:
Apart from that staging server hiccup, etl chart creation and editing is working as expected, and the addition of UUID to the chart steps makes sense. |
Codex review fixes on #6511: an mdim whose dimensions were all pruned (single choice in use) was reclassified as a single chart and failed the chart_config_id validation with a misleading error — chart mode is now decided by the declared chart_config_id, with an actionable error for the pruned case. The chart-editing skill no longer pins an outdated $schema and no longer prescribes --force --only as the normal push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pabloarosado
left a comment
There was a problem hiding this comment.
Thanks for this work, Daniel. I've tested it and works as expected (except for an issue I mentioned that seems to be temporary).
I've also created this other PR to handle how chart diff and chart sync should behave. But it shouldn't block the current PR.
Heads-up: the grapher side of this work (owid/owid-grapher#6826) removes This branch still calls the removed endpoint in one place: The fix is to drop resp = target_api.upsert_chart_etl_config(
chart_config_id=diff.target_chart.configId,
grapher_config=migrated_etl_config,
catalog_path=diff.source_chart.catalogPath,
user_id=user_id,
)It has to be Two related grapher-side changes worth knowing about:
These two branches need to land together, so this one should be updated before the grapher PR merges. |
Lets ETL push standalone Grapher charts as zero-dimension mdim collections. A `.config.yml` with `dimensions: []` and exactly one view is routed through `Collection.upsert_to_db` to a new endpoint that writes the chart's ETL-authored grapher config into `chart_configs.etlConfig` (added on the grapher side). The endpoint lives on owid-grapher: PUT /admin/api/charts/:id/etlConfig Admin patches in `chart_configs.patch` are preserved across ETL re-pushes by construction — ETL and admin write to different columns. No drift-check apparatus, no bot-user stamping, no ETL_FORCE_CHART. The full rendered config is recomputed server-side as merge(variableETL, etlConfig, patch). Contents: - `apps/chart_sync/admin_api.py`: new `put_chart_etl_config` and `delete_chart_etl_config` methods. - `etl/collection/chart_upsert.py`: new module that translates a zero-dim collection view into a grapher chart config (resolving catalog paths to variable IDs) and pushes it via the new endpoint. Creates the chart with a minimal bootstrap if it doesn't exist yet. - `etl/collection/model/core.py`: `Collection.upsert_to_db` short- circuits to `upsert_collection_as_chart` when `dimensions` is empty. - `etl/steps/__init__.py`: `ExportStep` can run from a `.config.yml` with no Python script for `export://multidim/...` paths. - `apps/step_update/cli.py` + `etl/version_tracker.py`: recognise YAML-only export steps in step discovery and version tracking. - `schemas/multidim-schema.json`: documents that `dimensions` can be empty for single-chart collections (the schema already allowed it structurally — `minItems` was never set). - `etl/steps/export/multidim/animal_welfare/latest/banning_of_chick_culling.config.yml` + DAG entry: chick-culling chart as the example/test artifact. Depends on owid-grapher PR #6553 (the endpoint and the etlConfig column don't exist on master yet). To test end-to-end on staging: SSH into the staging server, check out `etl-chart-config` in the owid-grapher folder, run the migration, restart admin, then run this ETL step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `Collection.upsert_to_db`: when dimensions are empty, explicitly require exactly one view before dispatching to chart upsert. Previously the assertion lived only inside `upsert_collection_as_chart`; surfacing it at the dispatch point makes the implicit "zero-dim ⇒ single chart" behaviour switch concrete with a clear error for the malformed case. - `dag/animal_welfare.yml`: rewrite the test-scaffolding-flavoured comment on the chick-culling export step to a normal one-liner. Resolves codex findings 4 and 5 from the PR review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DAG entries for `export://multidim/...` steps backed by a `.config.yml` (single-chart or mdim collections without a `.py`) were not clickable because the resolver only looked for `.py`, `__init__.py`, or `.ipynb`. Add `.config.yml` to the candidate list for `export://` URIs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single-chart `.config.yml` files (zero-dim collection + no sibling `.py`)
push to the chart admin endpoint and are created unpublished by default.
The public `/grapher/{slug}` route doesn't render unpublished charts, so
clicking "Preview chart" was failing for newly-created ETL-authored
charts. Detect the single-chart case and route the preview through
`/admin/grapher/{slug}` instead, which renders drafts.
`parseExportMultidim` becomes async and returns `isChart` + `chartSlug`
based on (a) `dimensions: []` in the .config.yml AND (b) no sibling
`.py` file (multi-dim steps like air_pollution use `dimensions: []` as
a placeholder filled programmatically — those still go via the mdim
route).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`OWIDEnv.admin_site` already ends in `/admin`, so prefixing `/admin/charts/...` produced URLs like `http://.../admin/admin/charts/9138/edit`. Drop the duplicated segment; matches the convention used by `datasets_admin_site` etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `/grapher/<slug>.png` route only renders published charts. For drafts it 404s — which is the common case while iterating on an ETL-authored chart. owid-grapher already exposes `/grapher/by-uuid/<chart_configs.id>.png` which serves any chart from R2's `byUUID/` directory, published or not. Adds `get_chart_png_url.py`: takes a chart slug or numeric chart id, queries the staging DB via `OWID_ENV.read_sql` to resolve it to the `chart_configs.id` UUID, prints the by-uuid PNG URL. Accepts extra grapher query params via `--key=value` flags (tab, time, country, …). Updates SKILL.md to recommend this as the primary path; keeps the agent-browser path as a fallback for MDIM data pages and interactive UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous content documented a `.chart.yml` system in `etl/steps/graph/` that was an unmerged experiment and no longer exists in the repo. Rewrite the skill to cover the actual Phase 1 workflow: single ETL-authored charts as zero-dim mdim collections in `etl/steps/export/multidim/`, pushed via `chart_configs.etlConfig`, previewed via the `by-uuid` PNG route. Covers: file layout, minimum viable config, DAG entry, indicator catalog paths + display.name overrides, common chart-config edits, push command, preview via the check-chart-preview skill, and the admin co-editing model. Adds a "when NOT to use" section pointing at create-multidim for charts with dropdowns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hens-by-housing-system chart was an iteration experiment, not a canonical reference. The chick-culling chart is the committed Phase 1 example in this repo, so cite only that. Replace the eggs-and-hens indicator paths in the display-name example with placeholders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- chart_upsert: set topic_tags on newly created ETL-authored charts (admin-owned after creation) - chart-preview ext: use fs.access for .py existence check instead of readFile - clickable-dag-steps: backfill 0.0.4 changelog entry Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…quired - Schema: drop `title`/`default_selection` from top-level required; drop `title_variant` from the title block's required. - Model: `Collection.title` and `Collection.default_selection` default to None. - Runtime: error when an mdim collection has no title; per-view warning when a non-map-only view has no entity selection fallback (neither collection-level `default_selection` nor per-view `selectedEntityNames`). - Example YAML + chart-editing skill: drop top-level `title:` / `default_selection:` from single-chart configs (they were ignored in chart-mode anyway). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
validate_title() raised for Explorers (Collections with dimensions) even though explorers manage their own title, breaking test_explorer_migration. Gate both validations on _collection_type == "multidim". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The admin etlConfig endpoint only checks the schema version, not structure, so a typo'd field or wrong type would be stored and silently render wrong. Validate the built config against the locally vendored grapher schema before pushing (no network), surfacing a clear error pointing at the offending field. Skips if the config's schema version isn't vendored locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 🎉🤖 Key ETL charts by catalogPath (parallel to mdims) Persist the collection's catalog_path on charts.catalogPath (via the etlConfig endpoint) and look up the chart by it instead of by slug. This gives single charts the same stable ETL identity mdims already have (multi_dim_data_pages.catalogPath), with the slug left as the mutable public URL. Behavior change: ETL no longer adopts a hand-authored chart that merely shares a slug — it keys on catalogPath, so an accidental slug collision surfaces at publish time instead of silently overwriting. Pairs with owid-grapher PR (adds the charts.catalogPath column). Targets the chart-config feature branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 📜🤖 Fix put_chart_etl_config docstring for row-model storage The ETL config is stored in its own chart_configs row (charts.configIdETL), not in a chart_configs.etlConfig column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ETL-authored charts are created independently on staging and prod, so they have different ids but a shared catalogPath. Match them across environments by catalogPath (falling back to id+createdAt for hand-authored charts), sync the etlConfig layer alongside the main config, and bootstrap a created chart with its etlConfig + catalogPath so ETL can adopt it. Approvals/conflicts for "catalog-path twins" (prod minted its own row) key on a NULL target timestamp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ins) set_status recorded a catalog-path twin's approval with the prod chart's updatedAt, but _get_approvals looks twins up with a NULL target timestamp — so re-approving an existing ETL chart would lose the approval. Record via _target_updated_at_for_review so recording and lookup agree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… columns Production's charts table doesn't have catalogPath/configIdETL yet (owid-grapher #6553 isn't merged to master), so chart-diff — which loads prod charts — failed with "Unknown column", breaking the staging build. Added a PROD_HAS_ETL_CHART_COLUMNS flag (default False): chart-diff falls back to id+createdAt matching and defers those columns out of the prod query. No practical loss yet (prod has no ETL/catalogPath charts). TODO: remove the flag + defer() once the grapher schema is in prod. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the PROD_HAS_ETL_CHART_COLUMNS flag with a runtime check of the target (production) DB's information_schema: chart-diff uses catalogPath matching only when charts.catalogPath/configIdETL actually exist there, otherwise it defers those columns out of the query and falls back to id+createdAt matching. The ETL PR merges after owid-grapher #6553, so prod will already have the columns by then and chart-diff switches over on its own — no flag to flip or code to touch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… run test_chart_diff.py imported chart_diff at module top. pytest imports every test module during collection (even ones deselected by -m integration), and importing chart_diff applies its module-level @st_cache_data decorators, which call the @cache'd is_running_in_streamlit(). At collection time there's no Streamlit runtime, so it froze to False process-wide and routed producer_analytics' cached _process_df onto the buggy non-Streamlit path, crashing an unrelated integration test. Moving the import into the test bodies keeps collection clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ETL-authored charts now set isInheritanceEnabled=True explicitly when the chart row is first created, so grapher_config defined on an indicator flows into every chart built on it. Set only at creation, so existing charts (ETL or admin) are never touched. No owid-grapher change needed; the admin create endpoint already honors the inheritance flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Charts that pre-date ETL authorship have no catalogPath, so the catalogPath-only lookup failed against any DB where the step had never run (rebuilt staging, prod at merge time) and crashed on the slug collision with the existing chart. Fall back to the slug and adopt the chart: put_chart_etl_config stamps the catalog path, so later runs find it by catalogPath directly. Refuse to adopt a chart already owned by a different ETL step. Verified on staging: first run adopts chart 7118 with a byte-identical rendered config (admin map override preserved, no version bump); second run takes the regular catalogPath update path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use the chart's config UUID (charts.configId) as its stable identity when talking to the grapher admin and when matching charts across environments, instead of the numeric auto-increment id: - chart_upsert: replace the bootstrap create + etlConfig PUT with the new idempotent PUT /charts/by-config/:uuid/etlConfig upsert. New charts get a client-generated UUIDv7; the admin patch of created charts stays empty so the ETL layer owns dimensions etc. from birth. - chart-diff: match source/target by configId first (works on prod today, no new columns needed), then catalogPath, with id+createdAt as a legacy fallback; catalog-path twins generalize to cross-env twins. - chart-sync: carry the source chart's configId when creating charts on the target (via the upsert endpoint for ETL-authored charts and a new configId param on POST /charts for hand-authored ones), so future diffs match directly. Narrative charts are now matched by their chartConfigId instead of the collision-prone numeric id. - model: Chart.load_chart supports config_id lookups; NarrativeChart.load_by_chart_config_id added. Requires the corresponding owid-grapher branch (by-config etlConfig upsert + configId param on chart creation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The chart config UUID is the chart's identity, so it belongs in the config YAML rather than being resolved from whichever environment we happen to be pushing to. chart_upsert previously looked the chart up by catalogPath (falling back to slug) and adopted the `configId` it found, minting a fresh UUIDv7 only when nothing matched. That makes the identity environment-derived: the same YAML could resolve to different charts in different environments, or create a new one where the lookup missed. - `Collection.chart_config_id`: new top-level field, required for — and only valid on — single-chart collections (`dimensions: []`). Mdims keep being identified by their catalog path. - `validate_chart_config_id()`: runs from `save()` (so it covers programmatically-set values too) and from `upsert_collection_as_chart()` (so no entry point can bypass it). Requires a canonical lower-case dashed UUID — `uuid.UUID()` alone accepts braces, `urn:uuid:`, undashed hex and upper case, none of which would match grapher's CHAR(36) lookup key, so the upsert would silently create a new chart instead. Warns on non-v7, since every chart config UUID grapher has generated is a v7. - chart_upsert: the DB lookup is gone; the UUID comes from the collection and goes straight to the by-config upsert endpoint. - `_uuid_v7()` becomes the public `new_chart_config_id()` — it is now a config-authoring helper rather than runtime logic. - multidim schema: `chart_config_id` with a canonical-form pattern. - banning_of_chick_culling: declares the existing chart's `charts.configId` (0191b6c7-5595-70b2-8d30-fa03fccd7add), so pushes keep landing on the chart at /grapher/banning-of-chick-culling instead of orphaning it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Writing `chart_config_id` by hand means either inventing a UUIDv7 or digging the
existing chart's `charts.configId` out of the DB, and a wrong value silently
abandons a chart or creates a duplicate. This adds a subcommand for both cases:
etl chart-config-id new <config.yml>
etl chart-config-id lookup <config.yml> --slug banning-of-chick-culling
etl chart-config-id lookup <config.yml> --chart-id 7118
`lookup` reads the configured grapher DB (`OWID_ENV`, or `--env <staging-branch>`
/ `--env <path/to/.env>`). The chart is always named explicitly — by slug or by
the numeric id shown in the admin, which is what older automation carries — since
guessing which chart is meant is the failure the field exists to prevent.
Guards, because this field decides chart identity:
- Only single-chart configs are accepted (`dimensions: []` and exactly one view),
so an mdim — including one that fills dimensions programmatically — can't get
the field that `Collection.validate_chart_config_id()` would then reject.
- An existing `chart_config_id` is never replaced without `--force`.
- Writing the same value again is a no-op, so the command is safe to re-run.
- A slug matching several charts resolves to the published one, or errors telling
you to pick with --chart-id.
The write goes through ruamel so comments and formatting survive; the UUID is
placed first, as the header of the file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two leftovers from before the CLI existed: - The "chart that exists only in the admin" case still said to write the `charts.configId` into the YAML by hand; point it at `chart-config-id lookup --chart-id` instead. - "ETL addresses a chart by its config UUID, never by slug or numeric id" read as contradicting the `lookup --slug` / `--chart-id` examples right below it. Scope it to push time and note that slug/id are how you find the UUID once, while authoring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Master's test_every_admin_api_call_has_a_timeout (merged in 8a13943) enforces a bounded timeout on every call in admin_api.py; the etlConfig methods predate that rule on this branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The config pinned grapher-schema.009, two versions behind the current default. Since only the current schema is vendored in schemas/, _validate_chart_config found no local file and silently skipped validation entirely. Omitting the field lets chart_upsert's setdefault apply DEFAULT_GRAPHER_SCHEMA (011 today), so the config validates locally again and won't go stale on the next bump. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex review fixes on #6511: an mdim whose dimensions were all pruned (single choice in use) was reclassified as a single chart and failed the chart_config_id validation with a misleading error — chart mode is now decided by the declared chart_config_id, with an actionable error for the pruned case. The chart-editing skill no longer pins an outdated $schema and no longer prescribes --force --only as the normal push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UUIDs are case-insensitive and the grapher admin accepts a caller-supplied one in any case, so DB values can differ in case across environments while naming the same chart. Normalize on the ETL side: chart-diff and chart-sync twin-matching compare case-insensitively, and `etl chart-config-id lookup` writes the canonical lower-case form into the YAML (which is the only form `validate_chart_config_id` accepts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Master renamed `ChartConfig.full` to `ChartConfig.config` and added a test
requiring multidim configs to pin `grapher_schema`. Adapt this branch's
`load_etl_config` and the new single-chart config accordingly, and restore the
`title or {}` guard in `combine_collections` that this branch's optional
`Collection.title` needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 🐛 Follow grapher's chart column renames for the ETL layer * 🔨🤖 Follow grapher's chart column renames for the ETL layer owid/owid-grapher#6826 renamed charts.configIdETL to charts.patchConfigIdETL (an authored layer, like charts.patchConfigId and variables.patchConfigIdETL) and charts.catalogPath to charts.etlConfigCatalogPath (the step that owns the ETL layer, not an identifier). Rename the ORM columns and every read of them in chart-diff, chart-sync and the chart-config-id CLI, and check for the new names when probing whether production has the columns yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The docs still named `chart_configs.patch`, `chart_configs.full` and a `chart_configs.etlConfig` column. None of those exist: every layer is its own `chart_configs` row holding a `config`, and which layer you mean comes from the pointer on `charts` — `configId` for the rendered config, `patchConfigId` for what an admin authored, `patchConfigIdETL` for what ETL pushed. Doc-only. The chart-editing skill is the guide people follow, so it should describe the columns that are actually there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every other extension here keeps its current .vsix in install/ and moves older builds to install/archive/. chart-preview had 0.0.1, 0.0.2 and 0.0.3 sitting side by side, so it was not obvious which one to install. package.json is on 0.0.3; the other two are archived. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
This is a slight modification of @pabloarosado's existing "charts as first-class citizens" work in #6174 — the collection/chart-upsert machinery and the layered etlConfig approach are his. This PR adds two commits on top that make the chart's config UUID (
charts.configId) its identity, both for addressing charts and for matching them across environments, instead of the numeric auto-increment id. Companion grapher PR: owid/owid-grapher#6826 (stacked on owid/owid-grapher#6825).New on top of #6174:
PUT /charts/by-config/:uuid/etlConfigupsert. A new chart's admin patch starts out empty so the ETL layer ownsdimensionsetc. from birth (no more rediff workaround needed for fresh charts). Retries can no longer create duplicate charts.configIdfirst (the column exists on production today, so no runtime column guard is needed), thencatalogPath, keeping id+createdAt only as a legacy fallback. Catalog-path twins generalize to cross-env twins.configIdwhen creating charts on the target (via the upsert endpoint for ETL-authored charts, and a newconfigIdparam onPOST /chartsfor hand-authored ones), so future diffs match the rows directly. Narrative charts are now matched by theirchartConfigIdinstead of the collision-prone numeric id.Chart.load_chartsupportsconfig_idlookups;NarrativeChart.load_by_chart_config_idadded.etl chart-config-id: new CLI to write the UUID into a chart's config YAML — see below.Requires the grapher endpoints from owid/owid-grapher#6826.
How chart identity differs from the base PR
In #6174 a single chart's ETL identity was resolved from the target database:
chart_upsertlooked the chart up bycatalogPath, fell back to matching on slug, adopted whateverconfigIdit found, and minted a fresh UUID only when nothing matched.Here the identity is declared in the config YAML as a new required top-level field, and nothing is looked up:
Why the change: a lookup makes the identity environment-derived, so the same YAML can resolve to a different chart in each environment, or create a new chart wherever the lookup misses (e.g. a chart whose slug was changed in the admin). Declaring the UUID makes one YAML address one chart everywhere — local, staging and production — and makes "which chart does this step own?" answerable by reading the file.
Consequences:
chart_config_idis required for single-chart collections (dimensions: []) and rejected on mdims, which stay identified by their catalog path.charts.configIdin the YAML. Creating a new chart means minting a UUIDv7 (new_chart_config_id(), formerly the private_uuid_v7).Collection.validate_chart_config_id(): the value must be a canonical lower-case dashed UUID.uuid.UUID()on its own also accepts{braces}, aurn:uuid:prefix, undashed hex and upper case — none of which match grapher'sCHAR(36)lookup key, so they would have missed the intended chart and silently created a new one. A non-v7 UUID is accepted but logged as a warning, since every chart config UUID grapher has generated so far is a v7. The check runs fromCollection.save()(covering programmatically-built collections) and again fromupsert_collection_as_chart()(so no entry point bypasses it), and the multidim schema carries the same canonical-form pattern for YAML configs.etl chart-config-idSince the UUID now has to be in the file, there's a subcommand to put it there, rather than expecting people to invent a UUIDv7 or hand-query the DB:
lookupqueries the configured grapher DB (OWID_ENV, or--env <staging-branch>/--env <path/to/.env>). The chart is always named explicitly — by slug, or by the numeric id that the admin shows and that older automation carries — because guessing which chart is meant is precisely the failure this field exists to prevent.Guards, since this field decides chart identity:
dimensions: []and exactly one view), so an mdim — including one that fills dimensions programmatically — can't be given a field that would then be rejected at build time.chart_config_idis never replaced without--force.--chart-id.The write goes through ruamel, so comments and formatting survive.
banning_of_chick_culling.config.yml— the one ETL-authored single chart so far — declares the UUID of the chart that already exists at/grapher/banning-of-chick-culling, so pushes keep landing on it rather than orphaning it.Test plan
chart_config_idvalidation tests: required for single charts, rejected on mdims, must be a string, must be canonical (parametrized over the four lenient formsuuid.UUIDwould otherwise wave through), non-v7 warning, and UUIDv7 format/time-ordering of the minted value.etl chart-config-idtests: accepted/rejected config shapes, overwrite guard, no-op on re-write, comment preservation, and the exactly-one-identifier rule.lookupmodes exercised againststaging-site-etl-charts:--slug banning-of-chick-cullingand--chart-id 7118both resolve to0191b6c7-5595-70b2-8d30-fa03fccd7add, matching the UUID committed in the YAML.make checkand the affected test files pass.🤖 Generated with Claude Code