Reach the non-India zones through their selectors, not a second bridge
The province bridge only reaches India and China, because that is all
gospel-by-province-groups holds. Every other UPG-100 zone already has a path:
each zone in clusters.json carries a selector, and selector-evaluator.js turns a
selector plus people-group records into the slugs in that zone. So this is an
adapter, not a new join.
Which path a zone needs is a property of the zone rather than a fallback chain:
a pg_slugs selector IS the membership and is matched on slug, the evaluator
rejecting that type on purpose; country selectors go through evaluate(); the
India and China zones use the province fold.
Records must be passed FLAT. The evaluator reads country_code as a string, while
the file header describes the source shape as { value, label } — that is the
shape before normalize.js flattens it. Passing the object form matches nothing
and returns an empty set for every zone, which reads as "this zone has no
groups" rather than as a mistake. It cost a run here, so the check pins the
empty-set arm as well as the working one.
Zones whose selector needs a field the records do not carry return an empty set;
zones the evaluator rejects are reported separately. An empty zone and an
unanswerable zone are different facts and a legend that draws them the same way
is lying about one of them.
This commit is contained in:
parent
d6253b34d0
commit
2e62192c27
1 changed files with 61 additions and 0 deletions
|
|
@ -55,3 +55,64 @@ export function zonesByGroup(adminToList, provinceRows, key = (r) => `${r.people
|
|||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* ── THE SECOND JOIN: COUNTRY GRAIN ───────────────────────────────────────────
|
||||
*
|
||||
* The province bridge above only reaches India and China, because that is all
|
||||
* gospel-by-province-groups.json holds (IND 855 + CHN 154 rows). Every other
|
||||
* UPG-100 zone needs a different path, and upg100 already built it: each zone in
|
||||
* clusters.json carries a SELECTOR, and selector-evaluator.js turns a selector
|
||||
* plus a set of people-group records into the set of slugs in that zone.
|
||||
*
|
||||
* So this is an adapter, not a new join. Three paths, and which one a zone needs
|
||||
* is a property of the zone, not a fallback chain:
|
||||
*
|
||||
* pg_slugs zones -> the selector IS the membership; match on slug.
|
||||
* The evaluator REJECTS this type ON PURPOSE (it throws and
|
||||
* names the supported types) because there is nothing to
|
||||
* evaluate — reading `selector.pg_slugs` is the whole join.
|
||||
* country selectors -> evaluate(): country_code, country_codes, union,
|
||||
* wagf_block, wagf_region, deaf_predicate, affinity_bloc.
|
||||
* province zones -> the fold above.
|
||||
*
|
||||
* ⚠ THE RECORD SHAPE IS FLAT, and this cost me a run. The evaluator reads
|
||||
* `r.country_code === want` — a plain string. The file header describes the
|
||||
* source fields as `{ value, label }`, which is the shape BEFORE normalize.js
|
||||
* flattens them. Passing `{ value }` here matches nothing and returns an empty
|
||||
* Set for every zone, which looks exactly like "this zone has no groups" rather
|
||||
* than like a bug. Pass flattened records.
|
||||
*/
|
||||
|
||||
/** near-neighbors decoded row -> a record the selector evaluator understands. */
|
||||
export function toSelectorRecord(row, slug) {
|
||||
return {
|
||||
slug,
|
||||
country_code: row.iso3 ?? null, // FLAT, not { value }
|
||||
name: row.name ?? null,
|
||||
language: row.langName ?? null,
|
||||
rop1: row.bloc ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* zone id -> Set<slug>, for every zone whose selector this record set can answer.
|
||||
*
|
||||
* Zones whose selector needs a field the records do not carry come back as an
|
||||
* EMPTY SET rather than being omitted, and zones the evaluator rejects are
|
||||
* reported separately — an empty zone and an unanswerable zone are different
|
||||
* facts, and a legend that renders them the same way is lying about one of them.
|
||||
*/
|
||||
export function zonesBySelector(zoneLists, records, evaluate) {
|
||||
const members = new Map()
|
||||
const unanswerable = new Map()
|
||||
for (const z of zoneLists || []) {
|
||||
if (!z?.id || !z.selector) continue
|
||||
try {
|
||||
members.set(z.id, evaluate(z.selector, records))
|
||||
} catch (err) {
|
||||
unanswerable.set(z.id, z.selector.type)
|
||||
}
|
||||
}
|
||||
return { members, unanswerable }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue