faostat-export-dataset
Use when the user wants a clean, documented tabular export of FAOSTAT data — "export FAOSTAT", "download the data", "give me the CSV / xlsx of [indicator]", "I want the raw numbers", "tabular data pull", "export to spreadsheet". The deliverable is a bundle — one multi-sheet .xlsx (README / Data_tidy / Data_wide / Methodology / Sources) + one tidy-long .csv mirror + a data-dictionary .md. No prose, no charts, no narrative — just the data with full provenance. Do NOT use for reports, briefs, papers, infographics, or visualisations — those skills embed their own xlsx appendices. Use this skill when the data itself IS the deliverable.
FAOSTAT Data Export
Ship clean, documented FAOSTAT data. The user wants the numbers, not the story. Deliver a three-file bundle: one multi-sheet .xlsx, one tidy-long .csv, and a data-dictionary .md. Every row traces back to the FAOSTAT API call that produced it.
Prerequisites
FAOSTAT MCP tools: faostat_get_data, faostat_search_codes, faostat_list_domains, faostat_get_metadata. If any are missing, stop and tell the user the skill requires the FAOSTAT MCP server.
Python packages: openpyxl, pandas. Install with --break-system-packages in the sandbox.
Invariants
Cross-skill invariants (all six — violations are skill bugs):
-
FILTER vs DISPLAY codes.
faostat_get_datatakes FILTER codes (e.g.,2510Production).faostat_get_rankingstakes DISPLAY codes (e.g.,5510). Never invert. -
Year syntax. Comma-separated lists only (
'2010,2011,...,2023'). Colon ranges return empty in practice. -
Element filter required on every
faostat_get_datacall. Unfiltered pulls are massive, especially in emissions domains. -
TCL for national trade aggregates, TM only for partner breakdowns. Never sum TM rows to reconstruct national totals.
-
China composite default (Apr 2026 user preference). Default: composite
China(area 351).China, mainland(41) is an opt-in; full disaggregation (41 + 96 + 128 + 214) is also opt-in. Record the choice in the README sheet with the FAOSTAT-default-41 caveat. -
faostat_get_rankingsHTTP-500 fallback. On failure, reconstruct by pullingfaostat_get_dataacross all reporting countries and sorting client-side. Note the fallback in Methodology. -
Element and item code resolution. Never use a hardcoded numeric element or item code as the primary value in a
faostat_get_datacall. Always resolve at runtime:faostat_search_codes(domain_code='<dom>', dimension_id='element', query='<metric name>')for elements;faostat_search_codes(domain_code='<dom>', dimension_id='item', query='<item name>')for items. Numeric codes shown in reference tables and code examples are verified hints — use them to validate the search result, not as the authoritative source. Domain letter-codes (QCL, TCL, GT, EM, FBS, FS…) are stable and may be used directly.
Export-specific invariants:
- Every value traces to an API call. The Methodology sheet logs one row per
faostat_get_datacall with domain / area / element / item / year list / timestamp / rows-returned. No value appears in the export that did not come from a documented call. - FAO-native units kept as-is. No auto-conversion. Values stay in FAOSTAT's native units (kt CO₂eq, tonnes, USD 1000, etc.). Unit is a column in tidy-long and a header row in wide. If the user explicitly asks for normalised units, add normalised columns alongside the native ones — never in place of.
- No FAO branding. No FAO logo, "Food and Agriculture Organization of the United Nations" masthead, ISSN, "FAO Statistics Division" stamp, or "Required citation: FAO. …" line. CC-BY-4.0 attribution to FAOSTAT (source, licence, access date) IS kept — that's a property of the source data.
- Both shapes in the xlsx.
Data_tidy(long) andData_wide(years-as-columns pivot) are both always present. The CSV mirror is tidy-long only. A wide CSV is produced only if the user asks.
Workflow
Step 1 — Gather parameters
Required from the user (prompt via AskUserQuestion in Cowork, inline otherwise):
- Domain — e.g., GT, QCL, TCL, ET, PP, FBS, FS.
- Element(s) — FILTER code(s). If the user supplies a name rather than a code, resolve via
faostat_search_codes(invariant 7) before use. - Year range — list of years (will be passed comma-separated).
- Area scope — World, specific regions, specific country list, or "all reporting countries". Default if unspecified: all reporting countries plus regional aggregates (5000/5100/5200/5300/5400/5500).
Optional:
- Item(s) — FILTER code(s). If omitted, all items in domain for the chosen element.
- Flag filter — default keep all; optional strip to official-value-only.
- China handling — default
composite_351. Options:mainland_41ordisaggregated(41,96,128,214). - Topic title — propose one from domain + element + item + year range if the user does not supply.
Step 2 — Resolve codes
Always resolve element and item codes at runtime before the data pull (invariant 7). Call faostat_search_codes(domain_code=…, dimension_id='element'|'item'|'area', query=…) for every numeric code needed — even when the user supplies a code directly, verify it matches. Print the resolved codes back in a short confirmation block before the heavy pull so mismatches surface early.
Step 3 — Pull data
Call faostat_get_data with:
domain_codearea= comma-separated list (applying the China rule:351composite /41mainland /41,96,128,214disaggregated)element= comma-separated FILTER codesitem= comma-separated FILTER codes (or omit for "all items in domain")year= comma-separated year list (invariant 2)
Log one Methodology row per API call, as the call is made — do not retrofit. If multiple element codes need different item sets, issue separate calls and concatenate. Never conflate element-item pairings across calls.
Step 4 — Clean the dataframe
In order:
- Coerce
Valueto numeric. Drop rows whereValueis null. - Rename columns to the canonical schema:
area_code, area, item_code, item, element_code, element, year, unit, value, flag, note(omititem_code/itemif the domain has no items, e.g. ET temperature anomalies). - Keep both code and label columns — downstream analysts need them.
- Sort by
area, item, element, year.
Step 5 — Build the tidy-long CSV
Schema: area_code, area, item_code, item, element_code, element, year, unit, value, flag, note.
Header comment lines at the top of the CSV (prefixed with #):
# Source: FAOSTAT (https://www.fao.org/faostat/), accessed YYYY-MM-DD. Licence: CC-BY-4.0.
# Domain: <CODE> (<label>). Elements: <codes>. Items: <codes or "all">. Years: <list>.
# China handling: <composite_351 / mainland_41 / disaggregated>.
# Rows: <N>. Generated by faostat-export-dataset skill.
Save as <topic_slug>_<year_start>-<year_end>_tidy.csv.
Step 6 — Build the wide pivot
Rows: (area_code, area, item_code, item, element_code, element, unit). Columns: years. Values: value.
Keep area_code and item_code — do not drop identifiers. If the pivot contains multiple elements with different units, keep one row per element; unit shown in its own column.
Step 7 — Assemble the xlsx
Sheets in this order:
- README — plain-prose block:
- Title (topic + year range)
- Source line (FAOSTAT, licence, access date)
- Column dictionary (one line per column — mirrors the
.mdfile) - China-handling choice with the FAOSTAT-default-41 caveat
- Flag codes key (from
faostat_get_metadata) - File manifest (tidy.csv / xlsx / data-dictionary.md)
- Data_tidy — identical schema to the tidy CSV (no
#comment header inside xlsx). - Data_wide — pivoted with years as columns.
- Methodology — one row per API call:
call_id, timestamp_utc, domain, area_param, element_param, item_param, year_param, rows_returned, notes. Year param stored as the literal comma-separated string that was passed. - Sources — one row per domain used:
domain_code, domain_label, citation, url, licence, access_date. Pulled fromfaostat_get_metadata.
Step 8 — Build data-dictionary.md
Plain markdown file:
- Dataset description — topic, year range, scope, row count.
- Column schema table — column name | type | description | example | nullable.
- Unit reference table — unit string | full name | note (no conversion applied; purely descriptive).
- Flag codes — flag | meaning.
- Known caveats — China handling, any FAOSTAT revisions relevant to the pulled years, any items that changed codes over the series.
- Citation block — FAOSTAT + licence + access date + full URL of each domain used.
Step 9 — Save and present
Save all three files to the outputs folder with a shared slug:
<slug>.xlsx<slug>_tidy.csv<slug>_data-dictionary.md
Confirm to the user with links to all three files and a one-line summary: "topic, year range, N rows, K sheets in xlsx, China handling = choice." Nothing more — this is a data-delivery skill, not a narrative skill.
Slug convention
{domain_lower}_{primary_element_label}_{year_start}-{year_end}
Examples:
gt_emissions-total_2001-2023qcl_production_cattle-poultry-pig_2010-2024tcl_export-value_cereals_2015-2024pp_producer-price_coffee-cocoa_2020-2024
Lowercase, hyphen-separated labels, underscore between segments.
Error handling
- Empty
faostat_get_dataresult. Re-check comma-separated year syntax (invariant 2) and the element/item resolution. If the problem persists, widen the item filter to "all items in domain" to confirm the domain + area + year combo returns anything at all. Log the failure in Methodology withrows_returned = 0, notes = "…". - Rankings HTTP 500. Use the
get_datafallback (invariant 6). Record in Methodology. - Mixed units in one element. Shouldn't happen for a single element code; if it does, split into separate
Data_wideblocks per unit. - User asks for a narrative or a chart. Decline politely and route to
faostat-analytical-brief/faostat-infographic/faostat-story/faostat-scientific-paper/faostat-viz. This skill is data-only. - User asks to normalise units. Explain the invariant (keep FAO-native, auditable). If they insist, add normalised columns alongside — never replace the native ones.
Defaults summary
| knob | default |
|---|---|
| bundle | xlsx + tidy csv + data-dictionary md |
| shapes | tidy-long + wide pivot (both) |
| China | composite 351 |
| units | FAO-native (no conversion) |
| flag filter | keep all flags |
| year range | user must supply — no default |
| item filter | if omitted, all items in domain |
| area scope | if omitted, all reporting countries + regional aggregates |
All defaults can be overridden by the user; every override is documented in the README sheet and the data-dictionary.md.