📊 Refresh the long-run history of child mortality static viz - #6701
Draft
paarriagadap wants to merge 8 commits into
Draft
📊 Refresh the long-run history of child mortality static viz#6701paarriagadap wants to merge 8 commits into
paarriagadap wants to merge 8 commits into
Conversation
Contributor
|
Quick links (staging server):
Login: chart-diff: ✅No charts for review.data-diff: ❌ 1 new — full report+ garden/papers/2026-08-18/child_mortality_in_the_past (new dataset: 5 table(s), 24 column(s))Automatically updated datasets matching excess_mortality|covid|fluid|flunet|country_profile|garden/ihme_gbd/2019/gbd_risk are not included. Run locally with Edited: 2026-08-28 17:33:56 UTC |
Replaces the hand-drawn Youth-mortality-rates-over-last-two-millennia-updated-to-2022.png with a reproducible export://static_viz step, and brings the data it needs into ETL. Data - New snapshots: Volk & Atkinson (2013) Tables 1 and 2, and Knodel (1970) Table 11, both transcribed by OWID and verified cell by cell against the papers' own column geometry. - New garden dataset papers/2026-08-18/child_mortality_in_the_past: the 21 historical societies the chart plots, the 17 hunter-gatherer societies, the global series, the national extremes, and the averages the chart states in words. - All 21 historical values reproduce the published chart exactly, and the average comes out at 48.10% -> 48%, the figure it states. Sweden 1751-80 is computed from HMD life tables (39.5%) and Bavaria 1750-99 from Knodel's counts (50.0%). - Modern anchors updated: 1950 = 26.3% (was 27%) and 2024 = 4.32% (was 2022 = 4.3%). The global series splices UN WPP life tables before 1990 onto UN IGME from 1990. Two things about the sources are recorded rather than smoothed over. Volk & Atkinson's published means do not follow from their own tables - Table 1 states 48.8% where the 17 printed rates average 47.3% - so the hunter-gatherer figure is carried as published and never recomputed, and the gap between the two is asserted. And Knodel's column counts children *surviving* to 15, not children dying; for this cohort the two coincide, so the subtraction is written out to stop the column being read as deaths. Layout follows the static-chart Vertical template's auto-layout rhythm rather than its placeholder y values, at the template's own measured type sizes. The license line drops "the author" because two names overrun the slot it shares with the tagline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ortality-staticviz
…attened header Master's shared static-viz module and the design team's template edits both landed after this step was written, so it was restating a contract that now exists as code and laying itself out against a header structure that no longer exists. - Adopt `etl.static_viz`: `apply_svg_rcparams`, `export_frame` (which owns the clip sweep, the opaque-PNG/transparent-SVG split and the figsize check against the template) and `source_citation`. That deletes the step's own citation builder and its preamble. - Draw every multi-line in-plot label as one text call per line, on an explicit baseline. A "\n" handed to a single ax.text gets no text-anchor at all, so those lines lose their alignment when Figma re-renders them in Lato. 12 of 33 in-plot labels were affected, including both centred average labels; all 33 now carry an anchor. - Drop the logo from the header rhythm. The design team flattened the Vertical template's header into [title, subtitle] with the logo as a sibling, so it no longer sets the title row's height and this chart's one-line title stops inheriting 12.5px of dead space. Verified against the live frame with create-figma-chart's verify_templates.js, which reports no drift on any template, and the band now lands at the 89 the frame itself measures. - Write the Human Mortality Database out in full in the source line. Keying the citation on attribution_short is what collapses UN IGME's two releases into one entry, but it renders HMD as an acronym a reader of the chart would not recognise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ortality-staticviz
…tself Following the rule master just added to create-figma-chart: a deep link handed over in chat is gone by the next session, and "where in Figma is this?" then means searching a ~200-page file by eye. Records the page, both frames with their node ids and a deep link, and says in the same breath that the ids are a convenience while the frame name is the durable join - it is the kebab-case slug the website exports the PNG by, so it is the one string shared by the layer panel, the filename and this file. Also writes down how the page was built, to the bar of redoing it from this file alone: the template node and the verify-before-cloning rule, the upload route and the cropped-raster trap, the 850/816 rescale and where it comes from, the full list of gid suffixes the accent binding has to name (a missed one leaves a mark on its matplotlib colour, which is how the paired viz shipped its outer band unbound), the two library style keys, and the weight runs each footer slot has to get back after `characters` collapses them. Includes one negative result, so it is not re-derived: Figma fits a whole number of dash repetitions into each segment of a path, so a dashed line follows its vertex spacing rather than its pattern. This chart's gridlines are two-vertex spans of 589pt - one segment each, far above the ~50px where that starts to matter - so they need no resampling. A dashed line following a curve here would. Outputs are byte-identical; this is a docstring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…E axis Two defects inherited from the published chart, both about the calendar rather than the data. **There is no year 0.** The calendar runs 1 BCE straight into 1 CE, and the chart asserted the missing year twice: an axis tick reading "0", and Roman Egypt labelled "around the year 0". The tick is gone - 0 on this scale is the boundary between the eras, not a year to label - and Volk & Atkinson's printed "0 A.D.", which is not a year either, is read in garden as the turn of the era it means. The snapshot keeps the paper's own string; garden resolves it and asserts that no row's period_start or period_end is 0. period_mid still can be, and Rome's is: it is a position on a continuous scale, not a claimed year, which is why only the endpoints are checked. **The BCE stretch of the axis had no tick at all.** Three of the 21 societies are dated in BCE and the leftmost tick was the era boundary, so a third of the axis carried no scale and the labels mentioned an era the axis never named. Adds a tick at 500 BCE - the round century between the undated hunter-gatherer marker and the earliest dated society, so it gives that stretch a reading without landing under the marker and appearing to date it. Both fixes need the eras naming, because "500" beside "500 BCE" is exactly the pair a reader misreads. Ticks and period labels now carry CE below the year 1000 and omit it above, so the axis reads 500 BCE / 500 CE / 1000 / 1500 / 2024 and a period reads "200 BCE-200 CE" or "300-550 CE". The wider labels re-solved on their own: the label placement search found slots for all 21, and the trailing axis margin is derived from the widest label rather than fixed. The Figma frame is updated to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…in their placement Each society's label put its name, its rate and its period at one size, so the three competed and the reader had to parse the run to find the number. The name and the rate now sit at 13px and the period at 12px, one step apart on the annotation ladder, on the same baseline. The size the labels ran at before was 8pt - 11.1 template px, under the 12px floor for in-plot text. The quieter of the two lines now sits on that floor rather than below it. Stacking the period onto a second line was built first and reverted. It reads the same and costs 2.3x the height, which this plot does not have: all 21 placed only by drifting up to 19.1 percentage points from their marks, on fifteen leaders, with four labels stranded in the empty band below the cluster. Ranking on one line trades that back for width, and width stopped being the binding constraint once the two bugs below were fixed. Measured over the same 21 labels: worst drift 19.1pp -> 8.4pp, median 7.3pp -> 3.8pp. Two defects in the placement search, neither of which surfaced as an error - the search just returned a worse answer: - **A label was blocked by its own mark.** The gap a label sits at (4px) is smaller than the clearance its mark reserves (3px + 1.5px of padding), so every label overlapped its own mark's box and the slot level with it - the one the ladder tries first, and the only one needing no leader - was rejected for all 21. - **A stale reserve fenced off the empty band.** The global series' label column was reserved to the left of the series, from before those labels were moved to its right. It protected space nothing occupies while blocking the band between the pre-modern cluster and the modern line. The offset ladder is now derived from each label's own height rather than listed, since rungs finer than the block is tall only offer the search slots that cannot clear. The step logs how far it had to push, so a run that only just fits is visible rather than silent. Every one of the 54 in-plot labels carries a text-anchor, checked against the emitted SVG. The Figma frame is updated to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds "The long-run history of child mortality" as a reproducible
export://static_vizstep, and brings the data it needs into ETL. The image it replaces (Youth-mortality-rates-over-last-two-millennia-updated-to-2022.png) is hand-drawn, so none of it could be re-derived, and its modern figures were two data revisions stale.Figma page: long-run-history-child-mortality —
20260827 The long-run history of child mortality (Pablo A).What's here
snapshots/papers/2026-08-18/volk_atkinson_2013.csvsnapshots/papers/2026-08-18/knodel_1970.csvdata://garden/papers/2026-08-18/child_mortality_in_the_pastexport://static_viz/papers/2026-08-18/child_mortality_long_runGarden-only — nothing reaches the grapher DB, so there are no new variable IDs and no charts to remap.
Numbers
All 21 historical values reproduce the published chart exactly (62, 57, 55, 53, 51, 51, 50, 50, 50, 49, 48, 48, 47, 45, 45, 45, 44, 41, 40, 40, 40), and their average comes out at 48.10% → 48%, the figure the chart states. Nineteen come from Volk & Atkinson's Table 2; Sweden 1751–80 is computed from Human Mortality Database life tables (39.5%, matching the published point) and Bavaria 1750–99 from Knodel's counts (50.0%).
Modern anchors are updated: 1950 = 26.3% (the old chart said 27%) and 2024 = 4.32% (it said 2022 = 4.3%). Niger still has the highest national rate, now 13.7% rather than 15%.
Both producers are current — UN IGME's 2025 release (published 2026-03-17) is in ETL, and UN WPP's 2026 revision has been postponed to 2027, so the 2024 revision remains the latest.
Two things about the sources, recorded rather than smoothed over
Volk & Atkinson's published means do not follow from their own tables. Table 1 states a mean child mortality rate of 48.8% across 17 societies; the 17 printed rates average 47.3%. Table 2 states 46.2%, which recomputes to 47.0%. In both tables the infant-mortality N is one lower than the count of printed values. The hunter-gatherer average is therefore carried through as published and never recomputed, while the historical average is our own computation — and the gap between them is asserted, so a future transcription error cannot hide inside it.
Knodel's column counts children who survived to 15, not children who died. For the cohort used here the two coincide (5.6 born, 2.8 surviving, so 2.8 died), which makes the column easy to misread, so the subtraction is written out explicitly. All five of his marriage cohorts are transcribed, and the meadow step reconciles them against the four figures his own Total row prints.
Layout, and the shared module
The step uses
etl.static_vizfor the Figma handoff contract —apply_svg_rcparams,export_frame(the clip sweep, the opaque-PNG/transparent-SVG split, and the figsize check against the template) andsource_citation— rather than restating it.Two fixes came out of building the Figma page:
"\n"handed to a singleax.textgets notext-anchorat all, so those lines lose their alignment the moment Figma re-renders them in Lato. 12 of 33 in-plot labels were affected, including both centred average labels; all 33 now carry one. Invisible in the PNG, which is why it needed a mechanical check.[title, subtitle]with the logo as a sibling, so it no longer sets the title row's height and this chart's one-line title stops inheriting 12.5px of dead space. Verified against the live frame withverify_templates.js(no drift on any of the ten templates), and the band now lands at the 89 the frame itself measures.The 21 labels are placed by a deterministic search against measured text boxes, with a hairline leader wherever a label lands more than 1.4pp from its mark. The license line drops the words "the author", because two names overrun the slot it shares with the tagline.
One departure needing design sign-off
The published chart drew a faded line from Sweden's 18th-century point down to the 1950 global rate. No data supports it — UN IGME and UN WPP begin in 1990 and 1950 — and it reads as one country's history continuing into the world's. It is dropped, and the global series begins where its sources do. The empty band that leaves is real.
Still open
TEMPLATES.mdin/create-static-vizstill documents the pre-flattening header rhythm, so it now disagrees withNODE-MAP.mdon a fact it owns. Its own paired-skill rule says to correct it; worth a separate PR.igme_under_fifteen_mortality, unrelated to this PR but live: Central African Republic's rate spikes to 60.4% (2009), 33.0% (2019) and 54.1% (2022) against ~11% either side. It is in the publishedyouth-mortality-ratedata, most likely from the vintage/current combination ingarden/un/2026-06-09/igme.py. To raise with that dataset's owners separately.verify_page.js(thediff_against_template.jsgate was run instead, and reports the frame matches the template), the Good Data Viz Checklist, and the adversarial-data-review / metadata-style / typo skills. Figma comments cannot be read over MCP, so the design review is outstanding rather than clean.