UPG Neighbor: each row carries its DX slug, and every DX group ships, so a DX page joins by the key it holds
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

This commit is contained in:
Doxa Maps 2026-09-28 15:30:15 +00:00
parent a0aa4df171
commit 39cba76c75
3 changed files with 18 additions and 3 deletions

File diff suppressed because one or more lines are too long

View file

@ -42,6 +42,8 @@ import { indexReached, closestReached, ringCounts } from '../src/rings.js'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
const CSV = join(ROOT, 'data/people-groups/people_groups.csv')
const OUT = join(ROOT, 'data/tables/near-neighbors.json')
// DX's own people-group set ({ slug, rop3, country: ISO3 }): gives each row the slug DX pages key on.
const DX_SNAPSHOT = join(ROOT, 'data/people-groups/doxa-groups-snapshot.json')
const NEAR_KM = 600 // E-3 radius: close enough to share a region, a market, a media footprint
const KEEP = { 1: 6, 2: 6, 3: 4 } // neighbors kept per unreached group, per E-level (best first)
@ -184,6 +186,18 @@ for (const u of unreached) {
}
const ringCount = ringCounts(unreached, (u) => ringOfU.get(u.i)?.[0])
// slug = the DX people-group slug for this row (null when the group is not in DX's set). Joined on
// rop3 + ISO3, the key both sources carry. It lets a DX page (gospel's bounded/centered sets) join by
// the key its own rows hold, with no second file to fetch or keep in step.
const dxSlug = new Map()
for (const d of JSON.parse(readFileSync(DX_SNAPSHOT, 'utf8')).groups || []) {
const k = d.rop3 + '|' + d.country
if (dxSlug.has(k)) fail(`two DX slugs for rop3+country ${k} (${dxSlug.get(k)}, ${d.slug})`)
dxSlug.set(k, d.slug)
}
// Every DX group ships too, reached or not, so a DX page never reads a reached group as "not in the table".
for (const g of groups) if (dxSlug.has(g.rop3 + '|' + g.iso3)) used.add(g.i)
// Ship only the groups a view can show: every unreached group, plus the reached groups some link or
// bridge uses.
const shipped = groups.filter((g) => !g.reached || used.has(g.i))
@ -192,7 +206,7 @@ const COLS = ['pgid', 'name', 'iso3', 'country', 'lat', 'lng', 'pop', 'reached',
'family', 'ethne', 'cluster', 'clusterName', 'bloc', 'blocName', 'religion', 'engaged', 'bible', 'film', 'audio', 'radio',
// rop3 + iso3 is the join key other plugins hold (DX groups: rop3 + country). ring = centered-set ring 1-10
// (0 = REACHED, the centre; -1 = unreached with no relation, unplaced: never drawn as a ring); ringNb = the shipped row of the reached group that sets it (-1 = none).
'rop3', 'ring', 'ringNb']
'rop3', 'ring', 'ringNb', 'slug']
// Repeated strings are dictionary-encoded (the value in the row is an index into dict[col]); this
// halves the file. A reader decodes with dict[col][row[j]].
const DICT_COLS = ['iso3', 'country', 'lang', 'langName', 'family', 'cluster', 'clusterName', 'bloc', 'blocName', 'religion']
@ -200,6 +214,7 @@ const dict = Object.fromEntries(DICT_COLS.map((c) => [c, []]))
const dictIdx = Object.fromEntries(DICT_COLS.map((c) => [c, new Map()]))
const enc = (c, v) => { const m = dictIdx[c]; if (!m.has(v)) { m.set(v, dict[c].length); dict[c].push(v) } return m.get(v) }
const rows = shipped.map((g) => COLS.map((c) => {
if (c === 'slug') return dxSlug.get(g.rop3 + '|' + g.iso3) ?? null
const rg = ringOfU.get(g.i)
if (c === 'ring') return rg ? rg[0] : g.reached ? 0 : -1 // 0 = reached (the centre), -1 = unplaced
if (c === 'ringNb') return rg ? newIdx.get(rg[1]) : -1

File diff suppressed because one or more lines are too long