#!/usr/bin/env node /** * check-evangelical-share — the packet's "% evangelical" page states THIS list's share. * * The page says each box is a share of the people in this list. It used to print one DOXA-wide figure * in every packet. Each packet now sums its own groups from the per-group table * (data/people-groups/evangelical-by-slug.json), by the method the DOXA-wide figure was made with. * * (a) ANCHOR — summed over every DOXA group, the table reproduces christian-share.json exactly, so * the per-list figures and the committed figure are one method. * (b) NOT A CONSTANT — two different lists give different figures. A page that printed the same * number for every list would pass (a) and fail here. * (c) NO DATA IS A DASH — a list with no published levels prints "—" and no percentage. Presence is * tested, not finiteness: Number(null) is 0, and a finiteness guard prints "0%" for no data. * (d) COVERAGE ONLY WHEN PARTIAL — the "computed over N of M groups" clause appears when some of the * list's groups lack a level, and not when all have one. * * Exit 0 pass · 1 fail · 75 NOT MEASURED. */ import { readFileSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' const HERE = dirname(fileURLToPath(import.meta.url)) const REPO = resolve(HERE, '..') let evangelicalShareFor, createPacketRenderer, table, committed, snapshot, clusters, strings try { ;({ evangelicalShareFor } = await import(join(REPO, 'library/packet/evangelicalShare.js'))) ;({ createPacketRenderer } = await import(join(REPO, 'library/packet/packet-render.js'))) table = JSON.parse(readFileSync(join(REPO, 'data/people-groups/evangelical-by-slug.json'), 'utf8')) committed = JSON.parse(readFileSync(join(REPO, 'data/people-groups/christian-share.json'), 'utf8')) const snap = JSON.parse(readFileSync(join(REPO, 'data/people-groups/doxa-groups-snapshot.json'), 'utf8')) snapshot = Array.isArray(snap) ? snap : (snap.posts || snap.groups || snap.rows || []) clusters = JSON.parse(readFileSync(join(REPO, 'plugin-designs/my-upg-100-list/data/clusters.json'), 'utf8')) strings = JSON.parse(readFileSync(join(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8')) } catch (e) { console.error(`EVANGELICAL-SHARE: NOT MEASURED (exit 75) — an input did not load: ${e.message}`) process.exit(75) } const fails = [] const notes = [] // (a) ANCHOR { const all = evangelicalShareFor(snapshot.map((g) => ({ slug: g.slug })), table) const same = all.groupsMatched === committed.groupsMatched && all.population === committed.population && all.evangelicalPopulation === committed.evangelicalPopulation && Math.abs(all.pctEvangelical - committed.pctEvangelical) < 0.00005 if (!same) fails.push(`summed over every DOXA group the table gives ${JSON.stringify({ m: all.groupsMatched, p: all.population, e: all.evangelicalPopulation, pct: all.pctEvangelical })}; christian-share.json says ${JSON.stringify({ m: committed.groupsMatched, p: committed.population, e: committed.evangelicalPopulation, pct: committed.pctEvangelical })}`) else notes.push(` ok summed over all ${all.groupsInList} DOXA groups it reproduces christian-share.json: ${all.pctEvangelical.toFixed(4)}%`) } // (b) NOT A CONSTANT — two lists whose members are listed by slug in the catalogue, so no API is needed { const bySlugZones = (clusters.asia_lists || []).filter((z) => z.selector && z.selector.type === 'pg_slugs') const figures = bySlugZones.map((z) => evangelicalShareFor(z.selector.values.map((slug) => ({ slug })), table).pctEvangelical) const distinct = new Set(figures.map((f) => (f === null ? 'none' : f.toFixed(5)))).size if (bySlugZones.length < 2) fails.push('fewer than two slug-listed zones to compare — (b) cannot be measured') else if (distinct < 2) fails.push(`${bySlugZones.length} different lists all print the same figure — the page is a constant, not each list's share`) else notes.push(` ok ${bySlugZones.length} slug-listed lists give ${distinct} distinct figures (${Math.min(...figures.filter((f) => f !== null)).toFixed(3)}%–${Math.max(...figures.filter((f) => f !== null)).toFixed(3)}%)`) } // (c) + (d) THE PAGE { const R = createPacketRenderer({ strings, lang: 'en' }) const zone = { id: 'z', label: 'Z' } const page = (christian) => R.christianPage(zone, christian) const figure = (html) => ((html.match(/class="ev-pct">([^<]*)<0\.01%