Files

126 lines
5.1 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* verify-candidats.mjs — vérifie la taxonomie des espèces candidates à l'extension.
*
* La session 5 doit porter le dataset de 8 à 24 fruits. Ce que je peux faire : vérifier
* les noms scientifiques, les familles, les Q-id et les clés GBIF contre les référentiels.
*
* Ce que je ne peux PAS faire : donner leur nom kréyòl. Le nom kréyòl est le nom
* PRINCIPAL de l'entrée (ADR 0005) et aucun agent de code n'en écrit. Le tableau produit
* comporte donc une colonne vide, et c'est le point du document.
*
* Sortie : docs/candidats-24-fruits.md
*/
import { writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const UA = 'ki-fwi-dataset/0.1 (https://o-k-i.net ; projet educatif ORGANISATION KA INTERNATIONALE)';
/** Espèces couramment rencontrées aux Antilles. Le nom français sert de repère, pas de référence. */
const CANDIDATS = [
['Mangifera indica', 'mangue'],
['Carica papaya', 'papaye'],
['Musa acuminata', 'banane'],
['Ananas comosus', 'ananas'],
['Cocos nucifera', 'coco'],
['Annona reticulata', 'cachiman cœur-de-bœuf'],
['Passiflora laurifolia', 'pomme-liane'],
['Passiflora edulis', 'fruit de la passion'],
['Averrhoa carambola', 'carambole'],
['Tamarindus indica', 'tamarin'],
['Manilkara zapota', 'sapotille'],
['Spondias mombin', 'mombin'],
['Persea americana', 'avocat'],
['Citrus sinensis', 'orange'],
['Malpighia emarginata', 'cerise des Antilles'],
['Artocarpus heterophyllus', 'jacquier']
];
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
async function getJson(url, params) {
const u = new URL(url);
for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
const res = await fetch(u, { headers: { 'User-Agent': UA, Accept: 'application/json' } });
if (!res.ok) throw new Error(`${res.status}`);
return res.json();
}
async function wikidata(noms) {
const values = noms.map((n) => JSON.stringify(n)).join(' ');
const query = `SELECT ?taxon ?name ?fam WHERE {
VALUES ?name { ${values} }
?taxon wdt:P225 ?name .
OPTIONAL { ?taxon wdt:P171+ ?f . ?f wdt:P105 wd:Q35409 ; wdt:P225 ?fam . }
}`;
const u = new URL('https://query.wikidata.org/sparql');
u.searchParams.set('query', query);
const res = await fetch(u, { headers: { 'User-Agent': UA, Accept: 'application/sparql-results+json' } });
const json = await res.json();
const m = new Map();
for (const b of json.results.bindings) {
m.set(b.name.value, { qid: b.taxon.value.split('/').pop(), famille: b.fam?.value ?? null });
}
return m;
}
const resultats = [];
const wd = await wikidata(CANDIDATS.map(([s]) => s));
for (const [sci, fr] of CANDIDATS) {
process.stdout.write(`→ ${sci}… `);
const g = await getJson('https://api.gbif.org/v1/species/match', { name: sci, strict: 'false' });
const w = wd.get(sci) ?? null;
const ecarts = [];
if (!w) ecarts.push('Wikidata : pas de P225 exact');
if (g.status !== 'ACCEPTED') ecarts.push(`GBIF : ${g.status}${g.canonicalName}`);
if (w?.famille && g.family && w.famille !== g.family) ecarts.push(`familles divergentes : ${w.famille} / ${g.family}`);
console.log(g.status ?? 'AUCUN', ecarts.length ? `⚠ ${ecarts.join(' · ')}` : '');
resultats.push({ sci, fr, w, g, ecarts });
await pause(250);
}
const l = [
'# 16 espèces candidates — taxonomie vérifiée, noms kréyòl manquants',
'',
`> Généré par \`npm run verify:candidats\` le ${new Date().toISOString().slice(0, 10)}.`,
'',
'La session 5 doit porter le dataset de 8 à 24 fruits. **La taxonomie est vérifiable par',
'machine, le nom kréyòl ne lest pas.**',
'',
'Le nom kréyòl est le nom principal de chaque entrée (ADR 0005), et aucun agent de code',
'nen écrit. La colonne de droite est donc vide, et cest tout lobjet de ce document :',
'il ne manque que ce quune machine ne peut pas produire.',
'',
'| # | nom scientifique | famille | Wikidata | GBIF | français | **nom kréyòl** |',
'|---|---|---|---|---|---|---|'
];
resultats.forEach((r, i) => {
l.push(
`| ${i + 1} | *${r.g.canonicalName ?? r.sci}* | ${r.g.family ?? '—'} | ${r.w?.qid ?? '—'} | ${r.g.usageKey ?? '—'} | ${r.fr} | |`
);
});
const avecEcart = resultats.filter((r) => r.ecarts.length);
l.push('', `**${resultats.length - avecEcart.length} / ${resultats.length}** vérifiés sans écart.`, '');
if (avecEcart.length) {
l.push('## Écarts relevés', '');
for (const r of avecEcart) l.push(`- *${r.sci}* — ${r.ecarts.join(' · ')}`);
l.push('');
}
l.push(
'## Comment compléter',
'',
'Remplir la colonne « nom kréyòl » dans la norme gwadloupéyenne, puis ajouter les entrées',
'à `fruits.seed.json` et rejouer `npm run verify:taxa && npm run build:dataset`.',
'',
'Chaque entrée ajoutée sortira en `orthographe.statut: "a_relire"` et',
'`valide: false` — la porte ne souvre pas plus grand parce que le dataset grandit.',
''
);
await writeFile(join(ROOT, 'docs', 'candidats-24-fruits.md'), l.join('\n'));
console.log(`\n✔ docs/candidats-24-fruits.md — ${resultats.length} candidats, ${avecEcart.length} avec écart.`);