Plates of official statistics, one country at a time.
Five atlases — Argentina, Brazil, France, South Korea, the United States — built only from the numbers each country's own institutions publish. 52 plates, 13 country-language editions, 158 committed datasets, 2006 to 2026.
Live at worldreports.io.
Every chart is regenerated from CSVs committed in this repository. Nothing reads an API at draw time, so a figure on a page and the file behind it cannot drift apart, and any plate can be reproduced from the commit it was built at.
countries/<slug>/ one country, and nothing of any other
country.yaml its name, languages, palette, years, currency
data/ the committed CSVs every plate is drawn from
SOURCES.yaml who publishes each file and how it is refreshed
provenance.json which CSVs each plate was watched reading
plates.py the drawing code
chapters.py the cards: titles, blurbs, headline figures
i18n.json the plate strings, translated
ui.json the masthead line, the dek, the reading notes
events.py its recessions, shocks and political calendar
fetch/ the refreshers (the only code that uses the network)
output/ the rendered plates, committed
tests/ what can only be asked of this country
core/ everything the five share, and no country's name
country.py the Country object — and the isolation boundary
style.py the figure: palette, header, footer, fonts
plotting.py the one CSV reader, and the drawing primitives
i18n.py the translation engine
context.py what a plate is handed
render.py draw one country, or all of them
thumbs.py the card-sized copies
provenance.py watch a plate draw and record what it read
sitebuild.py the cards' numbers, formatted per language
page.py the static pages
qa.py the layout checker
site/ the page chrome: one stylesheet, one script, one catalog
tests/ everything that must hold for every country
pip install -r requirements.txt
make # render, thumbnail, record provenance, build, test
make serve # build _site and serve it on :8000
make C=france charts # just one country's plates
make test # everything, including the slow render suitesKorean plates need a CJK font (fonts-noto-cjk on Debian and Ubuntu). The
build refuses to draw a language it has no font for rather than publishing
a page of empty boxes.
No code that draws one country's plate may read another country's data.
Five atlases that used to be five repositories now share a checkout, and the failure that matters is not a crash. It is a French number on a Brazilian plate, which nobody notices, because both are plausible.
So the rule is mechanical, not a convention:
- every read goes through
core.country.within, which resolves the path and refuses anything outsidecountries/<slug>/— a..segment, an absolute path and a symlink pointing out are all caught by the same test; tests/test_isolation.pydraws every plate and builds every card with the filesystem watched, and fails on any read outside the country's own directory. It is verified to catch a deliberate breach;core.provenancerecords what each plate actually read, andtests/test_sources.pychecks every recorded file is one the country's own manifest declares;tests/test_core.pyreads the shared code and fails if it names a country at all — in code; a comment may explain one.
Adding a country is therefore a directory, not an edit to anything shared. ADDING_A_COUNTRY.md is the mechanics; SOURCING_A_COUNTRY.md is the research that comes first — which eleven questions an atlas answers, which institution answers each of them, and the traps that have already produced a wrong published chart here.
Each country's SOURCES.yaml names every CSV, who publishes it, and how
it is refreshed; each page prints that table with the years read off the
files themselves rather than off the manifest. A dataset is official
unless the manifest says otherwise, and the one series here that is not —
a French industry survey of household credit — says so in the file and on
the page.
Fetchers never overwrite a curated number. They write what they pulled to
a sidecar beside it — <name>_check.csv or <name>_from_<source>.csv —
so a bad upstream day shows up as a diff a person reads, not as a plate
that quietly changed. No plate is ever drawn from a sidecar, and a test
enforces that.
Source difficulty had two distinct forms. China was the hardest access problem: the NBS machine interface returned HTTP 403, Customs returned 412, and several central-bank and fiscal series endpoints could not be isolated. Japan and Taiwan were the hardest normalization problems because available-looking figures often measured different things. Japan mixes April–March fiscal years with calendar-year series, migration entries with immigrant persons, and citizenship with country of birth. Taiwan requires explicit conversion from ROC years, distinguishes cash from accrual accounts and central-government measures from general-government measures, and is covered inconsistently by international datasets.
The practical lesson is to keep access failure separate from comparability failure. A statistic may not exist, may exist but resist reliable fetching, or may have a nearby proxy with a different definition. Those cases remain separate here rather than being hidden behind a plausible substitute.
- The running year is not a full year. Annual totals stop at the last complete year; a reading that is genuinely current — a debt stock, a policy rate, a closing price — is marked as the latest point instead.
- Broken series are left broken. Where two sources measure the same idea differently, both are drawn and the difference is named. They are never spliced into one line.
- A figure is a picture of a table. Every plate on every page carries the committed files it was drawn from, and the American plates carry the table itself, so a reader who cannot see the chart still has the numbers.
Code is MIT. The data belongs to the institutions that published it; each
file's source is named in its country's SOURCES.yaml.