Agent skill

Map Explorer To Mdim

by owid in owid/etl

Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end.

MITAuto-check: notesData & Analytics

Install Map Explorer To Mdim

skills CLI
$ npx skills add owid/etl --skill map-explorer-to-mdim -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install owid/etl map-explorer-to-mdim --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/owid/etl.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/map-explorer-to-mdim .claude/skills/map-explorer-to-mdim && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
map-explorer-to-mdim
GitHub stars
159
Token cost
~7.3k tokens
SKILL.md length
3,629 words
Files
7 (incl. scripts)
Skills in repo
35
Repo updated
First seen
Licence
MIT

At a glance

Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end.

  • Works in 8 steps: Extract views + scaffold → Write mapping_rules.py → Build the proposal → …
  • The user says map explorer <slug to mdim(s) <...
  • SKILL.md covers Inputs, DB access (confirm this before…, Workflow and What each run writes, plus 1 more section
  • Runs Python scripts from its folder; calls python, curl and make; reaches ourworldindata.org

What it does

Map Explorer To Mdim is an agent skill from owid/etl. Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end. Maps each explorer's views to the views of one or more replacement MDIMs, writes ONE apply-ready JSON payload per explorer for the admin bulk-redirect endpoint, audits every article that links or embeds each explorer (with the view each link will land on) plus the featured metrics pointing at it, preflights every validation the endpoint performs — including site redirects that would block it — and covers retiring the explorer's ETL step…

Its SKILL.md is about 7.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 7 other files, including scripts (for example `scripts/audit_references.py`, `scripts/build_mapping.py` and `scripts/build_review.py`).

It sits in Data & Analytics, covering Data pipelines and ETL. The repository describes itself as: A compute graph for loading and transforming OWID's data. The licence is MIT.

When your agent uses it

  • The user says map explorer <slug to mdim(s) <...
  • Suggest explorer-MDIM redirects
  • Were sunsetting the <slug explorer
  • Map its views to the new multidims

Example prompts

  • “s ETL step afterwards. Trigger when the user says”
  • “suggest explorer-MDIM redirects”
  • “re sunsetting the <slug explorer, map its views to the new multidims”
  • “/map-explorer-to-mdim”

Requirements

  • Python 3

Workflow steps

8 steps, taken from the step headings in SKILL.md.

  1. Extract views + scaffold
  2. Write mapping_rules.py
  3. Build the proposal
  4. Review
  5. Audit what references the explorers (ALWAYS offer; run when the user says yes)
  6. Preflight (read-only, gated)
  7. Apply — the admin bulk endpoint (GATED, production)
  8. Retire the explorer, then its ETL step

What it can do on your machine

Read from SKILL.md and the folder at commit d5ba5a6. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 6 files in scripts/ (Python), which the agent can run.

    Shell commands in SKILL.md call:

    • python
    • curl
    • make
    • git

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • ourworldindata.org

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Map Explorer To Mdim loads about 7.3k tokens when it runs. Until then it costs about 228 tokens; SKILL.md has 3,629 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~228
When it runs · the whole SKILL.md, loaded when a task matches
~7.3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteMentions a .env fileSKILL.md:46
    pplies before running, and don't assume `.env.prod` exists:**
  • NoteMentions a .env fileSKILL.md:52
    what exists (`ls -la .env*`); on some machines it is `.env.prod`, on others `.env.live`.
  • NoteMentions a .env fileSKILL.md:59
    ls -la .env* 2>/dev/null          # which credentials files exist?

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from owid/etl at commit d5ba5a6, republished under its MIT licence (© owid). 3,629 words, ~7,322 tokens.

Download SKILL.mdSave it as .claude/skills/map-explorer-to-mdim/SKILL.md (or your agent's skills folder). This skill also uses 6 other files; get the full folder from GitHub.
name
map-explorer-to-mdim
description
Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end. Maps each explorer's views to the views of one or more replacement MDIMs, writes ONE apply-ready JSON payload per explorer for the admin bulk-redirect endpoint, audits every article that links or embeds each explorer (with the view each link will land on) plus the featured metrics pointing at it, preflights every validation the endpoint performs — including site redirects that would block it — and covers retiring the explorer's ETL step afterwards. Trigger when the user says "map explorer <slug> to mdim(s) <...>", "suggest explorer->MDIM redirects", "we're sunsetting the <slug> explorer, map its views to the new multidims", "redirect these explorers to MDIMs", "review the explorer->MDIM mapping", "build a review tool for the <slug> migration", "make a side-by-side HTML so <reviewer> can sign off", or similar.
metadata.internal
true
metadata.owner
paarriagadap

Map an explorer's views to MDIM views (redirect proposal)

When an explorer is being retired in favour of one or more MDIMs, every explorer view needs a redirect to the equivalent MDIM view. This skill produces the input for that: a CSV of explorer views, a CSV per target MDIM, and a joint proposal mapping each explorer view to a target MDIM view (the suggestion is for human review).

The mapping itself is explorer-specific (how the explorer's dimensions translate to MDIM dimension slugs, and — when there are multiple MDIMs — which MDIM each view routes to). The skill automates everything mechanical (pulling views, the join, the shared-target accounting, validation) and leaves only the per-explorer rules for you to write, seeded with auto-suggested matches.

Inputs

  • Explorer slug — matches explorers.slug in the grapher DB (e.g. natural-disasters).
  • One or more MDIM catalogPaths — as stored in multi_dim_data_pages.catalogPath, e.g. natural_disasters/latest/deaths#deaths. The MDIMs must be published in the DB you connect to (their fully-expanded views are read from multi_dim_data_pages.config).

DB access (confirm this before running)

Both the explorer and the MDIMs are read from the grapher DB via OWID_ENV, so the scripts only work where that DB actually contains both the explorer and the published MDIMs. There are three ways to point OWID_ENV at such a DB — figure out which one applies before running, and don't assume .env.prod exists:

  1. Staging branch (often easiest): if you're on a staging-site-<branch> branch, OWID_ENV already points at that prod-clone DB — run the commands as-is, no prefix.
  2. Production, read-only, via an env file: prefix commands with ENV_FILE=<prod creds> DATA_API_ENV=production. Don't assume the file name — check what exists (ls -la .env*); on some machines it is .env.prod, on others .env.live.
  3. Some other credentials file: the user may keep prod (or other) DB creds in a different env file — run with ENV_FILE=<their file> [DATA_API_ENV=production].

Preflight — check, then ask if needed:

bash
ls -la .env* 2>/dev/null          # which credentials files exist?

# Connectivity test (swap the ENV_FILE prefix for whatever applies; drop it on a staging branch):
ENV_FILE=<prod creds> DATA_API_ENV=production .venv/bin/python -c \
  "from etl.config import OWID_ENV; print('DB OK:', OWID_ENV.read_sql('SELECT 1 AS x').iloc[0,0])"

If no prod credentials file exists and you're not on a staging branch with the data, stop and ask the user which credentials / env file to use (e.g. "which env file holds DB credentials that can reach the explorer + MDIMs? Or should I run this from a staging branch?"). Then use that file as the ENV_FILE= prefix for both script invocations below. Don't hardcode credentials.

If the connection works but a query returns nothing, the scripts stop with a clear message (explorer slug not found, or MDIM not published in this DB) — that means the DB you reached doesn't have it, so re-check which DB you're pointed at.

Workflow

1. Extract views + scaffold
bash
.venv/bin/python .claude/skills/map-explorer-to-mdim/scripts/extract_views.py \
  --explorer <slug> \
  --mdim <ns/v/short#short> [--mdim <ns/v/short#short> ...] \
  --out ai/<slug>-mdim-mapping

Writes into the out folder:

  • explorer_views.csv — id (1..N) + dimension_1..M (explorer display values).
  • multidim_<short>_views.csv — one per MDIM; id is letter-prefixed by --mdim order (A1…, B1…, C1…) so ids are unique across MDIMs; columns are the MDIM dimension slugs.
  • _scaffold.md — the explorer dimension legend (which dimension_i is which name), the distinct values per dimension, each MDIM's dims/choices, auto-suggested value matches (where a slugified explorer value equals a real MDIM choice slug), and a ready-to-edit mapping_rules.py template.
  • _sources.json — machine-readable record of the explorer slug + dimension names and each MDIM's short/prefix/catalogPath/dim-slugs. Consumed by build_mapping.py to emit mapping.json (below); don't hand-edit it.
2. Write mapping_rules.py

Open _scaffold.md, then write ai/<slug>-mdim-mapping/mapping_rules.py defining:

  • EXPLORER_DIMENSIONS — list naming dimension_1..N (copy from the scaffold; keep order).
  • MDIMS — MDIM short names in the same order as --mdim (= prefixes A, B, C, …).
  • route(dims) -> str — given a view's {dimension name: value}, return the target MDIM short name. For a single MDIM this is just return "<short>". For several, it's a decision on some explorer dimension (e.g. natural-disasters routes on Impact: Deaths→deaths, Economic damages (% GDP)→economic_damages, the rest→affected).
  • translate(dims, mdim) -> dict — return {mdim_dim_slug: choice_slug} for the target MDIM view, built from the *_MAP dicts. Only include slugs the MDIM actually has (e.g. economic_damages has no metric — single-choice dims are pruned from MDIM views).
  • (optional) DEFAULT_MDIM = "<short>" — the catch-all target for the bare explorer URL (see mapping.json → catchAll). Omit it and the best-fitting MDIM is chosen automatically (the one receiving the most resolved views; tie-break = earliest in MDIMS). Set it only when the automatic pick isn't the MDIM you'd want a param-less explorer link to land on.

The scaffold seeds the *_MAP dicts with slugify(value) guesses. Verify every entry — slugify won't catch label↔slug differences like Decadal average→decadal, Injuries→injured, Volcanoes→volcanic_activity, or aggregate collapses like All disasters/All disasters (by type)→all_stacked.

3. Build the proposal
bash
.venv/bin/python .claude/skills/map-explorer-to-mdim/scripts/build_mapping.py --out ai/<slug>-mdim-mapping

Writes mapping_proposal.csv, one row per explorer view:

columnsmeaning
id, dimension_1..Nthe explorer view (same as explorer_views.csv)
target_mdim, target_view_idthe resolved target (target_view_id is the A*/B*/C* id)
<mdim>_<dimslug> …wide block; only the target MDIM's columns are filled with the translated slugs
shared_target_explorer_idswhen >1 explorer view lands on the same MDIM view, the comma-joined list of all those explorer ids (e.g. 1,12); empty when the target is unique

It also writes mapping.json — the machine record — and admin_bulk_payload.json, which is what you actually apply. The API exists: POST {admin}/api/multi-dim-redirects/bulk (handleBulkCreateMultiDimRedirects), also reachable from the Bulk-create redirects from JSON button on /admin/multi-dim-redirects. This endpoint is the only way to apply an explorer redirect: the CSV CLI that map-charts-to-mdim hands over (createMultiDimRedirectsFromCsv) accepts an /explorers/ source, but it writes no sourceQueryParams — so the row bakes as one unconditional rule and every view of the explorer 302s to a single MDIM view, per-view routing gone. Its Zod schema deliberately mirrors this file's catchAll + redirects shape, ignores keys it doesn't know (sourceViewId, viewId, mdim, stats, targets), and reports target: null entries as skipped.

Post admin_bulk_payload.json, never mapping.json. The payload has empty-valued source dimensions stripped out. That is mandatory, not cosmetic: a condition is matched against the incoming URL's params, and an absent param is not an empty string — so a condition of {"Period": ""} can never match, and every view carrying one silently falls through to the catch-all instead of its intended target. mapping.json keeps them because it is the faithful record of the view grid.

Unlike the CSV (positional dimension_N columns, meant for a spreadsheet), the JSON carries every identifier a redirect needs:

jsonc
{
  "explorer": { "slug": "...", "dimensions": ["<name>", ...] },
  "targets":  [ { "mdim": "...", "catalogPath": "ns/v/short#short", "dimensions": ["<slug>", ...] } ],
  "stats":    { "total": N, "resolved": N, "unresolved": N },
  "catchAll": {                        // bare explorer URL (no query params) fallback
    "source": { "explorerSlug": "..." },
    "target": { "mdim": "...", "catalogPath": "ns/v/short#short",
                "viewId": null, "dimensions": {} }   // no params → the MDIM's default view
  },
  "redirects": [
    {
      "sourceViewId": 1,
      "source": { "explorerSlug": "...", "dimensions": { "<name>": "<value>", ... } },
      "target": {                       // null when unresolved
        "mdim": "...", "catalogPath": "ns/v/short#short",
        "viewId": "A2",                 // internal id, cross-references the CSVs
        "dimensions": { "<slug>": "<choiceSlug>", ... }
      },
      "sharedTargetSourceIds": [1, 29, 57],   // present only when >1 source shares this target
      "unresolvedReason": "..."               // present only when target is null
    }
  ]
}

The source view is identified by the explorer slug + dimension name→display-value (the explorer URL query params); the target view by the MDIM catalogPath + dimension slug→choice-slug (the MDIM URL query params). Unresolved views are kept with target: null so the API can see the full picture; a consumer typically skips them.

An unresolved view is not a view left alone. The endpoint reports target: null entries as skipped, so they get no rule of their own — and the catch-all constrains no params, so it matches them instead. Those URLs land on the target MDIM's default view, carrying the explorer's own params into the grapher URL, rather than on anything equivalent to the view that was asked for. Leaving views unresolved is a deliberate call (they may genuinely have no MDIM counterpart), never a no-op; preflight reports the count so it gets made on purpose.

catchAll is always present: it redirects the bare explorer URL (no query params) — and serves as the sensible fallback for any view a consumer doesn't route individually — to the best-fitting MDIM with no query params, which grapher renders as that MDIM's default view (hence viewId: null, dimensions: {}). The best-fitting MDIM is the one most views resolve to, or whatever DEFAULT_MDIM in mapping_rules.py overrides it to.

The catch-all's destination is the only one nothing pins. Its row stores viewConfigId = NULL, so it resolves at request time to whichever view the MDIM renders for an empty selection — the first view in config order (grapher's filterToAvailableChoices takes the first available view's choice at every dimension). Rebuilding the MDIM can reorder its views, or edit that view's config in place, and the bare explorer URL then lands somewhere nobody reviewed. Extraction records that view in _sources.json (not in the payload, which must stay byte-reproducible) and preflight warns when it moves. A warning, not a blocker: the destination is a moving reference by construction, so blocking would flip an approved catch-all to NOT READY on any unrelated MDIM rebuild. To pin it, give the catch-all an explicit target view instead.

The script prints a validation report: how many explorer views resolved, distinct MDIM views hit per MDIM, how many rows share a target, and FLAGS for any explorer view that didn't resolve to a real MDIM view (fix the rules and re-run until there are no flags).

4. Review

Sanity-check the flagged rows and the judgment calls (approximate type matches, aggregate collapses, MDIM choices with no explorer source). For a topic owner's sign-off, build the side-by-side review page: each explorer view on the left, the MDIM view it will redirect to on the right, with approve/flag controls.

bash
.venv/bin/python .claude/skills/map-explorer-to-mdim/scripts/build_review.py \
    --mapping-dir ai/<slug>-mdim-mapping \
    --explorer-slug <slug> \
    --mdim-slug <short_1>=<grapher_slug_1> \
    --mdim-slug <short_2>=<grapher_slug_2> \
    [--host https://ourworldindata.org] \
    [--output ai/<slug>_view_review.html] \
    [--no-coverage]

--mdim-slug maps each MDIM short name in mapping_rules.MDIMS to its published Grapher slug (the /grapher/<slug> part of the URL, not the catalogPath). _sources.json records the slugs extraction saw, and multi_dim_data_pages.slug has them if you have DB access; otherwise ask. The default host is production; for MDIMs that only exist on a staging branch pass --host https://staging-site-<branch>.

The script prints a coverage summary before writing the HTML: rows, distinct MDIM targets, many-to-one collapses, unresolved rows, MDIM views never targeted. Read it; it is the fastest way to spot a mapping gap the reviewer cannot see by eye.

What the reviewer gets: one pair at a time in iframes, the selection shown as chips above each chart, Approve / Flag / Clear with an optional note, keyboard navigation (← →, a, f, c), filters and live counts. Decisions auto-save to the browser's localStorage, can be mirrored to a JSON file on disk (Chrome/Edge), and Import merges another reviewer's export. The file is self-contained: send it directly, or publish it to vibe.owid.io with /owid-staff:create-vibe-app (from the owid-staff plugin, auto-installed here) when the whole team needs a link.

When the reviewer is done, ask for the exported JSON (or the auto-saved one): every row carries status (approved / flagged / blank) and note. Approved rows are ready to wire up; flagged rows need a second pass with the user. Corrections go through mapping_rules.py and a rebuild (step 3), never through the HTML.

Gotchas:

  • localStorage is per browser and per file path, so switching machine loses the decisions unless they were exported or mirrored to disk.
  • Single-choice MDIM dimensions are pruned from the URLs, matching the wide block of mapping_proposal.csv; do not add them back by hand.
  • Explorer URL parameters are display names (Disaster Type=Floods), which is what the dimension_1..N columns hold. hideControls=true is appended to both sides; "open ↗" shows the view with its controls.
  • A mapping hand-made in another schema (for example a colleague's multi-block CSV) must be converted to mapping_proposal.csv plus mapping_rules.py first; a one-off adapter in ai/ is fine.
5. Audit what references the explorers (ALWAYS offer; run when the user says yes)
bash
ENV_FILE=<prod creds> DATA_API_ENV=production .venv/bin/python \
  .claude/skills/map-explorer-to-mdim/scripts/audit_references.py \
  --mapping ai/<a>-mdim-mapping --mapping ai/<b>-mdim-mapping \
  --out ai/<combined>-redirects

Writes references.csv + references.md across all the explorers in one pass. It resolves each referencing URL through the rules the payload would create, so every row says which view the reader lands on — and separates the link had no params from the link names a choice the explorer has since dropped, which lands on the MDIM's default view and needs authoring attention rather than a URL swap.

The timeline differs from the chart skill's, and this is the thing to say out loud: an embedded explorer breaks the moment the redirect is created, not later at unpublish, because the embed renders by fetching the explorer page and parsing it. So the 🔴 rows are migrated before step 7, not after.

The report also carries a ⭐ Featured metrics section — the one surface where this skill's usual "a link survives the 302" reasoning inverts. A featured metric is a topic-page slot held by URL, resolved only when Algolia indexes, matching pathname and exact params against published records. So it does not survive: it empties silently, and cannot be re-added once the explorer is gone. Hence step 5b.

Work the ⭐ section of references.md, by hand at /admin/featured-metrics. These are editorial slots, so ask whoever owns the topic before repointing their rail.

Per row — add the MDIM view under the same tag and income group, drag it to the old ranking, delete the old row, then re-apply boost in search if it was on. One explorer view can hold several rows: the key is (URL, tag, income group). Full procedure: docs/guides/data-work/redirect-to-mdims.md.

Why here and not after step 7: creating the redirect darkens the explorer on the spot, and adding a featured metric requires a published slug. Once the redirect exists, no replacement is accepted and no record of the ranking survives. Unlike an embed, which breaks visibly, this fails quietly. The replacement is the bare MDIM view, not the redirect target — the admin strips reader params on paste and never validates the dimension params.

Show full SKILL.md (1,522 more words)Show less
6. Preflight (read-only, gated)
bash
ENV_FILE=<prod creds> DATA_API_ENV=production .venv/bin/python \
  .claude/skills/map-explorer-to-mdim/scripts/preflight.py \
  --mapping ai/<a>-mdim-mapping --mapping ai/<b>-mdim-mapping \
  --out ai/<combined>-redirects [--record ai/<combined>-redirects/bulk_redirects.json]

Mirrors every validation the endpoint performs, per explorer — because it memoizes its source-side checks and re-throws the cached rejection, so one source-level problem fails every entry for that explorer. Blockers: the explorer path is already a site-redirect source, or the target of one (the chain case); the slug collides with a chart's old slug; a target MDIM is missing or unpublished; the target /grapher/<slug> is itself a redirect source; two views share a source condition; the view fingerprint no longer matches the live explorer; the payload was not built from the extraction sitting beside it; redirects already exist that differ from the payload; or embedded references are outstanding. A missing references.csv is a blocker too — "never looked" must not read like "looked and found nothing".

Two of those checks exist because nothing else ties the payload to the run that produced it, and an aborted or skipped rebuild leaves a stale one in place. build_mapping.py writes mapping_proposal.csv and mapping.json before admin_bulk_payload.json, and aborts between them on duplicate conditions — so the artifacts a human reviews can describe one build while the payload about to be posted comes from another, with the fingerprint check none the wiser (it validates the extraction, not the rules). Preflight therefore checks both ends:

  • the payload's source conditions against explorer_views.csv + _sources.json, which catches an extraction re-run with no rebuild at all;
  • the payload as a whole against the transform of the mapping.json beside it, which is what catches a rebuild that aborted in between — including one where only the targets changed, invisible to any source-side check.

The reference gate is bound to live state the same way. audit_references.py records a referenceDigests entry per explorer in references_manifest.json; preflight re-runs the sweep and compares. Without that, the gate reads a CSV from an earlier run and cannot see a page that added an embed since, or an audit folder carried over from another migration — both of which would read as a clean audit while the redirect is about to break something live.

It also records mappingDigests, binding the audit to the mapping its advice came from. Every replacement URL in references.md is derived from the mapping, so rebuilding the mapping with different targets invalidates that advice while the explorer's own references — and so the reference digest — stay identical. This is the one staleness with no second chance: an operator who repoints an embed at the wrong view leaves it no longer naming the explorer, so no later sweep can ever surface the mistake. Preflight blocks on it before the reference gate reports.

!!! note "An audit folder predating a surface reads as drifted, and should" The digest hashes the findings, so adding a surface to find-chart-references invalidates every recorded referenceDigests — most recently when featured metrics were added. Preflight blocks on an audit folder from before that, which is correct: it really is missing rows. Re-run audit_references.py rather than reading it as a bug.

Unverifiable is a blocker, not a warning, in all three cases — no extraction pair, no mapping.json, no reference digest. A warning does not reach the exit code, so Ready would print over a report stating in plain words that the payload's provenance is unknown. Re-running extract_views.py / build_mapping.py / audit_references.py is the cheap way to clear any of them; posting an artifact nothing backs is not.

A retired explorer whose redirects are all live reports DONE and exits 0. That is the finished state, not an error. But retirement does not retire the target side: an MDIM that has since been unpublished, rebuilt, re-slugged or edited in place breaks redirects that are already in the DB, and no explorer row is left to notice it. So every target check runs for a retired explorer too, and a slug carrying any blocker is never reported DONE.

Non-zero exit means do not post anything.

7. Apply — the admin bulk endpoint (GATED, production)

[!WARNING] Creating the redirect darkens the explorer immediately. It is checked on every /explorers/* request, ahead of env.ASSETS.fetch, so it beats the baked explorer page and any _redirects entry, and fires while the explorer is still published. There is no staged rollout and no bulk undo — removal is one row at a time.

Paste each admin_bulk_payload.json into Bulk-create redirects from JSON at {admin}/multi-dim-redirects, one explorer at a time. Read the response positionally: every results[i].source is the same /explorers/<slug> string, so index 0 is the catch-all when present and index i is redirects[i-1]. Expect created + skipped + errors == entries.

Then wait for the bake plus ~2 minutes (the redirect map is fetched with a 2-minute edge TTL) and verify:

bash
curl -sI "https://ourworldindata.org/explorers/<slug>?<one view's params>" | grep -i "^HTTP/\|^location:"
# expect 302 + location: /grapher/<mdim>?<view dims>
8. Retire the explorer, then its ETL step

The explorer is unpublished or deleted BY HAND in the admin. Removing the ETL step does not unpublish it — the explorers row survives, so it keeps showing up in listings and search. Do not flip isPublished in the step's .config.yml and re-run hoping to achieve it; that is not the route (and no explorer config in the repo carries isPublished: false).

Then remove the explorer's ETL footprint — and never delete a step without archiving it (see CLAUDE.md). Delete the step's .py and its sibling .config.yml (the periodic archive sweep only knows .py, which is why orphaned configs exist on disk today), remove the dag/*.yml entry, make check, commit; then run .venv/bin/etl archive-dag and commit dag/archive/*.yml separately, since it reads committed history. git checkout anything unrelated it sweeps in. Archive anything now orphaned upstream — a garden step that existed only to feed this explorer — in a second round. If any of those is a migrated/backport dataset, delete its now-orphaned snapshots/backport/latest/dataset_<id>_* mirror files too: archiving the DAG entry leaves them on disk, and nothing will point at them again.

Grep before deleting a shared explorer data step. Some are consumed off-DAG, by scripts fetching their published catalog CSVs by URL. Those consumers are invisible to .venv/bin/etl archive-dag, so the step looks like a safe leaf when it is not. Search for its catalog URL first and keep it until every consumer is retired.

Track these steps with TodoWrite in the chat. Do not generate a checklist file, and there is no HANDOFF.md for this skill — unlike the chart path there is no cross-team handoff, since the same operator pastes the payload.

What each run writes

filewritten bycontents
explorer_views.csvextractid 1..N + dimension_1..M (display values)
multidim_<short>_views.csvextractone per MDIM, ids A1…/B1…
_scaffold.mdextractdimension legend, distinct values, auto-matches, mapping_rules.py template
_sources.jsonextractslugs, catalogPaths, MDIM ids/slugs/published, viewsFingerprint, configMd5 — don't hand-edit
mapping_rules.pyyourouting + value translation
mapping_proposal.csvbuildone row per explorer view, wide target block
mapping.jsonbuildfaithful machine record (empty source dims kept)
admin_bulk_payload.jsonbuildthe apply unit — one per explorer, paste into the admin modal
references.csv / references.mdauditcombined across explorers, in --out
bulk_redirects.jsonpreflight --recordcombined record; not postable
ai/<slug>_view_review.htmlreviewself-contained side-by-side HTML with approve/flag controls, for the topic owner

Notes & gotchas

  • MDIM views come from multi_dim_data_pages.config (published, fully expanded). This already reflects code-generated views (group_views aggregates) and pruned single-choice dimensions — so e.g. a metric that has only one active choice won't appear as a column.
  • Many explorer views can redirect to one MDIM view — that's expected (the explorer often splits a concept the MDIM merges, e.g. a single-line "All disasters" total and a stacked-by-type view both mapping to the all_stacked MDIM view). shared_target_explorer_ids surfaces these so the reviewer sees the collisions.
  • An MDIM may have choices with no explorer source (e.g. an …_excluding_extreme_temperature aggregate). Nothing redirects to those — fine, just confirm.
  • Explorer dimension columns stay dimension_1..N (compact, and joinable to the explorer CSV); the name legend lives in _scaffold.md and in EXPLORER_DIMENSIONS.
  • Re-running extract_views.py overwrites the CSVs and _sources.json but not your mapping_rules.py.
  • mapping.json needs _sources.json; if you extracted before this output existed, just re-run extract_views.py once (it preserves mapping_rules.py), then build_mapping.py.
  • One payload per explorer is a correctness requirement, not a convention: the endpoint's schema has a single catchAll, so a merged file would silently drop all but one.
  • There is no bulk delete — only DELETE /api/multi-dims/:id/redirects/:redirectId, one row at a time. A 460-row batch posted wrongly is expensive to undo, which is why the preflight exists.
  • Explorer redirects never reach _redirects (getRecentMultiDimRedirects excludes non-/grapher/ sources), so there is no static-redirect fallback and no one-week window — they live only in the baked redirect map.
  • Matched source params are deleted from the outgoing URL and unmatched ones leak through. The target view's params win; country=, time=, tab= ride through untouched, which is what a reader following an old link wants.
  • An explorer view's id is positional row order — a re-saved TSV renumbers every id that mapping_proposal.csv, the review HTML and sourceViewId key on. That is what viewsFingerprint detects; configMd5 is only a secondary signal, since it flips on any FAUST edit and gating on it would force a needless re-review.
  • Algolia keeps indexing the explorer until the next index build, so it can still appear in site search after the redirect exists.
  • Retiring the explorer row itself is separate from the redirect: deleting it makes linkedChart unresolvable, so Chart blocks render nothing and tiles vanish. The five WB/WID inequality explorers migrated in July are the worked example.

© owid, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 6 other files (scripts) in .claude/skills/map-explorer-to-mdim of owid/etl.

  • SKILL.md
  • scripts/audit_references.py
  • scripts/build_mapping.py
  • scripts/build_review.py
  • scripts/extract_views.py
  • scripts/preflight.py
  • scripts/redirect_rules.py

Open the folder on GitHubat commit d5ba5a6

Compare with similar skills

Map Explorer To Mdim next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Map Explorer To Mdim compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Map Explorer To Mdim this skillowid/etl159—~7.3kAutomated safety check: NotesMIT
Crawl4AI Web Scrapingsmallnest/goclaw5991 repos~2.5kAutomated safety check: PassMIT
Glue 09 10 Migrationaws-samples/aws-glue-samples1.5k—~2.4kAutomated safety check: PassMIT-0
Migrate Glue Devendpoint To Interactive Sessionsaws-samples/aws-glue-samples1.5k—~3.6kAutomated safety check: PassMIT-0
Dbt Databricks PR Readydatabricks/dbt-databricks380—~2.8kAutomated safety check: PassApache-2.0
Apache Spark EngineerJeffallan/claude-skills12k1 repos~1.7kAutomated safety check: PassMIT

Similar skills

  • Crawl4AI Web Scraping

    smallnest/goclaw

    Scrapes sites, handles JavaScript-heavy pages and extracts structured data with Crawl4AI, through its crwl CLI or Python SDK, including schema-based extraction without an LLM.

    599 GitHub starsUsed in 1 repo~2.5k tokens
    Data & AnalyticsAuto-check passed
  • Glue 09 10 Migration

    aws-samples/aws-glue-samples

    Official

    Upgrade an AWS Glue ETL job from Glue version 0.9 or 1.0 to Glue 4.0.

    1.5k GitHub stars~2.4k tokensUpdated 1 mo ago
    Data & AnalyticsAuto-check passed
  • Official

    Migrate a legacy AWS Glue development endpoint to a Glue interactive session, following the official AWS migration checklist.

    1.5k GitHub stars~3.6k tokensUpdated 1 mo ago
    Data & AnalyticsAuto-check passed
  • Dbt Databricks PR Ready

    databricks/dbt-databricks

    Official

    A skill your agent uses for an open dbt-databricks pull request, including your own PR or a fork PR, to assess merge readiness and optionally repair selected gaps on the PR head branch.

    380 GitHub stars~2.8k tokensUpdated today
    Data & AnalyticsAuto-check passed
  • Apache Spark Engineer

    Jeffallan/claude-skills

    Guides writing and tuning Apache Spark jobs: DataFrame and RDD code, Spark SQL, partitioning, caching, shuffle tuning and structured streaming.

    12k GitHub starsUsed in 1 repo~1.7k tokens
    Data & AnalyticsAuto-check passed
  • Mz Dbt Release

    MaterializeInc/materialize

    Cut a dbt-materialize PyPI release: bump the version in version.py and setup.py, date the Unreleased CHANGELOG entry, and open the release PR with a Ship: <url body.

    6.4k GitHub stars~1.2k tokensUpdated today
    Data & AnalyticsAuto-check passed

More from owid/etl

All 35 skills in this repo
  • Find every OWID surface that references a chart, indicator, MDIM, or explorer — articles (links vs embeds), explorers, narrative charts, data insights, static viz, key-chart slots, MDIM views.

    159 GitHub stars~4.9k tokensUpdated today
    Auto-check passed
  • Add a scatter view (with GDP per capita on x) to existing OWID charts via the admin API, mirroring the admin UI's "Add scatter type" defaults, then retire the old standalone "X vs.

    159 GitHub stars~19k tokensUpdated today
    Auto-check passed
  • Add new survey question codes (e.g. An agent skill from owid/etl.

    159 GitHub stars~11k tokensUpdated today
    Auto-check: notes
  • Build or refresh an OWID static visualization end to end — resolve what data it needs from an old static viz image, an indicator, or a grapher chart; check both the ETL catalog and the producer's…

    159 GitHub stars~8.3k tokensUpdated today
    Auto-check passed
  • Propose redirects from (soon-to-sunset) grapher charts to the matching views of published MDIMs.

    159 GitHub stars~9.6k tokensUpdated today
    Auto-check: notes
  • Check chart or multidim preview on the staging server using a browser.

    159 GitHub stars~1.2k tokensUpdated today
    Auto-check passed

Questions about Map Explorer To Mdim

What does Map Explorer To Mdim do?

Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end. Map Explorer To Mdim is an agent skill from owid/etl. Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end.

When should I use Map Explorer To Mdim?

Map Explorer To Mdim fits situations like: the user says map explorer <slug to mdim(s) <..; suggest explorer-MDIM redirects; were sunsetting the <slug explorer; map its views to the new multidims.

How do I install Map Explorer To Mdim in Claude Code?

Run `npx skills add owid/etl --skill map-explorer-to-mdim -a claude-code`. Or copy the skill folder (.claude/skills/map-explorer-to-mdim in owid/etl) into .claude/skills/map-explorer-to-mdim in your project. Claude Code loads it when a task matches its description.

How do I install Map Explorer To Mdim in Codex?

Run `npx skills add owid/etl --skill map-explorer-to-mdim -a codex`. Or copy the skill folder (.claude/skills/map-explorer-to-mdim in owid/etl) into .agents/skills/map-explorer-to-mdim in your project. Codex loads it when a task matches its description.

Can I use Map Explorer To Mdim in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add owid/etl --skill map-explorer-to-mdim -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/map-explorer-to-mdim, .gemini/skills/map-explorer-to-mdim, .github/skills/map-explorer-to-mdim and .opencode/skills/map-explorer-to-mdim in your project.

What does Map Explorer To Mdim need to run?

Going by SKILL.md and its folder, Map Explorer To Mdim needs Python for the scripts in its folder and the command-line tools its instructions call (python, curl, make and git). Our summary lists: Python 3.

Does Map Explorer To Mdim access the network?

SKILL.md names 1 domain. In commands or code: ourworldindata.org; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.

Is Map Explorer To Mdim safe to install?

Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Map Explorer To Mdim use?

Map Explorer To Mdim is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Map Explorer To Mdim use?

About 7.3k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Map Explorer To Mdim?

Skills that share tags, products or a category with Map Explorer To Mdim: Crawl4AI Web Scraping (smallnest/goclaw, 599 stars), Glue 09 10 Migration (aws-samples/aws-glue-samples, 1.5k stars), Migrate Glue Devendpoint To Interactive Sessions (aws-samples/aws-glue-samples, 1.5k stars) and Dbt Databricks PR Ready (databricks/dbt-databricks, 380 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Map Explorer To Mdim?

owid (a GitHub organization) maintains it in owid/etl, which has 159 GitHub stars. The repository holds 35 skills in this directory. The repository was last updated on October 9, 2026.

Source: owid/etl on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.