diff --git a/library/scripts/posters/packet.mjs b/library/scripts/posters/packet.mjs index 41d8c5a05..58eb7ec01 100644 --- a/library/scripts/posters/packet.mjs +++ b/library/scripts/posters/packet.mjs @@ -119,14 +119,14 @@ const clusters = JSON.parse(readFileSync(join(DATA, 'clusters.json'), 'utf8')); // // ★ READ, NEVER COUNTED. The four steps come verbatim from vision-funnel.json, generated by // build-vision-funnel.mjs from the IMB CSV and the live network list, and already driving the -// Vision Card. K and the unassigned count ARE measured here — but from the catalogue that defines +// Vision Card. K IS measured here — from the catalogue that defines // them, at build time, so no figure on the page is a typed constant. // A missing file yields null, and buildPacket omits page 2 rather than printing numbers it could // not read. // ⚠ A FUNCTION, NOT A CONSTANT, because it depends on PACKET_LIST — which is resolved further down, // after the records load. It mirrors upg-100-list.vue's packetFunnel() exactly: the DEFAULT list is the // only one the zone catalogue describes, so it is the only one that may claim "1 of K lists" and an -// unassigned count; a non-default list states its OWN total and goes quiet on the rest. +// claim; a non-default list states its OWN total and goes quiet on the rest. function buildFunnel(membership) { try { const raw = JSON.parse(readFileSync(join(DATA, '../../../data/people-groups/vision-funnel.json'), 'utf8')); @@ -134,11 +134,17 @@ function buildFunnel(membership) { if (membership) { return { ...raw, listTotal: membership.total, listId: LIST, listLabel: membership.label }; } + // ⚠ NO `unassigned` — see upg-100-list.vue's packetFunnel() for the full measurement. Short + // version: it printed "21 of the 2,107 are not yet in any list", derived from summing DECLARED + // count fields, and neither that figure nor the 418 I got by resolving the selectors survives + // scrutiny — the records cache lacks wagf_region/wagf_block, so 13 of the 27 selectors cannot + // resolve, and clusters.json's two DEAF lists are a second, overlapping partition that + // allLists() excludes. "In no list" has no single answer until someone says which partition is + // meant. Both paths must agree, so this one stops claiming it too. const lists = [...(clusters.lists || []), ...(clusters.asia_lists || []), ...(clusters.uncovered_lists || [])]; const listTotal = Number(raw.steps[raw.steps.length - 1].groups) || 0; - const assigned = lists.reduce((s, l) => s + (Number(l.count) || 0), 0); - return { ...raw, listCount: lists.length, listTotal, unassigned: Math.max(0, listTotal - assigned) }; + return { ...raw, listCount: lists.length, listTotal }; } catch (_) { return null; } } const { evaluate } = await import(join(DATA, 'selector-evaluator.js')); diff --git a/plugin-designs/my-upg-100-list/upg-100-list.vue b/plugin-designs/my-upg-100-list/upg-100-list.vue index 7ca034279..380ece4c9 100644 --- a/plugin-designs/my-upg-100-list/upg-100-list.vue +++ b/plugin-designs/my-upg-100-list/upg-100-list.vue @@ -1356,16 +1356,28 @@ async function packetFunnel() { const doxaTotal = Number(raw.steps[raw.steps.length - 1].groups) || 0; // THE DEFAULT LIST is the one the zone catalogue actually describes, so it is the only one that - // can honestly claim "1 of K lists" and an unassigned count. + // can honestly claim "1 of K lists". + // + // ⚠ `unassigned` IS DELIBERATELY NOT SUPPLIED ANY MORE, and taking it out is the point. It printed + // "21 of the 2,107 are not yet in any list", derived as doxaTotal minus the sum of the DECLARED + // `count` fields — a claim about clusters.json's BOOKKEEPING, not about membership. Measured + // 2026-09-26 while trying to verify it, and neither available figure survives: + // · resolving every selector against the packet records gives 418 in no geographic list, not 21; + // · but that cache carries only 12 fields (no wagf_region, no wagf_block), so 13 of the 27 + // selectors cannot resolve against it at all, and deaf_predicate matches 0 of the 123 deaf + // records that ARE there. + // Both numbers describe the FIXTURE, not the product. And the question is ill-posed besides: + // clusters.json has FOUR buckets — the two deaf lists (61 + 62 groups) are excluded from + // allLists(), and summing all 29 gives 2,209 against a total of 2,106, so the deaf lists OVERLAP + // the geographic ones. They are a second, cross-cutting partition, which means "in no list" has no + // single answer until someone says which partition is meant. + // A confident figure I cannot defend is exactly the defect class I have been removing from other + // surfaces all day; this one I put here myself. funnelPage already falls back to the + // provenance-only footnote when unassigned is absent, so the page simply stops claiming it. + // Carded for the Driver: 27 geographic / 29 counting deaf, and whether "Central Asia + Pakistan" + // (55 groups, contiguous, poster-walled as a real region) is a prayer list rather than a residual. if (listId === LIST_DEFAULT) { - const lists = allLists(); - const assigned = lists.reduce((sum, l) => sum + (Number(l.count) || 0), 0); - return { - ...raw, - listCount: lists.length, - listTotal: doxaTotal, - unassigned: Math.max(0, doxaTotal - assigned), - }; + return { ...raw, listCount: allLists().length, listTotal: doxaTotal }; } // A NON-DEFAULT LIST: read its own DOXA-side total from the membership file the build writes.