From data2story-pro
Finds and validates datasets for Data2Story blogs. Accepts a topic, URL, or category; downloads and checks against 4 completeness gates. Local-first with support for full re-fetch or validate-only audit.
How this skill is triggered — by the user, by Claude, or both
Slash command
/data2story-pro:find-data <topic | URL | category | folder-path> [--mode single|theme] [--source Economist|Pudding|tidytuesday] [--out ./datasets/<name>] [--validate-only]<topic | URL | category | folder-path> [--mode single|theme] [--source Economist|Pudding|tidytuesday] [--out ./datasets/<name>] [--validate-only]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Turn an idea, URL, or category into a `phase2/datasets/<name>/` folder
Turn an idea, URL, or category into a phase2/datasets/<name>/ folder
that the /data2story-pro pipeline can run on without crashing.
You are the gatekeeper before the 7-agent newsroom. Detective, Analyst, Editor, Designer, Programmer, Auditor, Inspector all assume the data is already there, parseable, and provenanced. Your job is to make sure that's true before they start.
Refuse to mark a folder ready until it passes 4 gates. See
references/completeness_gates.md for the criteria. The gates are codified
in tools/audit.py, not in this prose.
Python deps for the tools/:
pandas — required (audit.py reads/inspects CSV/JSON).openpyxl — required only when a source is .xlsx (audit.py's
pd.ExcelFile); a mid-run missing-openpyxl is the usual cause of an XLSX
audit error.urllib — stdlib, no install (fetch.py downloads, audit.py HEAD-checks).One install line:
pip install pandas openpyxl
After editing anything under tools/, run the no-network regression suite:
py tools/selftest.py (exit 0 = pass).
SKILL_DIR = directory containing this SKILL.mdWORKSPACE = ancestor that contains phase2/datasets/ (the parent repo root), if one existsDATASETS_ROOT = WORKSPACE/phase2/datasets when WORKSPACE exists; otherwise ./datasets
(clone-relative, under the current working dir) — the open-source clone has no phase2/datasetsBLOGS_ROOT = WORKSPACE/phase2/blogs when WORKSPACE existsOUT_DIR default = DATASETS_ROOT/<name> (so ./datasets/<name> on an OSS clone). Callers may
override with an explicit --out, which always wins.INPUT = first positional argument from $ARGUMENTS$ARGUMENTS: --mode, --source, --out, --validate-onlyNever hard-code machine paths. Resolve at runtime by walking up from SKILL_DIR.
Decision tree (the FIRST condition that matches wins):
--validate-only flag present → MODE = validate-only. INPUT must be an existing folder path.INPUT is an existing folder path (and --validate-only absent) → still ask the user before re-fetching; default to validate-only behaviour with a confirmation.INPUT starts with http:// or https:// → MODE = url.INPUT (case-insensitive) exactly matches a DIP category from phase2/datasets/data_is_plural/dip_category_summary.md → MODE = category.topic.State your classification out loud in one line before doing anything else.
No local corpora (open-source clone): on a machine without the Economist/, Pudding/,
tidytuesday/ clones and without the Data-is-Plural CSV under phase2/datasets/, browse_local.py
and dip_query.py both return empty (no crash). In that case there is nothing to match locally, so
topic/category discovery degrades straight to WebSearch (Step 1). No flag is needed — detect it
from the empty browse_local.py + dip_query.py results. Keep the local-first path intact for
machines that DO have the corpora.
Just audit. Skip discovery, skip fetch.
python "SKILL_DIR/tools/audit.py" "<folder>"
This writes <folder>/validate.json and prints the gate summary. Read it back
with Read, surface the verdict, and stop.
If overall.ready_for_data2story is true → print the /data2story-pro <folder>
command for the user to copy.
If it's false → list the specific gate failures and recommend remediations.
Determine OUT_DIR (use --out if given, else derive a slug from the URL path's
last meaningful segment, store under DATASETS_ROOT/<slug>/ — i.e. ./datasets/<slug>/
on an open-source clone with no phase2/datasets).
Branch on URL shape:
| URL pattern | Tool |
|---|---|
github.com/{owner}/{repo}/tree/{branch}/{path} | python fetch.py github-folder <url> <out_dir> |
github.com/{owner}/{repo}/blob/{branch}/{path} | Same (fetch.py auto-detects blob) |
github.com/{owner}/{repo} with no path | Refuse — tell user to narrow to a folder |
| Anything else | python fetch.py url <url> <out_dir> |
fetch.py is source-aware: if the URL points to TheEconomist/graphic-detail-data,
the-pudding/data, or rfordatascience/tidytuesday AND the local clone already
contains that path, it copies from the local clone (no network). Otherwise it
fetches via the GitHub API. This happens automatically — do not handle it yourself.
Several hosts serve a human-facing page at the obvious URL, not the raw bytes,
so a naive download returns HTML instead of a .csv/.json/.xlsx. fetch.py
auto-rewrites GitHub /blob/ URLs to raw.githubusercontent.com and, as a
backstop, warns on stderr if a data-extension download comes back as an HTML
page (<!doctype html> / <html). The other common share-hosts are not
auto-rewritten — pass the direct-download form yourself:
| Host | Page URL (returns HTML) | Direct-download form to use |
|---|---|---|
| GitHub | github.com/<o>/<r>/blob/<branch>/<path> | auto-handled → raw.githubusercontent.com/<o>/<r>/<branch>/<path> |
| Google Drive | drive.google.com/file/d/<id>/view | drive.google.com/uc?export=download&id=<id> |
| Dropbox | ...?dl=0 | swap to ...?dl=1 (or dl.dropboxusercontent.com) |
| OneDrive | share link (1drv.ms/...) | append &download=1 to the direct link |
| Kaggle | dataset page (kaggle.com/datasets/...) | the file API / CLI download — the page is not a file |
If a download trips the HTML warning, you almost certainly handed fetch.py a
share/page URL — fix the URL to its direct-download form and re-fetch.
After fetch: check whether the folder has a README.md.
tools/README_template.md. Auto-fill what you can
(title from folder name, column headers from the first CSV, source URL from
the manifest). Leave the codebook definition cells as {TODO} for the user
to fill in. Tell the user explicitly that README is a stub.INPUT is one of the 14 DIP categories. Run both browse and DIP query in parallel:
python tools/browse_local.py "<INPUT>" --top 10
python tools/dip_query.py --category "<INPUT>" --top 10
Both emit JSON. Merge:
Present top 5 to the user as a numbered list via AskUserQuestion:
Then route the pick:
links array. If multiple URLs,
pick the one with a data-file extension (csv/xlsx/json) or the GitHub one.
Route to url mode for that URL.Same as category mode but use full-text query:
python tools/browse_local.py "<INPUT>"
python tools/dip_query.py "<INPUT>" --top 10
If BOTH return empty (no local hits, no DIP hits), only THEN do WebSearch:
<INPUT> open dataset CSV site:ourworldindata.org OR site:github.com OR site:data.gov.
Cap web results at 5. Surface them with a "no local matches found, querying web" disclaimer.
From the WebSearch results, pick the one whose URL ends in a data-file extension
(.csv / .xlsx / .json / .tsv) or is a github.com/{owner}/{repo}/tree/{branch}/{path}
folder. Route that URL through url mode: python tools/fetch.py url <url> <OUT_DIR> for a direct
data file, or python tools/fetch.py github-folder <url> <OUT_DIR> for a GitHub folder. Then
continue to the README step (Step 2) and the audit step (Step 3) exactly as in url mode — this
closes the topic → web → fetch → audit loop. If no result has a usable data URL, report that
honestly and do not fabricate one.
After fetch, the OUT_DIR may or may not have a README. Decide:
tools/README_template.mdWhen generating, fill in:
{TITLE}: human-readable from folder slug (snake_case → "snake case", title-cased){PRIMARY SOURCE URL}: from manifest.json items' urls{filename.csv} rows: scan each CSV's columns{Codebook}: list each column with {TODO: definition} for unknown ones; you
may guess from column names (launch_year → "year of launch (integer)") but
flag guesses with a {?} prefix so the user can correct.python "SKILL_DIR/tools/audit.py" "<OUT_DIR>"
This writes <OUT_DIR>/validate.json. Read it back.
For Gate 4 (multimodal — advisory only), audit.py cannot judge by itself.
Do your own quick check after reading validate.json:
From the CSV columns and entity samples, classify subjects:
For concrete subjects, do ONE WebSearch with site:commons.wikimedia.org
to verify at least 1 reference photo exists for a representative subject.
Do NOT download photos — just verify they exist. That's Detective's job.
Write your Gate 4 finding back into validate.json under
gates.multimodal.info.
Read the final validate.json. Print a concise summary:
=== find-data verdict ===
folder: <OUT_DIR>
files: <N>
[OK ] Technical
[WARN] Story material
- <filename.csv>: only 47 rows (< 50)
[OK ] Provenance
[INFO] Multimodal: concrete subjects (agencies, rocket types)
overall: READY
next: /data2story-pro <OUT_DIR>
If overall is BLOCKED, do NOT print the /data2story-pro command. Instead print:
overall: BLOCKED
to fix: <specific remediations from the fails list>
Suggest concrete fixes:
Append a one-line entry to DATASETS_ROOT/_find-data-log.md (create if not
present) so the user has a running history:
- 2026-05-25T14:00Z · single · datasets/<name> · READY · invoked via URL
DATASETS_ROOT/<OUT_DIR>/) and the digest log.{PRIMARY SOURCE URL} placeholder and warn the user.references/completeness_gates.md — exact pass/warn/fail criteria for each gatereferences/input_mode_dispatch.md — full URL classification tablereferences/examples/good_economist.md — what single mode output should look likereferences/examples/good_theme.md — what theme mode output should look liketools/audit.py — deterministic gate evaluatortools/fetch.py — source-aware downloader (manifest / github-folder / single-url modes)tools/browse_local.py — index Economist/Pudding/TidyTuesday for candidate matchingtools/dip_query.py — filter dip_categorized.csv by category or topictools/README_template.md — graphic-detail-style skeleton for auto-generationnpx claudepluginhub qinghonglin/data2story-skill --plugin data2storyOrchestrates a 14-agent newsroom pipeline to turn a dataset (CSV, JSON, folder, or URL) into a verifiable multimedia blog post with images, interactive charts, and audit artifacts.
Find and evaluate research datasets for any scientific question. Maps questions to study designs and searches 30+ repositories including GEO, UK Biobank, and NHANES.
Find and evaluate research datasets for any scientific question. Maps questions to study designs and searches 30+ repositories including GEO, UK Biobank, and NHANES.