Rank the gap, and name the languages that have nothing
The filter shows WHERE the gap is; it could not say which part of it is biggest. The 3,444 groups with nothing were reachable only by tapping dots until a large one turned up, which is not how anyone chooses a field. "See the 40 largest" now opens a ranked list in the sheet — the slot the country and group views already use, so no new geometry and no new collision with the control rail. Sorted by population, because choosing where to go is choosing how many people are behind the decision. Tapping a row flies there and opens that group. Fly first, THEN open: opening sets `selected`, which swaps the sheet away from the list, so the other order loses the map movement behind a re-render. THE LIST EXPOSED A REAL DEFECT. The largest gaps are exactly the groups with no entry in any pool, so they had no record to carry a language name and the sheet rendered "Language: (blank) cjy" for a 60-million-speaker group. Every language is now named from the people-group dataset, creating the entry where no pool has one: 2,251 codes are named-only — the language is known, nothing was found for it anywhere. That makes "has a code entry" ~100% by construction, so it is relabelled in the build output as naming and NOT coverage. Coverage is the line under it and still reads 8,935 of 12,379, 72.2%. A metric that reaches 100% because its meaning changed is worse than no metric. Verified on the served tree: 40 rows, populations strictly descending 60,310,000 -> 974,000 so "largest" is not a claim taken on trust; tapping row 1 moves zoom 1.3 -> 4.2 and opens Han Chinese Jinyu, whose scripture_url and film_url are empty and whose Door43 list is empty — so the list is not mislabelling groups that have something. 200 sampled zero-resource groups all name their language, where the screenshot before this change shows that field blank. Pins, table, deep-link and filter probes all still pass, and the full a11y sweep passes 810 of 810 buttons across 21 leaf pages.
This commit is contained in:
parent
fc04f14a52
commit
f68ffc33ba
15 changed files with 106 additions and 38 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -162,6 +162,33 @@ function setPinFilter(v) {
|
|||
: ['==', ['get', 'tier'], 2])
|
||||
}
|
||||
|
||||
// THE RANKED GAP. "Where is the gap" is answered by the filter; "which gap is biggest"
|
||||
// is the question that actually picks a field, and nothing answered it — the 3,444 groups
|
||||
// with nothing were reachable only by tapping dots until you found a large one.
|
||||
// Sorted by population because a mobiliser choosing where to go is choosing how many
|
||||
// people are behind the decision, not how many rows are.
|
||||
const showGapList = ref(false)
|
||||
const gapList = computed(() => {
|
||||
if (!showGapList.value) return []
|
||||
const out = []
|
||||
for (let i = 0; i < pinRows.value.length; i++) {
|
||||
const r = pinRows.value[i]
|
||||
if (r[6] === 0) out.push({ i, name: r[0], lat: r[1], lng: r[2], iso3: r[3], code: r[4], pop: r[5] })
|
||||
}
|
||||
out.sort((a, b) => b.pop - a.pop)
|
||||
return out.slice(0, 40)
|
||||
})
|
||||
function openGapList() { showGapList.value = true; selected.value = null; selectedGroup.value = null }
|
||||
function closeGapList() { showGapList.value = false }
|
||||
// Fly first, THEN open: openPin sets `selected`, which swaps the sheet away from this list,
|
||||
// so doing it the other way round loses the map movement behind a re-render.
|
||||
function gotoGap(g) {
|
||||
const m = mapRef.value
|
||||
if (m) m.flyTo({ center: [g.lng, g.lat], zoom: Math.max(m.getZoom(), 4.2), duration: 900 })
|
||||
showGapList.value = false
|
||||
openPin(g.i)
|
||||
}
|
||||
|
||||
const pinTally = computed(() => {
|
||||
const t = [0, 0, 0]
|
||||
for (const r of pinRows.value) t[r[6]]++
|
||||
|
|
@ -633,6 +660,9 @@ useShadowStyles(
|
|||
Showing only <b>{{ pinFilter === 'none' ? 'groups with nothing found'
|
||||
: pinFilter === 'some' ? 'groups with some resource' : 'groups with Scripture and film' }}</b>.
|
||||
<button type="button" class="gm-kclear" @click="setPinFilter('all')">Show all {{ fmt(pinTally.total) }}</button>
|
||||
<button v-if="pinFilter === 'none'" type="button" class="gm-kclear" @click="openGapList">
|
||||
See the 40 largest →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gm-caveat">
|
||||
|
|
@ -650,10 +680,26 @@ useShadowStyles(
|
|||
<div v-if="err" class="gm-err">{{ err }}</div>
|
||||
|
||||
<!-- Tap a country: bottom sheet on mobile, corner card on desktop. -->
|
||||
<div v-if="selected" class="gm-sheet">
|
||||
<div v-if="selected || showGapList" class="gm-sheet">
|
||||
|
||||
<!-- ITEM 0 — the ranked gap: the largest groups with nothing found anywhere. -->
|
||||
<template v-if="showGapList && !selected">
|
||||
<div class="gm-sh-head">
|
||||
<span class="gm-sh-name">Largest groups with nothing found</span>
|
||||
<button type="button" class="gm-x" @click="closeGapList">Close</button>
|
||||
</div>
|
||||
<p class="gm-note" style="margin-top:0">
|
||||
{{ fmt(pinTally.none) }} people groups have no Scripture, film or Door43 resource in the
|
||||
pools checked. These are the 40 largest. Tap one to fly there and see what was checked.
|
||||
</p>
|
||||
<button v-for="g in gapList" :key="g.i" type="button" class="gm-pg gm-pg-btn" @click="gotoGap(g)">
|
||||
<span class="gm-pg-n">{{ g.name }}</span>
|
||||
<span class="gm-pg-l">{{ fmt(g.pop) }} people · {{ g.iso3 }}<code class="gm-code">{{ g.code || 'no code' }}</code></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- ITEM 2 — one people group: what exists in THEIR OWN language. -->
|
||||
<template v-if="selectedGroup">
|
||||
<template v-else-if="selectedGroup">
|
||||
<div class="gm-sh-head">
|
||||
<button type="button" class="gm-back" @click="selectedGroup = null">‹ {{ selected.country }}</button>
|
||||
<button type="button" class="gm-x" @click="closeSheet">Close</button>
|
||||
|
|
|
|||
|
|
@ -133,6 +133,25 @@ for (const [c, ee] of Object.entries(eb)) {
|
|||
ebOnly++
|
||||
}
|
||||
|
||||
// NAME EVERY LANGUAGE, even one with no resource anywhere. A code only gets a `codes`
|
||||
// entry when some pool carries something for it, so the groups with NOTHING — precisely
|
||||
// the ones the gap list ranks — were rendering a blank language name next to their code.
|
||||
// The slim dataset knows the name for every pin, so it is filled here and the entry is
|
||||
// created when it does not exist. MEASURED: this is the sheet the gap list opens on, and
|
||||
// "Language: (blank) cjy" for a 60-million-speaker group is the worst place to be vague.
|
||||
let namedOnly = 0
|
||||
for (const p of slim) {
|
||||
const c = p.LanguageCode
|
||||
const n = (p.LanguageName || '').trim()
|
||||
if (!c || c.length !== 3 || !n) continue
|
||||
if (!codes[c]) {
|
||||
codes[c] = { l: n, ck: '', un: 'ebible, arclight, door43, grn, api_bible, dbl, progress_bible' }
|
||||
namedOnly++
|
||||
} else if (!codes[c].l) {
|
||||
codes[c].l = n
|
||||
}
|
||||
}
|
||||
|
||||
const num = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null }
|
||||
const pins = []
|
||||
let joined = 0, withUrl = 0, noCoord = 0, liftedByD43 = 0
|
||||
|
|
@ -179,12 +198,15 @@ else idx.tables.push({ name: 'people-group-pins', rows: pins.length })
|
|||
writeFileSync(INDEX, JSON.stringify(idx, null, 1))
|
||||
const pct = (n) => `${((100 * n) / pins.length).toFixed(1)}%`
|
||||
console.log(`pins ${pins.length}`)
|
||||
console.log(`joined a record ${joined} (${pct(joined)})`)
|
||||
// NOT a coverage number. Since every language is now named, this is ~100% by construction
|
||||
// and means only "has a codes entry". The coverage number is the next line.
|
||||
console.log(`has a code entry ${joined} (${pct(joined)}) <- naming, NOT coverage`)
|
||||
console.log(`REAL URL ${withUrl} (${pct(withUrl)})`)
|
||||
console.log(`language codes ${Object.keys(codes).length}`)
|
||||
console.log(`door43 codes ${Object.keys(d43).length} (${d43Only} new)`)
|
||||
console.log(`ebible codes ${Object.keys(eb).length} (${ebOnly} new)`)
|
||||
console.log(`arclight codes ${Object.keys(arc).length} (${arcOnly} new)`)
|
||||
console.log(`named-only ${namedOnly} (language named, no resource in any pool checked)`)
|
||||
console.log(`lifted off 0 ${liftedByD43} (had nothing; now have a published Door43 resource)`)
|
||||
console.log(`dropped nocoord ${noCoord}`)
|
||||
console.log(`wrote ${OUT} (${(readFileSync(OUT).length / 1024).toFixed(0)} KB)`)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
2
public/assets/tables/people-group-pins.json
vendored
2
public/assets/tables/people-group-pins.json
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
10
public/doxa-maps/manifest.json
generated
10
public/doxa-maps/manifest.json
generated
|
|
@ -556,7 +556,7 @@
|
|||
"bundle": "gospel-resources",
|
||||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"output": "doxa-maps/gospel-resources/gospel-resources.js",
|
||||
"integrity": "sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht",
|
||||
"integrity": "sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK",
|
||||
"elements": [
|
||||
"gospel-resources-map"
|
||||
],
|
||||
|
|
@ -572,7 +572,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -586,7 +586,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -600,7 +600,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -614,7 +614,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-DpU1SWn4u94XMNElOTNoASob58FSPB7BTxE522CQEVO+cyFgnwXgNoD6O55tfkht\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-Toqa+/OszJnojk7ji5Ca9G5mrAIEY8DPZWyhyeNwPthz8QmcO5eiqpFyFh7HWQbK\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue