Agent skill

Add Ivs Indicators

by owid in owid/etl

Add new survey question codes (e.g. An agent skill from owid/etl.

MITAuto-check: notesDocuments & Office

Install Add Ivs Indicators

skills CLI
$ npx skills add owid/etl --skill add-ivs-indicators -a claude-code

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

GitHub CLI
$ gh skill install owid/etl add-ivs-indicators --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/add-ivs-indicators .claude/skills/add-ivs-indicators && 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
add-ivs-indicators
GitHub stars
159
Token cost
~10k tokens
SKILL.md length
4,213 words
Files
2 (incl. scripts)
Skills in repo
35
Repo updated
First seen
Licence
MIT

At a glance

Add new survey question codes (e.g. An agent skill from owid/etl.

  • Works in 11 steps: Verify scales against the .dta (NEVER… → Stata: snapshots/ivs//ivs_create_file.do → Re-snapshot (same version, no bump) → …
  • The user wants to add IVS/WVS/EVS questions
  • SKILL.md covers Why this pipeline is unusual, Reference docs (kept out of…, Step 0 — Verify scales against… and Step 1 — Stata:…, plus 10 more sections
  • Runs Python scripts from its folder; calls git, python and make; reaches admin.owid.io

What it does

Add Ivs Indicators is an agent skill from owid/etl. Add new survey question codes (e.g. C001, D059, H00201, Y022, E268, G055) to OWID's values-survey pipeline WITHOUT bumping the version — either the Integrated Values Surveys table (integratedvaluessurveys, WVS+EVS merged) or the World Values Survey table (worldvaluessurvey, WVS-only questions). Both tables live in the same ivs/<version garden+grapher dataset. Use when the user wants to add IVS/WVS/EVS questions, extend integratedvaluessurveys or worldvaluessurvey, or says "add these codes to IVS" / "add these WVS…

Its SKILL.md is about 10k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including scripts (for example `scripts/indicator_admin_table.py`).

It sits in Documents & Office. 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 wants to add IVS/WVS/EVS questions
  • Extend integratedvaluessurveys
  • Worldvaluessurvey
  • Says add these codes to IVS / add these WVS questions

Example prompts

  • “add these codes to IVS”
  • “add these WVS questions”
  • “/add-ivs-indicators”

Requirements

  • Python 3

Workflow steps

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

  1. Verify scales against the .dta (NEVER from memory)
  2. Stata: snapshots/ivs//ivs_create_file.do
  3. Re-snapshot (same version, no bump)
  4. Meadow: etl/steps/data/meadow/ivs//integrated_values_surveys.py
  5. Garden: etl/steps/data/garden/ivs//integrated_values_surveys.py
  6. Metadata: etl/steps/data/garden/ivs//integrated_values_surveys.meta.yml
  7. Pre-flight cross-check (before running the pipeline)
  8. Run & verify
  9. Git (per repo CLAUDE.md)
  10. Notion Available dictionary (required — IVS only)
  11. Indicator → admin-link table (share with reviewers / PR comment)

What it can do on your machine

Read from SKILL.md and the folder at commit 70c9705. 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 1 file in scripts/ (Python), which the agent can run.

    Shell commands in SKILL.md call:

    • git
    • python
    • make

    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:

    • admin.owid.io

    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

Add Ivs Indicators loads about 10k tokens when it runs. Until then it costs about 140 tokens; SKILL.md has 4,213 words of instructions outside code blocks.

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

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:600
    `live_grapher` creds from `.env`/`.env.live` — this is far more reliable than `.env`'s `127.0.0.1:3310`

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 70c9705, republished under its MIT licence (© owid). 4,213 words, ~10,301 tokens.

Download SKILL.mdSave it as .claude/skills/add-ivs-indicators/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
add-ivs-indicators
description
Add new survey question codes (e.g. C001, D059, H002_01, Y022, E268, G055) to OWID's values-survey pipeline WITHOUT bumping the version — either the Integrated Values Surveys table (integrated_values_surveys, WVS+EVS merged) or the World Values Survey table (world_values_survey, WVS-only questions). Both tables live in the same ivs/<version> garden+grapher dataset. Use when the user wants to add IVS/WVS/EVS questions, extend integrated_values_surveys or world_values_survey, or says "add these codes to IVS" / "add these WVS questions".
metadata.internal
true
metadata.owner
paarriagadap

Add indicators to the values-survey pipeline (IVS / WVS, no version bump)

Adds new question codes to OWID's values-survey pipeline without bumping the version — you extend the current ivs/<version> step in place. The dataset holds two tables, and you pick which one you're adding to:

  • integrated_values_surveys — the IVS (WVS + EVS merged trend). Source: the IVS .dta.
  • world_values_survey — WVS-only questions (asked in WVS but absent from IVS). Source: the WVS Time-Series .dta.

Both tables live in the same ivs/<v> garden + grapher dataset, so variable titles must be unique across both tables (Step 5/6). The two paths differ in only a handful of places; everything else in this skill is identical. Pick the target first, then read each step with the right column:

AspectIVS → integrated_values_surveysWVS → world_values_survey
Source .dtaIntegrated_values_surveys_1981-2022.dta (snapshots/ivs/<v>/)WVS_Time_Series_1981-2022_stata_v5_0.dta (snapshots/wvs/<wv>/)
Stata scriptsnapshots/ivs/<v>/ivs_create_file.dosnapshots/wvs/<wv>/wvs_create_file.do
Snapshot / meadowivs/<v>/integrated_values_surveyswvs/<wv>/world_values_survey
Garden codedrop_indicators_and_replace_nans + sanity_checksprocess_wvs + sanity_checks_wvs (same garden file)
Missing codesextended-missing .a/.b/.c/.d/.e (negatives unused)negative: -1 DK, -2 NA, -3 N/A, -4 not asked, -5 missing
DK / NA.a / .b-1 / -2
Keep rule (Step 1)keep if var>=1 then drop .c/.d/.ekeep if var>=-2 & var<. (drops -5/-4/-3 + sysmiss)
avg_scoregen avg_score=var (ext-missing auto-excluded)gen avg_score=var if var>=0 (exclude negative DK/NA)
zero→null in gardenyes (IVS has spurious zeros)no (WVS has none; absent country-years are NaN after merge)
Reference docsIVS Common EVS/WVS dictionaryWVS Time-Series variable list (F00003844…xlsx)
Notion Available dict (Step 9)yesn/a — skip (WVS has no Notion tracker)

Find the active versions: ls etl/steps/data/garden/ivs/ (shared garden/grapher dataset → <v>, e.g. 2025-06-27) and ls snapshots/wvs/ (WVS snapshot/meadow → <wv>, e.g. 2026-06-30).

Why this pipeline is unusual

Neither snapshot is a downloaded file — each is a CSV produced by a Stata script (*_create_file.do) that collapses survey microdata into country×year response shares (we publish only the aggregated shares, not the microdata — this also respects the WVS "no redistribution" license). So adding indicators means editing the .do, having the user run it in Stata (Claude cannot run Stata), then flowing the new columns through meadow → garden → grapher.

End-to-end flow (same for both targets):

edit <ivs|wvs>_create_file.do  →  USER runs it in Stata → regenerates the csv
  → re-snapshot (same version)  →  meadow VARS_DICT  →  garden constants + checks
  →  garden .meta.yml  →  etlr (meadow→garden→grapher)  →  [IVS only: Notion Available flags]

Reference docs (kept out of git — *.pdf/*.xlsx are git-ignored)

Two documents make scale/wording verification easy. They normally sit in snapshots/ivs/<v>/ during an IVS update but are git-ignored, so they're not in the repo. If they aren't in the snapshot folder, ask the user for them (or download from worldvaluessurvey.org → Data and documentation → Data Download):

  • Common EVS/WVS dictionary (…Common_EVS_WVS_Dictionary_IVS.xlsx) — maps each IVS code to its WVS-7 / EVS variable name (the Q-number) and label (sheet IVS_EVS_and_WVS_Variables). Path: Data Download → WVS/EVS Trend 1981-2022 → IVS documentation: IVS Common EVS-WVS dictionary.
  • WVS-7 master questionnaire (…WVS-7_Master_Questionnaire_…English.pdf) — verbatim question stems + answer-category wording (look up by Q-number). Path: Data Download → Wave 7 (2017-2022) (matches the current IVS version) → Questionnaire link.
  • EVS 2017 field questionnaire (ZA7500_q_gb.pdf, the Great Britain master) — the fallback for older EVS-only items that never made it into WVS-7 (so they're absent from the WVS-7 PDF). Look up by the EVS variable name / question number (e.g. the IVS E158 "concern about humankind" = EVS Q60, item v216 "the living conditions of all humans all over the world"). Source: europeanvaluesstudy.eu → Methodology, data, documentation → Survey 2017 → full release EVS2017 → participating countries → questionnaires.

To find which questionnaire a code lives in, check the dictionary's WVS-7 vs EVS columns: if the WVS-7 variable name is blank for that IVS code, it's EVS-only — go to the EVS questionnaire.

The 878 MB .dta is likewise git-ignored and must be present in snapshots/ivs/<v>/ locally to regenerate the CSV.


Step 0 — Verify scales against the .dta (NEVER from memory)

Model-recalled survey wording/scales are unreliable — verify. The authoritative code→category mapping for each variable lives in the .dta value labels. Read every code you're adding in one pass:

python
import pandas as pd
# IVS: snapshots/ivs/<v>/Integrated_values_surveys_1981-2022.dta   → convert_categoricals=True
# WVS: snapshots/wvs/<wv>/WVS_Time_Series_1981-2022_stata_v5_0.dta → convert_categoricals=False, then
#      inspect raw codes: WVS codes missing as NEGATIVES (-1 DK, -2 NA, -4 not asked, -5 missing); the
#      positive codes (and 0 where it's a real category, e.g. immigration G05x) are the substantive answers.
path = "snapshots/ivs/<v>/Integrated_values_surveys_1981-2022.dta"
# .dta is row-major: each read_stata() streams the whole 878 MB file once. Read EVERY code you
# need in ONE pass, then inspect each from the in-memory frame — NOT one read_stata() per code
# (that re-streams the 878 MB file per code: ~24 full passes for 24 codes, ~10x slower).
codes = ["C001", "C002", "H002_01"]  # all the codes you're adding
df = pd.read_stata(path, columns=codes, convert_categoricals=True)
for c in codes:
    print(c, list(df[c].cat.categories))   # order == code 1, 2, 3, … (the IVS coding)

(StataReader.value_labels() mapping is unreliable for these vars — use convert_categoricals=True.)

Key gotchas:

  • IVS often collapses the raw WVS scale. E.g. C001/C002 are 5-point in the WVS-7 questionnaire but 3 categories in the IVS .dta (1 Agree · 2 Disagree · 3 Neither). Trust the .dta categories.
  • Look-alike questions can have different scales. H002 neighborhood frequency = Very / Quite / Not / Not at all frequently; H008_02 ("felt unsafe at home") = Often / Sometimes / Rarely / Never — don't lump them into one block.
  • Confirm the Q-number + verbatim wording from the dictionary + questionnaire (see Reference docs).

For the user-facing wording, prefer the fuller questionnaire text; for the answer/category names the .dta labels and questionnaire may differ slightly ("some respect" vs "fairly much respect") — they mean the same; pick per the user's preference.

Step 1 — Stata: snapshots/ivs/<v>/ivs_create_file.do

The .do follows one idiom per question group: define a global listing codes → a preserve-scoped block (a foreach loop for multi-item groups, or a custom block for a single question) that recodes to 0/1 dummies → collapse (mean) … [w=S017], by(year country) → save a tempfile → later merge 1:1 year country in the "Combine all the saved datasets" section. New codes must also be added to the master keep S002VS S002EVS S003 S017 $questions line.

WVS target? Edit snapshots/wvs/<wv>/wvs_create_file.do instead — same idiom, but the master keep is keep S002VS S003 S017 $questions (no S002EVS) and the missing-value handling differs: per question use keep if \var' >= -2 & `var' < .(drops -5/-4/-3 and system-missing; keeps substantive + DK + NA), thendont_know_<q> = (`var' == -1), no_answer_<q> = (`var' == -2), substantive dummies on the positive codes, and for avg_score questions gen avg_score_<q> = `var' if `var' >= 0` (so negative DK/NA are excluded from the mean). The structural-twin table below still applies — only the keep / DK / NA lines change.

Reuse the closest existing structural twin instead of inventing a block:

New question shapeClone this existing block
4-pt with high/low aggregate (1|2 vs 3|4) + avg_scoreworries loop (H006)
binary 0/1 (keep if >= 0)neighbors loop (A124)
single 4-pt questionhappiness block (A008)
3-pt agree/disagree/neitherpolitical action loop (E025)
5-pt agree (with neutral)work loop (C039/C041)
5-pt frequency (Daily/Weekly/Monthly/Less than monthly/Never)extend the worries 4-pt loop to 5 levels (no exact twin)
10-pt agree / better-worseincome_equality block (E035): aggregates >=7 / 5|6 neutral / <=4, + avg_score native 1–10
multinomial 1-of-N named choice (respondent picks one option)environment_vs_econ block (B008): one 0/1 dummy per option, no aggregate, no avg_score
continuous 0–1 indexcustom (see Y022 below)

For a 10-pt block the three aggregates (>=7, 5\|6, <=4) + dont_know + no_answer partition to 100% — that's what check_sum_100 checks (avg_score is extra). For multinomial, the N option dummies + dont_know + no_answer sum to 100%. When several questions share the same option codes (e.g. E001/E002 "aims of country: 1st/2nd choice" both map 1–4 to the same four goals), put them in one loop.

Aggregate-name collisions: an aggregate must not collide with a category name. The closeness 4-pt scale has a category close (code 2), so the high aggregate (1|2) must be named feel_close, not close. Similarly check any not_* aggregate vs a not_* category before naming.

Aggregates and avg_score are NOT part of the check_sum_100 partition — only the mutually-exclusive categories + dont_know + no_answer sum to 100. Aggregates (worry_, feel_close_, agree_, at_least_weekly_…) are derived extras layered on top.

Stata name limits (this WILL bite you): local-macro / tempfile names are capped at 31 chars (variable names at 32). tempfile neighborhood_frequency_\var'_file(35) errors withr(198). Keep tempfile macro names short, e.g. nbhd_freq_`var'_file`.

Continuous indices (e.g. Y022 Welzel equality): keep the native scale; keep if Y022 < .; gen avg_score_<name> = Y022; collapse (mean) …; no DK/NA, no aggregate. Naming it avg_score_* makes the final replace \var' = `var'*100step skip it (that step multiplies everything **except**avg_score*`).

Each block's generated columns are {answer}_{CODE} for loops (meadow renames the CODE), or directly named for single-question custom blocks (e.g. secure_neighborhood, very_secure_neighborhood).

The user runs the edited .do in Stata to regenerate ivs.csv. You can sanity-check by reading the .dta in pandas and recomputing a couple of weighted means to diff against the produced CSV.

Step 2 — Re-snapshot (same version, no bump)

After the user regenerates ivs.csv (next to the .dta), first confirm the Stata run actually emitted your new columns before snapshotting — cheap insurance against a botched/partial .do run:

python
import pandas as pd
cols = set(pd.read_csv("snapshots/ivs/<v>/ivs.csv", nrows=0).columns)
# pre-rename raw names, i.e. {prefix}_{CODE} for loops + literal names for custom blocks
assert {"at_least_weekly_E248B", "agree_E217", "better_off_science_world", "concerned_humankind"} <= cols

Then re-snapshot:

bash
.venv/bin/etls ivs/<v>/integrated_values_surveys --path-to-file snapshots/ivs/<v>/ivs.csv

This overwrites the .dvc md5/size in place (commit that diff with a 📊🤖 message). Delete the local ivs.csv after.

Step 3 — Meadow: etl/steps/data/meadow/ivs/<v>/integrated_values_surveys.py

Add entries to VARS_DICT ("CODE": "Readable label") only for codes used as column suffixes — i.e. the loop groups. The rename works by column.endswith(code), then snake_cases. Custom single-question blocks already produce final names, so they get no VARS_DICT entry. Check that no new code is a suffix of another (e.g. H002_01 vs H002_1 — fine; just be deliberate).

Labels: avoid : and other punctuation. rename_vars snake_cases in two steps — first a crude .str.lower().str.replace(" ", "_"), then tb.format() applies the real underscore(). A label like "Information source: Daily newspaper" ends up as information_source__daily_newspaper (the colon becomes a second underscore). Use colon-free labels ("Information source daily newspaper") so the final suffix is clean single-underscore (information_source_daily_newspaper), and put the nicely punctuated wording in the .meta.yml title/description_short instead. Codes with trailing letters (e.g. E248B, G007_18_B) work fine with endswith.

Step 4 — Garden: etl/steps/data/garden/ivs/<v>/integrated_values_surveys.py

Per categorical group add:

  1. a suffix-list constant (the snake-cased item names),
  2. a replace_dont_know_by_null(...) call — answers = the individual response categories (no aggregate, no DK/NA),
  3. a check_sum_100(...) call — answers = individual categories + dont_know + no_answer.

check_sum_100 is the correctness gate: it fails loudly if a recode is wrong (categories must sum to 100%).

Derive the exact garden column suffixes (so the constants match meadow output) with the same transform meadow uses:

python
from owid.catalog.core.utils import underscore   # owid.catalog.utils path is deprecated
suffix = underscore("Information source daily newspaper")  # -> "information_source_daily_newspaper"

(apostrophes are dropped, hyphens → _; for colons see Step 3.)

avg_score and the replace(0, NaN) step. Only indices where 0 is a genuine value need to be excluded from the 0→null replacement (WELZEL_EQUALITY_INDEX_COLUMNS, the 0–1 Welzel index). An ordinary avg_score_* on a 1–N scale (frequency 1–5, closeness 1–4, agree 1–10, …) never equals 0, so the replacement is a no-op for it — do not add those to the exclusion set. A truly continuous block also gets no replace_dont_know/check_sum.

Step 5 — Metadata: etl/steps/data/garden/ivs/<v>/integrated_values_surveys.meta.yml

Every new column needs an entry: title, single-quoted description_short (quote the question stem + answer options, double internal apostrophes for YAML), and display.name + <<: *common-display. Source the wording from the questionnaire PDF; the code→category order from the .dta. Index/unit-less columns (the avg_score_*) override unit: "" / short_unit: "" and numDecimalPlaces: 2; share columns inherit unit: "%" from &common-display. Generate the entries programmatically (it's 100s of columns) and append to the variables: block. To re-run the generator after a wording fix, splice cleanly: find the first new key in the file, truncate from there, and re-append the regenerated block (don't blindly append twice).

Enumerate the possible answers in every description_short. Don't stop at naming the response(s) the indicator measures — append the full set of options the respondent could choose, so each indicator is self-describing. Follow the IVS pattern: for categorical / Likert scales end the sentence with Possible answers are "<a>", "<b>", … and "<z>". (list them in the source order from the .dta value labels); for an N-point numeric scale state the anchored range instead (e.g. on a scale from 1 ("never justifiable") to 10 ("always justifiable")). This applies to every column in the block — the aggregates (agree_agg_*, never_just_agg_*, …), each individual category, and the dont_know / no_answer / avg_score columns — they all carry the same possible-answer clause; only the measured-response part differs. Pull the exact option wording from the .dta value labels (verify per Step 0), never from memory — e.g. WVS D066_01's fifth point is literally Disagree strongly, and F114E labels only the 1 and 10 endpoints. This is a rule for new indicators you add — apply it as you write each entry.

Do not attempt an automated bulk back-fill of the possible-answers clause across the existing IVS indicators. Most already convey their answers (often inside the question quote, e.g. important-in-life ends "…very important, rather important, not very important or not at all important?"), so a blanket append is redundant; and mapping an existing shortName back to its source .dta code is trap-laden — suffixes collide (*_democratic_political_system is E117, not the 1–10 E236 *_democratic; *_secure_neighborhood is the H001 security scale, not the G007_18_B neighborhood-trust scale), and value-label wording can diverge from the pipeline's recode wording (E124 human-rights: labels say "There is a lot of respect…" while the columns use "a great deal of respect…").

Keep IVS and WVS at metadata parity. Any description_short house-style or convention you apply to one table's indicators, apply to the other's too (e.g. the possible-answers clause above), so the two blocks stay consistent, differing only in the survey-specific anchors (*_wvs) and the question wording.

Canonical shape of a variable entry (a WVS entry = the IVS entry plus the three *_wvs override lines; everything else is inherited from definitions.common, so don't re-specify it):

yaml
definitions:
  common:                                 # applied dataset-wide → inherited by BOTH tables
    presentation: {attribution_short: Integrated Values Surveys}   # IVS-worded
    processing_level: major
    description_key: [ …IVS-worded (merged WVS+EVS, IVS waves)… ]
    description_processing: | …IVS-worded…
    display: &common-display
      numDecimalPlaces: 1
      tolerance: 5
      entityAnnotationsMap: |-
        United Kingdom: England, Scotland, and Wales
    unit: "%"
    short_unit: "%"
  # WVS overrides (common is IVS-specific) — referenced per WVS variable:
  attribution_short_wvs: &attribution_short_wvs World Values Survey
  description_key_wvs: &description_key_wvs [ …WVS-worded… ]
  description_processing_wvs: &description_processing_wvs | …WVS-worded…

tables:
  integrated_values_surveys:              # IVS variable — inherits all of common
    variables:
      <short_name>:
        title: '<unique across BOTH tables>'
        description_short: '% of respondents … "<question stem>". Possible answers are "<a>", …, "<z>".'
        display:
          name: <short label>
          <<: *common-display
        presentation:
          topic_tags: *topic_tags_<topic>

  world_values_survey:                    # WVS variable — SAME shape + the 3 *_wvs overrides
    variables:
      <short_name>:
        title: '<unique across BOTH tables>'
        description_short: '% of respondents … "<question stem>". Possible answers are "<a>", …, "<z>".'
        description_key: *description_key_wvs                 # ← WVS-only override
        description_processing: *description_processing_wvs   # ← WVS-only override
        display:
          name: <short label>
          <<: *common-display
        presentation:
          attribution_short: *attribution_short_wvs           # ← WVS-only override
          topic_tags: *topic_tags_<topic>

      avg_score_<q>:                        # unit-less average column (either table)
        title: '…: average score'
        description_short: 'Average score … on a scale from 1 ("…") to 10 ("…").'
        display: {name: '…: average score', numDecimalPlaces: 2, <<: *common-display}
        unit: ""
        short_unit: ""
        presentation: {topic_tags: *topic_tags_<topic>}       # + attribution_short: *attribution_short_wvs on WVS

Battery questions — separate the prompt from the item. Many WVS/EVS questions read a generic prompt then list items (e.g. "…with the following statements? - Work is a duty…"). In description_short never leave a raw " - " separator and never let the item dangle straight after the "?" (e.g. …statements? Work is a duty…) — both read as two sentences mashed together. End the prompt's quote and re-open a quote for the item, picking the connector by what the item is:

CasePatternExamples
plural "…the following statements?" (or stem ending on a scale phrase)…?", when the statement was "<statement>"gender-roles, work, justifiable
singular "…with the following statement?"no connector — drop the dash, item follows directly: …statement? <statement>jobs-scarce (C001/C002)
non-statement items (situations, actions, "things")…?", regarding "<item>" — or a fitting noun where one reads cleanlyneighborhood frequency, security actions; worries already uses …situations?", when the situation was "…"

Rationale: the deciding factor for statements is singular vs plural — a singular "the following statement?" already names its one statement so a connector is redundant (just drop the dash), while a plural battery needs the connector to say which item. The connector noun must fit the item: "when the thing was" reads badly, so use "regarding" (or a clean noun like "situation"/"characteristic"). Always use the comma form ", when the … was ", matching the pre-existing justifiable battery.

YAML quoting in a Python generator: keep RAW apostrophes in your strings and run them through one single-quote helper that doubles '→''. Do not hand-write 'Don''t know' in Python source — that's adjacent-string-literal concatenation and silently yields Dont know. After appending, re-parse the file with ruamel_load and assert the count + that a new avg_score_* entry shows unit == "" and the &common-display anchor resolved (e.g. numDecimalPlaces).

title must be UNIQUE across the whole dataset — and the dataset holds BOTH tables. Because integrated_values_surveys and world_values_survey share one grapher dataset, a WVS title must not clash with any IVS title (or vice-versa). WVS shares many concepts with IVS (worries, confidence, justifiable, …), so when you add a WVS question that duplicates an IVS concept, suffix the WVS title (e.g. … (World Values Survey)) to keep it unique; display.name may still repeat. Grapher enforces this at the --grapher upload (_adapt_table_for_grapher), not at the garden build, so a clash sails through check_sum_100 and only explodes at upload (AssertionError: Variable titles are not unique). The trap: an aggregate and a category that describe the same thing collide on title even when their column names differ. For example, the closeness category close_* and the high aggregate feel_close_* both wanted title: "Feel close to X". Fix: disambiguate the aggregate title ("Feel close to X (very close or close)") and keep the short label in display.name. Assert title uniqueness in the pre-flight (Step 6), don't wait for the upload to catch it: titles=[e["title"] for e in vars.values()]; assert len(titles)==len(set(titles)).

Audit gotcha: verify any stated scale range against the actual .do recode — an existing entry can say "6 to 10" where the recode is >= 7. Don't copy ranges blindly.

Show full SKILL.md (1,748 more words)Show less
Topic tags — assign per indicator, from the curated enum

presentation.topic_tags is not free text. Valid values are the enum in schemas/dataset-schema.json (the ~128 OWID topic pages — e.g. Women's Rights, Human Rights, Migration, Democracy, Religion, Internet, Technological Change, Loneliness & Social Connections, War & Peace, Terrorism, Corruption, Marriages & Divorces, LGBT+ Rights, Happiness & Life Satisfaction, Economic Inequality, Economic Growth, Climate Change, Time Use, Working Hours). Tags that exist in the grapher tags table but have no topic page (Values, Crime, News, Communication Technology) are invalid — they're silently dropped at upload with a create_links.missing_tags warning. Check every tag against the enum before writing it (see the Step 6 assertion).

IVS spans many topics, so do not leave the whole dataset on one tag (e.g. a single topic_tags: [Trust] in definitions.common). Tag per indicator:

  • Anchor per distinct tag-set under definitions (the undp_hdr.meta.yml idiom), referenced per variable — generate it programmatically by mapping each garden question-group → tag-set:
    yaml
    definitions:
      topic_tags_women_rights: &topic_tags_women_rights
        - Women's Rights
    # …
        gender_roles_var:
          presentation:
            topic_tags: *topic_tags_women_rights
    A per-question override dict handles splits within a group: justifiable divorce→Marriages & Divorces, homosexuality→LGBT+ Rights, cheating_on_taxes/accepting_a_bribe→Corruption; worries war/civil_war→War & Peace, terrorist_attack→Terrorism, losing_job→Work & Employment; media internet/email/mobile_phone→Internet; jobs-scarce men_more_right_to_a_job→Women's Rights, priority_to_nationals→Migration.
  • Multiple tags are allowed — e.g. environment-vs-economy → [Climate Change, Economic Growth].
  • No matching topic page → OMIT topic_tags entirely. For the soft-attitude / crime-fear batteries (Schwartz, important-in-life, neighbors, most-serious-problem, crime fear, traditional media) there is no topic page. Do not write Uncategorized — it's a valid enum value but has no DB tag row, so it links to nothing and logs a missing_tags warning per variable. Omitting gives the same result with no noise. Drop the now-empty presentation block when topic_tags was its only key.
  • attribution_short survives. Setting only topic_tags on a variable does not wipe a common presentation.attribution_short — the combiner merges presentation key-by-key (_merge_variable_metadata merge_fields), so the common value still inherits.
  • Sanity-check choices against what existing IVS charts already use (chart tags can include non-topic-page tags, so still filter your final pick through the enum):
    sql
    SELECT t.name, COUNT(DISTINCT c.id) FROM charts c
    JOIN chart_dimensions cd ON cd.chartId=c.id JOIN variables v ON cd.variableId=v.id
    JOIN chart_tags ct ON ct.chartId=c.id JOIN tags t ON ct.tagId=t.id
    WHERE v.catalogPath LIKE '%ivs/<v>/%' GROUP BY t.name ORDER BY 2 DESC;

Sweep the untagged pool against the FULL enum — many items have a non-obvious nearest-fit page. Don't stop at the obvious group→tag map. Print the whole topic_tags enum and walk each remaining untagged question against it; surprisingly specific pages exist. Examples of matches found this way: justifiable suicide→Suicides, political_violence→War & Peace, parents_beating_children→Violence Against Children & Children's Rights, man_beating_wife→Women's Rights, invitro_fertilization→Fertility Rate; neighbours immigrant_foreign_workers→Migration, aids→HIV/AIDS, drug_addicts/drug_sale_in_streets→Illicit Drug Use, heavy_drinkers→Alcohol Consumption, racist_behavior→Human Rights; important-in-life work→Work & Employment, politics→Democracy, leisure_time→Time Use, friends/family→Loneliness & Social Connections; worry not_being_able_to_provide_good_education→Global Education. Present the candidate fits (clear vs. judgment-call) to the user/topic owner and let them confirm — they often know which existing charts already carry a tag (e.g. "our political-violence charts use War & Peace").

Add topic tags as SECONDARY tags to already-tagged items that also touch that topic (multi-tag, primary first). E.g. items mentioning religion that are primarily something else get Religion added alongside: religious_authorities_interpret_the_laws→[Democracy, Religion], confidence_churches / confidence_organization_of_the_islamic_world / trust_another_religion→[Trust, Religion], E220 science-vs-faith→[Technological Change, Religion]. Each distinct combo is its own anchor (topic_tags_trust_religion, …); reuse the existing single-tag anchor object when a variable's tag-set matches it.

Judge by what the indicator measures, not a keyword. Literal keyword matching over-reaches: family_victim_ of_a_crime mentions "family" but is a crime-victimisation question, not a family/social-connections one — don't tag it Loneliness & Social Connections. Same for "faith" inside a science item, etc. Flag the ambiguous ones to the user instead of auto-tagging.

Re-routing a tag → delete the orphan anchor. If you move a variable from one tag to another (invitro_fertilization: Medicine & Biotechnology → Fertility Rate), and the old anchor is now referenced by nothing, remove it from definitions so no dead anchors linger.

Tag edits are metadata-only — the data://grapher dataset must be rebuilt or the DB won't update (see Step 7's gotcha).

Step 6 — Pre-flight cross-check (before running the pipeline)

Assert the set of new column names is identical across three places, or you get silent breakage: (a) what Stata+meadow produce, (b) the garden check_sum_100/replace_dont_know groups, (c) the .meta.yml keys. Reconstruct (a) directly from the .do collapse lists — every column the loop emits is {prefix}_{underscore(VARS_DICT_label)} for loop groups, or the literal name for custom blocks — then diff against the meta keys:

python
from owid.catalog.core.utils import underscore
from etl.files import ruamel_load

# the exact (prefix list) per group, mirroring each .do collapse line (incl. aggregates + avg_score)
GROUPS = {("E248B", "E254B", ...): ["at_least_weekly", "less_than_weekly", "daily", ..., "avg_score"], ...}
LABELS = {"E248B": "Information source daily newspaper", ...}  # == meadow VARS_DICT
stata = {f"{p}_{underscore(LABELS[c])}" for codes, prefs in GROUPS.items() for c in codes for p in prefs}
stata |= {f"{p}_humankind" for p in [...]}            # custom blocks: literal names, no rename

meta = ruamel_load(open("etl/steps/data/garden/ivs/<v>/integrated_values_surveys.meta.yml").read())
# Pick the table you're editing: "integrated_values_surveys" (IVS) or "world_values_survey" (WVS).
TABLE = "integrated_values_surveys"
metakeys = set(meta["tables"][TABLE]["variables"])
print("stata not meta:", sorted(stata - metakeys) or "none ✓")
print("meta not stata:", sorted(metakeys - stata) or "none ✓")   # restrict metakeys to the new ones
# also: assert no new code endswith another new code (rename ambiguity)
# and assert ALL titles are unique across BOTH tables (one shared grapher dataset — see Step 5):
titles = [v["title"] for t in meta["tables"].values() for v in t["variables"].values()]
assert len(titles) == len(set(titles)), "duplicate variable titles — grapher upload will fail"

# and: every topic_tag must be in the curated schema enum (else it's silently dropped at upload)
import json
schema = json.load(open("schemas/dataset-schema.json"))
TAG_ENUM = set(schema["properties"]["tables"]["additionalProperties"]["properties"]["variables"]
               ["additionalProperties"]["properties"]["presentation"]["properties"]["topic_tags"]["items"]["enum"])
for v in meta["tables"]["integrated_values_surveys"]["variables"].values():
    for t in (v.get("presentation") or {}).get("topic_tags", []):
        assert t in TAG_ENUM, f"invalid topic_tag {t!r} — not a topic page in dataset-schema.json"

A clean both-empty diff (counts equal) + unique titles means the Stata→meadow output, the garden groups, and the meta keys agree, and the grapher upload won't trip the title-uniqueness assert — all before a single pipeline step runs.

Step 7 — Run & verify

bash
.venv/bin/etlr integrated_values_surveys  # meadow→garden→grapher
.venv/bin/etlr integrated_values_surveys --grapher  # upload to staging grapher
  • Two separate gates: the build (first command) runs check_sum_100 in garden (green = recodes sum to 100%). The --grapher upload (second command) runs additional grapher-only validations — notably title uniqueness (see Step 5) — so always run it too. Don't stop at the build.
  • Spot-check ranges by loading the garden dataset: share columns 0–100; each avg_score on its native scale (frequency 1–5, closeness 1–4, agree 1–10, index 0–1).
  • The benign DisplayNameWarning about presentation.title_public (1000+ columns) is expected — IVS doesn't set title_public; not an error.
  • Metadata-only changes (e.g. topic_tags) need the data://grapher dataset rebuilt — grapher://… --only is not enough. etlr grapher://… --grapher --force --only forces only the upload step, and --only skips its data://grapher build dependency, so it re-uploads the stale grapher dataset (every variable skipped_no_changes) and the new tags never reach the DB. Rebuild the built dataset itself — etlr data://grapher/ivs/<v>/integrated_values_surveys --force --only (delete data/grapher/ivs/<v>/integrated_values_surveys/ first if in doubt) — then run the --grapher upload. Confirm the links actually landed (and that omitted/Uncategorized columns produced no create_links.missing_tags warnings):
    python
    from etl.config import OWID_ENV
    print(OWID_ENV.read_sql("""SELECT t.name, COUNT(DISTINCT v.id) n FROM variables v
      JOIN tags_variables_topic_tags tv ON tv.variableId=v.id JOIN tags t ON t.id=tv.tagId
      WHERE v.catalogPath LIKE '%%ivs/<v>/%%' GROUP BY t.name ORDER BY n DESC""").to_string())
  • make check before committing.

Step 8 — Git (per repo CLAUDE.md)

bash
.venv/bin/etl pr "Add … indicators to Integrated Values Surveys" data   # no emoji in title with a category
git add … && git commit -m "✨🤖 …"   # 📊🤖 for the snapshot/.dvc regen
git push

Post @codex review as a separate PR comment; keep the PR body in sync with substantial pushes.

Step 9 — Notion Available dictionary (required — IVS only)

WVS target: skip this step entirely — there is no Notion Available tracker for world_values_survey. For IVS this is not optional — always finish the job by flipping the flags so the dictionary reflects what's actually in the dataset.

The Integrated Values Surveys dictionary Notion DB (collection://fd3a1354-9da6-48e7-91bb-a0665ddd0f0b) has an Available checkbox per code. Flip Available = ✅ only for codes that survive into the garden dataset = the .do keep list minus items excluded in drop_indicators_and_replace_nans. The reliable way to get that set is to read the garden table columns and reverse-map to base codes — a .do-only list over-counts (e.g. ds E069* keeps E069_64/"Elections" which produces no column, and ~8 confidence items are dropped in garden). Beware substring false-positives when reverse-matching ("elections" matches ..._free_elections); verify the exact column exists before flipping. The Notion MCP has no bulk row query — fetch/update per row (notion-search returns titles only; notion-fetch returns Available; notion-update-page with command="update_properties", properties={"Available":"__YES__"}). The row's Answers field is a handy independent cross-check of your recode mapping.

Each row's title is exactly the code (the Variable title property). Find a row by searching the data source for the code (notion-search with data_source_url=collection://…), but match the title EXACTLY — search is fuzzy and returns look-alikes (E248B query also returns E248; G062 returns F062/D062; E001 returns Y001/F001). Updates are independent per page, so fire all of them in parallel (one notion-update-page per code). Verify with one notion-fetch afterward that Available reads "__YES__".

After the upload, produce a compact one-row-per-question table that links each response option to its Grapher admin page — handy for a PR comment or a hand-off to a topic reviewer. A generic script lives at scripts/indicator_admin_table.py (next to this skill) — it hardcodes no indicators. Edit only the small CONFIG block (VERSION, BRANCH, ENV) and run it:

bash
.venv/bin/python .claude/skills/add-ivs-indicators/scripts/indicator_admin_table.py

It works in three moves, all auto-derived:

  1. Which variables did the PR create? Diff the garden .meta.yml variable keys between master and the PR branch (one entry per variable): meta_keys(BRANCH) - meta_keys("master"). Robust even when the working tree is on master (it uses git show <ref>:<file>).
  2. Resolve variable ids from the Grapher DB and cluster columns into questions. Loop-group columns → IVS code via the branch meadow VARS_DICT (it reverses {underscore(label): code} and matches the longest column suffix, so family_victim_of_a_crime beats victim_of_a_crime). Custom single-question blocks (no VARS_DICT entry, so the column carries no code) are clustered by stripping a generic IVS recode-prefix vocabulary and keyed by their column suffix; their label is derived from the variable names.
  3. Label and order each option link. Link text is the option's real category name (strip the per-question shared prefix / a parenthetical suffix from the variable name; fixed text for dk/na/avg; humanised recode key as last resort) — so e.g. E124 shows "Fairly much respect", not the terse key some. Order within a question: aggregates first (positive rollup before negative), Yes before No, then the remaining categories, then Don't know / No answer / average score.
  4. Emit | IVS code | Question | option links | rows — one flat table by default.

Three optional dicts in CONFIG add editorial polish without reintroducing hardcoded indicators: TOPICS = {"Human Rights": ["E124", ...], ...} (group/order rows), LABELS = {code_or_suffix: "…"} (nicer question wording), and CODE_FOR = {custom_suffix: "IVS_CODE"} (give custom blocks like humankind→E158, science_world→E234, secure_neighborhood→H001 their real code). With them empty you still get a complete, correct table.

Staging vs production differ only in the DB connection + admin base (the script switches on ENV):

  • Staging (ENV="staging"): container = staging-site-<branch normalised & truncated to 28 chars> (via get_container_name); admin http://<container>/admin/variables/<id>. On master, OWIDEnv.read_sql falls back to local, so the script connects to <container>.tail6e23.ts.net MySQL directly. The datasets.catalogPath has no grapher/ prefix (e.g. ivs/<v>/integrated_values_surveys) even though variables.catalogPath does — filter on the dataset row accordingly.
  • Production (ENV="production"): admin base is https://admin.owid.io/admin. Reach the prod grapher DB directly over Tailscale at host prod-db, port 3306 (check tailscale status for it) with the live_grapher creds from .env/.env.live — this is far more reliable than .env's 127.0.0.1:3310 local SSH tunnel, which is usually down (Connection refused). Build the engine with sqlalchemy.engine.URL.create(...) so the special-char password is encoded correctly. The indicators exist in production only after the PR is merged and the dataset is deployed, and production variable ids differ from staging — always re-query prod (don't reuse staging ids). After merge, the branch-vs-master diff is empty, so set NEW_COLS_FILE (e.g. the saved ai/ivs_pr_new_cols.txt) or BASE_REF to the merge base. The len(v) == len(NEW) assert doubles as an "is it deployed yet?" check.

Worked example

PR owid/etl#6180 is a full reference diff — it spans nearly every shape above (3/4/5-pt agree, 4-pt frequency & closeness, binary, 5-pt frequency, 10-pt agree + better/worse, multinomial 1-of-N, the Y022 0–1 index, and an EVS-fallback single-question block).

© 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 1 other file (scripts) in .claude/skills/add-ivs-indicators of owid/etl.

  • SKILL.md
  • scripts/indicator_admin_table.py

Open the folder on GitHubat commit 70c9705

Compare with similar skills

Add Ivs Indicators 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.

Add Ivs Indicators compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Add Ivs Indicators this skillowid/etl159—~10kAutomated safety check: NotesMIT
Pathmldavila7/claude-code-templates33k11 repos~1.9kAutomated safety check: PassMIT
Literature Statisticsaipoch/medical-research-skills1.9k—~1.9kAutomated safety check: PassMIT
Ppi Network Analysisaipoch/medical-research-skills1.9k—~2.2kAutomated safety check: PassMIT
Meta Forest Binary Plotaipoch/medical-research-skills1.9k—~2.1kAutomated safety check: PassMIT
Meta Forest Continuous Plotaipoch/medical-research-skills1.9k—~1.8kAutomated safety check: PassMIT

Similar skills

  • Pathml

    davila7/claude-code-templates

    Computational pathology toolkit for analyzing whole-slide images (WSI) and multiparametric imaging data.

    33k GitHub starsUsed in 11 repos~1.9k tokens
    Documents & OfficeAuto-check passed
  • Literature Statistics

    aipoch/medical-research-skills

    Generate statistics for publication-year and journal distributions from local references or PDFs; use when you need standardized Year/Journal tables and a summary without any network access.

    1.9k GitHub stars~1.9k tokensUpdated 23 days ago
    Documents & OfficeAuto-check passed
  • Ppi Network Analysis

    aipoch/medical-research-skills

    A skill your agent uses when you need a standardized R CLI workflow to build a protein-protein interaction network from a local gene list and an offline STRING cache, export node and edge tables…

    1.9k GitHub stars~2.2k tokensUpdated 23 days ago
    Documents & OfficeAuto-check passed
  • Meta Forest Binary Plot

    aipoch/medical-research-skills

    Generate meta-analysis forest plots for binary classification data.

    1.9k GitHub stars~2.1k tokensUpdated 23 days ago
    Documents & OfficeAuto-check passed
  • Meta Forest Continuous Plot

    aipoch/medical-research-skills

    Generate forest plots for meta-analysis of continuous data. An agent skill from aipoch/medical-research-skills.

    1.9k GitHub stars~1.8k tokensUpdated 23 days ago
    Documents & OfficeAuto-check passed
  • Research Writing

    alfonso0512/research-writing-skill

    科研论文写作助手,提供 30 个 Prompt 模板覆盖论文写作全流程. An agent skill from alfonso0512/research-writing-skill.

    490 GitHub starsUsed in 1 repo~818 tokens
    Documents & OfficeAuto-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.8k 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~18k tokensUpdated today
    Auto-check passed
  • 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~7.8k tokensUpdated today
    Auto-check passed
  • Propose redirects from (soon-to-sunset) grapher charts to the matching views of published MDIMs.

    159 GitHub stars~9.2k tokensUpdated today
    Auto-check: notes
  • Take (soon-to-sunset) OWID explorers to redirected MDIMs, end to end.

    159 GitHub stars~7.2k 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 Add Ivs Indicators

What does Add Ivs Indicators do?

Add new survey question codes (e.g. An agent skill from owid/etl. Add Ivs Indicators is an agent skill from owid/etl.g.

When should I use Add Ivs Indicators?

Add Ivs Indicators fits situations like: the user wants to add IVS/WVS/EVS questions; extend integratedvaluessurveys; worldvaluessurvey; says add these codes to IVS / add these WVS questions.

How do I install Add Ivs Indicators in Claude Code?

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

How do I install Add Ivs Indicators in Codex?

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

Can I use Add Ivs Indicators 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 add-ivs-indicators -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/add-ivs-indicators, .gemini/skills/add-ivs-indicators, .github/skills/add-ivs-indicators and .opencode/skills/add-ivs-indicators in your project.

What does Add Ivs Indicators need to run?

Going by SKILL.md and its folder, Add Ivs Indicators needs Python for the scripts in its folder and the command-line tools its instructions call (git, python and make). Our summary lists: Python 3.

Does Add Ivs Indicators access the network?

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

Is Add Ivs Indicators 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 Add Ivs Indicators use?

Add Ivs Indicators 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 Add Ivs Indicators use?

About 10k tokens (SKILL.md is roughly 41k 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 Add Ivs Indicators?

Skills that share tags, products or a category with Add Ivs Indicators: Pathml (davila7/claude-code-templates, 33k stars), Literature Statistics (aipoch/medical-research-skills, 1.9k stars), Ppi Network Analysis (aipoch/medical-research-skills, 1.9k stars) and Meta Forest Binary Plot (aipoch/medical-research-skills, 1.9k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Add Ivs Indicators?

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 10, 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.