a9550858 guarded twenty gates; these six were still unguarded. Each
now exits 75 (NOT MEASURED) rather than grading another tree:
- check-legend-export-reachable, check-vision-card-numbers --url:
requireThisTree on the URL they are given.
- check-embed-basemap-paints: it picks a RANDOM port, not an
OS-assigned one, so if another agent holds it, its own server fails
to start and it would measure theirs. It now checks the leaf server's
identity, stops its own servers and exits 75. Its "a server did not
come up" path exited 1 and now exits 75.
- check-tabs-localized, check-poster-prints read library/ from
DOXA_REPO. Pointing that at another checkout now needs
DOXA_REPO_MEASURE_OTHER=1 (new requireThisRepo helper).
- check-packet-render refuses a packet file inside a DIFFERENT git
checkout (new requirePathInThisCheckout helper); a file outside any
checkout is allowed with a note.
Proven by pointing each at a wrong build: a copy of this public/ whose
manifest differs by one byte, served on its own port, and another
worktree for the file and DOXA_REPO gates. All 8 (these 6, plus
shell-scroll and upg-map-data) exit 75. On this tree they measure:
legend-export, vision-card, upg-map-data, tabs-localized and
poster-prints PASS. packet-render gets past the guard and fails on a
non-packet file, as intended. embed-basemap measures and FAILS: "the
page does NOT paint a basemap on its own baked token". That is a
product finding, not the guard, and is reported to its owner.
69 lines
4.6 KiB
JavaScript
69 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* check-vision-card-numbers — the DOXA Vision Card shows the numbers the DATA gives, nothing typed.
|
||
*
|
||
* USAGE: node internal/check-vision-card-numbers.mjs (data only, no browser, ~2 s)
|
||
* node internal/check-vision-card-numbers.mjs --url <card> (also read the RENDERED card)
|
||
* Exit 0 = every number matches · 1 = a mismatch.
|
||
*
|
||
* INDEPENDENT RECOMPUTATION. It does not import build-vision-funnel.mjs: it re-reads the raw CSV with
|
||
* its own column lookup and re-applies the published rules (HOW-THE-NUMBERS-ARE-COUNTED.md) to the
|
||
* saved DOXA list, then compares with (1) data/people-groups/vision-funnel.json, which the card
|
||
* compiles in, and (2) with --url, the numbers actually painted in the card's bars. A typed-in
|
||
* number, a stale funnel file or a rendering bug each makes it fail.
|
||
*/
|
||
import { readFileSync } from 'node:fs'
|
||
import { resolve, dirname } from 'node:path'
|
||
import { fileURLToPath } from 'node:url'
|
||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||
const text = readFileSync(resolve(ROOT, 'data/people-groups/people_groups.csv'), 'utf8')
|
||
|
||
// Own CSV reader: split records on newlines outside quotes, fields on commas outside quotes.
|
||
const records = []; { let cur = ''; let q = false; for (const ch of text) { if (ch === '"') q = !q; if (ch === '\n' && !q) { records.push(cur); cur = '' } else cur += ch } if (cur.trim()) records.push(cur) }
|
||
const split = (line) => { const out = []; let f = ''; let q = false; for (let i = 0; i < line.length; i++) { const c = line[i]; if (c === '"') { if (q && line[i + 1] === '"') { f += '"'; i++ } else q = !q } else if (c === ',' && !q) { out.push(f); f = '' } else if (c !== '\r') f += c } out.push(f); return out }
|
||
const head = split(records[0]).map((h) => h.replace(/^/, ''))
|
||
const col = (n) => head.indexOf(n)
|
||
const L = col('LostnessPriority'), R3 = col('ROP3Code'), ISO = col('ISOAlpha3')
|
||
const data = records.slice(1).map(split).filter((f) => f.length === head.length)
|
||
const expect = {
|
||
all: data.length,
|
||
unreached: data.filter((f) => ['0', '1', '2'].includes(f[L])).length,
|
||
frontier: data.filter((f) => f[L] === '0').length,
|
||
}
|
||
const snap = JSON.parse(readFileSync(resolve(ROOT, 'data/people-groups/doxa-groups-snapshot.json'), 'utf8'))
|
||
const lost = new Map(data.map((f) => [f[R3] + '|' + f[ISO], f[L]]))
|
||
expect.doxa = snap.groups.length
|
||
expect.doxaFrontier = snap.groups.filter((g) => lost.get(g.rop3 + '|' + g.country) === '0').length
|
||
|
||
const funnel = JSON.parse(readFileSync(resolve(ROOT, 'data/people-groups/vision-funnel.json'), 'utf8'))
|
||
const got = Object.fromEntries(funnel.steps.map((s) => [s.id, s.groups]))
|
||
got.doxaFrontier = funnel.steps.find((s) => s.id === 'doxa').frontier
|
||
const bad = []
|
||
for (const k of Object.keys(expect)) {
|
||
const ok = expect[k] === got[k]
|
||
console.log((ok ? 'ok ' : 'FAIL ') + k.padEnd(13) + ' recomputed ' + String(expect[k]).padStart(6) + ' vision-funnel.json ' + got[k])
|
||
if (!ok) bad.push(k)
|
||
}
|
||
|
||
const i = process.argv.indexOf('--url')
|
||
if (i > -1) {
|
||
// Measure THIS build, or measure nothing (exit 75 = NOT MEASURED; see a9550858 / _serves-this-tree.mjs).
|
||
const { requireThisTree } = await import('./_serves-this-tree.mjs')
|
||
await requireThisTree(process.argv[i + 1], ROOT, 'VISION-CARD-NUMBERS')
|
||
const { chromium } = await import('playwright')
|
||
const b = await chromium.launch()
|
||
const p = await b.newPage({ viewport: { width: 900, height: 1000 } })
|
||
await p.goto(process.argv[i + 1], { waitUntil: 'networkidle' })
|
||
await p.waitForTimeout(1500)
|
||
const shown = await p.evaluate(() => { const el = document.querySelector('doxa-vision-card'); const r = el && el.shadowRoot; return r ? [...r.querySelectorAll('.vc-n')].map((n) => n.textContent.trim()) : [] })
|
||
const note = await p.evaluate(() => { const r = document.querySelector('doxa-vision-card')?.shadowRoot; return r ? r.querySelector('.vc-step-doxa .vc-note')?.textContent || '' : '' })
|
||
await b.close()
|
||
const want = [expect.all, expect.unreached, expect.frontier, expect.doxa].map((n) => new Intl.NumberFormat('en').format(n))
|
||
const okBars = JSON.stringify(shown) === JSON.stringify(want)
|
||
console.log((okBars ? 'ok ' : 'FAIL ') + 'rendered bars ' + JSON.stringify(shown) + (okBars ? '' : ' expected ' + JSON.stringify(want)))
|
||
const okNote = note.includes(new Intl.NumberFormat('en').format(expect.doxaFrontier))
|
||
console.log((okNote ? 'ok ' : 'FAIL ') + 'rendered DOXA note "' + note.trim() + '"')
|
||
if (!okBars) bad.push('rendered bars'); if (!okNote) bad.push('rendered note')
|
||
}
|
||
console.log(bad.length ? 'VISION-CARD-NUMBERS: FAIL — ' + bad.join(', ') : 'VISION-CARD-NUMBERS: PASS — every number matches the data')
|
||
process.exit(bad.length ? 1 : 0)
|