spotters: logo coverage script (Wikidata logo/icon, multi-language infobox, website icon fallback), cheaper planes operator queries, refresh adds logos for new orgs
All checks were successful
Deploy production / deploy (push) Successful in 1m9s

This commit is contained in:
2026-10-25 13:54:27 +00:00
parent 7d6cee26a8
commit 5e4cd83748
8 changed files with 577 additions and 19 deletions

View File

@@ -306,28 +306,54 @@ async function planes() {
for (let depth = 0; id && depth < 6; depth++, id = parent.get(id) ?? null)
for (const [cc, n] of m) add(scores, id, cc, n);
}
// Wikidata operators (P137) and their countries.
// Wikidata operators (P137) and their countries, as two cheap queries (the joined
// version times out on the public endpoint).
const byQid = new Map(types.map((t) => [t.wikidata, t.id]));
let ops = 0;
let failed = 0;
const opsOf = new Map<string, Set<string>>();
const fail = (e: unknown) => {
failed++;
log(`planes: operator chunk failed (${String(e).slice(0, 80)})`);
return [];
};
for (const c of chunks(
[...byQid.keys()].filter((q): q is string => !!q),
100
200
)) {
// A failing chunk (query timeout) only loses its operator bonus; registrations stay.
const res = await sparql<{ item: string; iso: string; n: string }>(`
SELECT ?item ?iso (COUNT(DISTINCT ?op) AS ?n) WHERE {
VALUES ?item { ${c.map((q) => `wd:${q}`).join(' ')} }
?item wdt:P137 ?op . ?op wdt:P17 ?country . ?country wdt:P297 ?iso .
} GROUP BY ?item ?iso`).catch((e: unknown) => {
failed++;
log(`planes: operator chunk failed (${String(e).slice(0, 80)})`);
return [];
});
const res = await sparql<{ item: string; op: string }>(`
SELECT ?item ?op WHERE { VALUES ?item { ${c.map((q) => `wd:${q}`).join(' ')} } ?item wdt:P137 ?op . }`).catch(
fail
);
for (const r of res) {
const id = byQid.get(r.item.split('/').at(-1)!);
if (!id) continue;
add(scores, id, r.iso.toLowerCase(), 25 * Math.min(Number(r.n) || 1, 6));
const item = r.item.split('/').at(-1)!;
const set = opsOf.get(item) ?? new Set<string>();
set.add(r.op.split('/').at(-1)!);
opsOf.set(item, set);
}
}
const isoOf = new Map<string, Set<string>>();
for (const c of chunks([...new Set([...opsOf.values()].flatMap((s) => [...s]))], 300)) {
const res = await sparql<{ op: string; iso: string }>(`
SELECT ?op ?iso WHERE { VALUES ?op { ${c.map((q) => `wd:${q}`).join(' ')} } ?op wdt:P17 ?country . ?country wdt:P297 ?iso . }`).catch(
fail
);
for (const r of res) {
const op = r.op.split('/').at(-1)!;
const set = isoOf.get(op) ?? new Set<string>();
set.add(r.iso.toLowerCase());
isoOf.set(op, set);
}
}
for (const [item, opSet] of opsOf) {
const id = byQid.get(item);
if (!id) continue;
const perCountry = new Map<string, number>();
for (const op of opSet)
for (const cc of isoOf.get(op) ?? []) perCountry.set(cc, (perCountry.get(cc) ?? 0) + 1);
for (const [cc, n] of perCountry) {
add(scores, id, cc, 25 * Math.min(n, 6));
ops++;
}
}
@@ -355,7 +381,7 @@ async function wildlife() {
us: 'United States',
gb: 'United Kingdom',
cz: 'Czech Republic',
tr: 'Turkey',
tr: 'Türkiye',
kr: 'South Korea'
}[cc] ?? dn.of(cc.toUpperCase())!;
const found = await inat<{