The page says each box is a share of the people in this list. It printed one DOXA-wide figure - 0.6133% of 209,079,159 people - in every packet, true of no single list. That figure had no builder in the repo. build-evangelical-share.mjs makes its method reproducible, per group: the IMB CSV joined to the DOXA snapshot by ROP3 + country, each group's EvangelicalLevel band at its midpoint (0 for No Known Evangelicals, the midpoint read from the band's own wording), weighted by IMB population. It writes evangelical-by-slug.json and refuses to write unless the table, summed over every DOXA group, reproduces christian-share.json exactly: 2,091 matched, 209,079,159 people, 1,282,178 evangelical, 0.6133%. Change a single band's midpoint rule and it refuses. Not the API's imb_gsec: GSEC puts 225 groups at "no evangelicals" where EvangelicalLevel puts 1,444, because its "less than 2%" band also carries church-planting status. A share from it sits on a different scale from the method the page's own footnote states. evangelicalShare.js sums a packet's own groups - pure, zero imports, so the browser print and the offline generator run the same sum. Across the 28 zones the page now carries 28 different figures, from 0.182% (Deaf) to 0.922%. A host that supplies its own figure still wins. A list with no published levels prints a dash and no number. The old guard, Number.isFinite(Number(x)), passes null as 0 and would have printed "0% evangelical" for no data. Where some of a list's groups lack a level, the footnote says what the figure is computed over; it says nothing when coverage is whole. That clause is in all eleven languages. check-evangelical-share (gates:pre): the table reproduces the committed figure; different lists differ; no data is a dash; coverage appears only when partial. Mutation-tested four ways, each caught - the fourth only once it corrupted an existing group rather than adding one no sum reads. |
||
|---|---|---|
| .. | ||
| data | ||
| fixtures | ||
| snapshots | ||
| build-gallery.mjs | ||
| build-hd-gallery.mjs | ||
| emit-countries.mjs | ||
| export-public.mjs | ||
| generate.mjs | ||
| make-previews.py | ||
| osm-basemap.mjs | ||
| packet-strings.json | ||
| packet.mjs | ||
| poster-regions.mjs | ||
| publish-html.mjs | ||
| README.md | ||
| series.json | ||
| serve-persist.sh | ||
| serve.sh | ||
| shoot.mjs | ||
| verify-posters.mjs | ||
library/scripts/posters/ — UPG-100 poster generator
Renders one 3ft × 3ft print poster per zone (28 today) for the my-upg-100-list profile, from the profile's own data. It ships inside the repo alongside that data on purpose — reaching for the data via an env var once silently rendered stale posters (Nepal, Bangladesh, Bhutan vanished).
bun run posters -> posters:shoot -> (WebP previews) -> bun run build
HTML per zone PNG @3000² assets/previews/ app shows them
generate.mjsis the template;shoot.mjsscreenshots to PNG (Firefox only — privacy-first policy).- A template change is not visible in the app until previews are regenerated and the app rebuilt.
- Exported PNGs go to a sibling folder (
../../../upg-100-posters/), neverpublic/— Capacitor packagespublic/wholesale. snapshots/holds frozen template versions (v5 is the approved current); each has its own README.
Full reference — files, env vars, gotchas, measurement discipline: ../../../documentation/architecture/poster-generation.md.