From 30db4bb7631822143048fce60ee50619af4885b1 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 03:00:23 +0000 Subject: [PATCH] The people-group list is documented in the repo, library/lists/README.md, not in a file outside it Four places in the repo (the registry, the resolver, the packet renderer and a gate's allowance) cited a research spec that lives outside the repository, so a reader of the code could not follow the reference. library/lists/README.md now documents the list as built: how a list is chosen, the registry and its five lists with their measured counts, where membership is generated, the two tiers (narrowing DOXA's rows, failing closed; IMB rows per map), the data sources and their terms, and the questions still open. The four citations point at it. --- internal/check-universal-props.mjs | 2 +- library/lists/README.md | 78 ++++++++++++++++++++++++++++++ library/lists/lists.json | 2 +- library/lists/peopleGroupList.js | 2 +- library/packet/packet-render.js | 2 +- 5 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 library/lists/README.md diff --git a/internal/check-universal-props.mjs b/internal/check-universal-props.mjs index 3496fb912..7c17f21da 100644 --- a/internal/check-universal-props.mjs +++ b/internal/check-universal-props.mjs @@ -76,7 +76,7 @@ const ALLOW = { + 'making it locale-derived would break a gate that exists to keep it fixed. Scoped to this FILE, for ' + 'every bundle that imports it (regions-dashboard, country-map): the rest of each bundle is still ' + 'answerable for a new en-US. Revisit only if the numerals policy changes.', - 'all-people-groups:LIST-DSM': 'draws the IMB dataset (all 12,379 groups), not the DOXA list: a list there is Tier B (SPEC-PEOPLE-GROUP-LIST-PROP.md)', + 'all-people-groups:LIST-DSM': 'draws the IMB dataset (all 12,379 groups), not the DOXA list: a list there is Tier B (library/lists/README.md)', } const stripComments = (src) => src diff --git a/library/lists/README.md b/library/lists/README.md new file mode 100644 index 000000000..ad7ce19c8 --- /dev/null +++ b/library/lists/README.md @@ -0,0 +1,78 @@ +# People-group lists + +Every map shows people groups. The **list** decides which ones, and it is a universal prop like the +engine, the locale and the token: the same map can show any project's list, and the choice is made +the same way everywhere. + +## Choosing a list + +First answer wins (`peopleGroupList.js`): + +1. `profile-config` `list`: this embed, decided by whoever mounted the element; +2. `?list=` in the page URL: any generated leaf can be switched from the URL; +3. `window.MAP_LIST`: a host global, for a host that sets it programmatically; +4. `doxa`, the default: today's behaviour, nothing fetched and nothing filtered. + +An id or an alias (`700`, `project-700`) is accepted. Anything unknown falls back to the default and +says so once in the console. + +## The registry: `lists.json` + +Each list is a **filter, never a hand-kept roster**: + +| id | Label | Source | Definition | Count when measured | +|---|---|---|---|---| +| `doxa` | Project 2000: DOXA's people groups | the DOXA API | DOXA's live list | 2,107 | +| `unreached` | Project 7000 | IMB | Lostness Priority 0–2 (under 2% evangelical) | 7,341 | +| `frontier` | Project 4700 | IMB | Lostness Priority 0 (0.1% evangelical or less) | 4,754 | +| `frontier-500k` | Project 700 | IMB | frontier, population 500,000 or more | 737 | +| `unengaged` | Unengaged unreached | IMB | Lostness Priority 0–2 and unengaged | | + +Counts are from the IMB file of 2026-08-02 and the DOXA API of 2026-09-26; they move when either +source is refreshed. Labels here are the English fallback: every locale's label and definition live +in `library/i18n/locales//lists.json` (read them with `listLabel` / `listDefinition`), and +`check:list-prop-unit` fails if the English catalogue and this registry disagree. + +**Membership is built, not computed in the browser.** `library/scripts/people-groups/build-lists.mjs` +writes `data/people-groups/lists/.json` for each non-default list (the member keys, plus what a +map draws for IMB rows), and the tree serves them from its asset pool. + +## How a map honours a list: two tiers + +**Tier A: narrowing DOXA's rows.** Rows keep coming from the DOXA API, because that is where the +prayer fields live (praying, adopted, committed, the prayer link). `applyPeopleGroupList()` keeps the +rows whose slug belongs to the list. So under Project 700 a map shows the DOXA groups that are +Project 700 groups, and every map that loads through the library follows with no plugin change. + +It **fails closed**: if a non-default list's membership cannot be loaded, the map gets no rows and the +console names the list and the URL. A map that silently showed all 2,107 while an embed asked for +Project 700 would be showing the wrong list with no sign of it. + +**Tier B: IMB rows.** A list's groups that DOXA does not carry exist only in the IMB data, without +DOXA's prayer fields. A map that shows them must either hide those layers and figures or join them +where a DOXA row exists (1,745 of the 4,754 frontier groups are DOXA groups; inside DOXA's 2,107, +1,753 are frontier). This is per-map work. `all-people-groups` already draws the whole IMB set, so a +list there is Tier B by nature. + +## The data + +- **IMB / peoplegroups.org:** `data/people-groups/people_groups.csv`, 12,379 rows, one per people group + per country (not distinct groups). A public download with no key or account; the attribution line + is "People group data: peoplegroups.org". +- **DOXA API:** `GET /api/people-groups/list` returns DOXA's active list (2,107) whatever filter is + passed, so every list beyond DOXA's comes from the IMB file. +- **The funnel** (the vision card, and page 2 of a prayer packet) is generated from the same two + sources by `library/scripts/people-groups/build-vision-funnel.mjs`; its rules are in + `plugin-designs/doxa-vision-card/HOW-THE-NUMBERS-ARE-COUNTED.md`. + +## Open questions + +- **Project 4000 and Project 3000** have no definition in the data held here. The nearest candidates: + unengaged unreached groups (2,968) for 3,000; Frontier + Pioneer (5,950) or unengaged frontier + (2,473) around 4,000. A list another organisation publishes would need that source. +- **The funnel's top step:** IMB counts 12,379 group-by-country rows; about 16–17k is Joshua Project's + count. One funnel should use one source, or its steps do not nest. +- **"Unreached", two rules 34 apart:** Lostness Priority 0–2 (7,341, used today) or evangelical level + under 2% (7,307). +- **peoplegroups.org terms:** the data is a public download with an attribution line, but no licence + text is recorded; republishing counts and rows in maps, packets and partner embeds needs confirming. diff --git a/library/lists/lists.json b/library/lists/lists.json index 34b4e2c55..a04b5d5bc 100644 --- a/library/lists/lists.json +++ b/library/lists/lists.json @@ -1,5 +1,5 @@ { - "_about": "The people-group LISTS a map can be asked to show (profile-config `list`, ?list=, window.MAP_LIST; see peopleGroupList.js). Each list is a FILTER, never a hand-kept roster: `source: doxa` is DOXA's live list as the API serves it; `source: imb` is a filter over the IMB / peoplegroups.org dataset in data/people-groups/people_groups.csv, using its own columns (LostnessPriority, Population, EngagementStatus). library/scripts/people-groups/build-lists.mjs turns each imb filter into data/people-groups/lists/.json. TIER A (today): a map shows the DOXA groups that belong to the list, because the DOXA API is where every map's rows and prayer fields come from. TIER B (later, per map): the list's groups that DOXA does not carry. Definitions and counts: _qa-cycle/03-deployment-cycle/SPEC-PEOPLE-GROUP-LIST-PROP.md. LABELS HERE ARE THE ENGLISH FALLBACK ONLY: every locale's label and definition live in library/i18n/locales//lists.json (read them with listLabel / listDefinition), and check:list-prop-unit fails if the English catalogue and this file disagree.", + "_about": "The people-group LISTS a map can be asked to show (profile-config `list`, ?list=, window.MAP_LIST; see peopleGroupList.js). Each list is a FILTER, never a hand-kept roster: `source: doxa` is DOXA's live list as the API serves it; `source: imb` is a filter over the IMB / peoplegroups.org dataset in data/people-groups/people_groups.csv, using its own columns (LostnessPriority, Population, EngagementStatus). library/scripts/people-groups/build-lists.mjs turns each imb filter into data/people-groups/lists/.json. TIER A (today): a map shows the DOXA groups that belong to the list, because the DOXA API is where every map's rows and prayer fields come from. TIER B (later, per map): the list's groups that DOXA does not carry. Definitions and counts: library/lists/README.md. LABELS HERE ARE THE ENGLISH FALLBACK ONLY: every locale's label and definition live in library/i18n/locales//lists.json (read them with listLabel / listDefinition), and check:list-prop-unit fails if the English catalogue and this file disagree.", "default": "doxa", "lists": [ { diff --git a/library/lists/peopleGroupList.js b/library/lists/peopleGroupList.js index 2f60b093f..9a5bcd373 100644 --- a/library/lists/peopleGroupList.js +++ b/library/lists/peopleGroupList.js @@ -15,7 +15,7 @@ * prayer fields live. applyPeopleGroupList() keeps the rows whose slug belongs to the list, from a * membership file the build writes (data/people-groups/lists/.json, served under the asset * pool). So under Project 700 a map shows the DOXA groups that are Project 700 groups. The list's - * groups that DOXA does not carry are Tier B, per map (SPEC-PEOPLE-GROUP-LIST-PROP.md). + * groups that DOXA does not carry are Tier B, per map (library/lists/README.md). * * FAILS CLOSED. If a non-default list's membership cannot be loaded, the map gets NO rows and the * console names the list and the URL. A map that silently showed all 2,107 while an embed asked for diff --git a/library/packet/packet-render.js b/library/packet/packet-render.js index 0480501f3..a37e4cc47 100644 --- a/library/packet/packet-render.js +++ b/library/packet/packet-render.js @@ -539,7 +539,7 @@ export function createPacketRenderer({ strings, lang = 'en', fontData = null, de // (`funnel.listTotal ?? steps[last].groups`), which is 2,107 — DOXA's list. That is right today // because every map shows DOXA, and it becomes WRONG the moment the universal `list` prop lands // (exp-shepherd, library/lists/peopleGroupList.js): under ?list=frontier a Tier A map shows the - // DOXA groups that are ALSO frontier, which SPEC-PEOPLE-GROUP-LIST-PROP.md measures at 1,753. The + // DOXA groups that are ALSO frontier, which library/lists/README.md records at 1,753. The // page would have printed "N of the 2,107" with total confidence while the map showed a different // set — a faithful proxy that silently changes meaning when a new path appears, which is the same // family as inferring "did it work" from a field that only answers "did it isolate".