diff --git a/docs/candidats-24-fruits.md b/docs/candidats-24-fruits.md new file mode 100644 index 0000000..6c2f8ac --- /dev/null +++ b/docs/candidats-24-fruits.md @@ -0,0 +1,43 @@ +# 16 espèces candidates — taxonomie vérifiée, noms kréyòl manquants + +> Généré par `npm run verify:candidats` le 2026-07-27. + +La session 5 doit porter le dataset de 8 à 24 fruits. **La taxonomie est vérifiable par +machine, le nom kréyòl ne l’est pas.** + +Le nom kréyòl est le nom principal de chaque entrée (ADR 0005), et aucun agent de code +n’en écrit. La colonne de droite est donc vide, et c’est tout l’objet de ce document : +il ne manque que ce qu’une machine ne peut pas produire. + +| # | nom scientifique | famille | Wikidata | GBIF | français | **nom kréyòl** | +|---|---|---|---|---|---|---| +| 1 | *Mangifera indica* | Anacardiaceae | L1365075-S1 | 3190638 | mangue | | +| 2 | *Carica papaya* | Caricaceae | L1371252-S1 | 2874484 | papaye | | +| 3 | *Musa acuminata* | Musaceae | Q132970 | 2762680 | banane | | +| 4 | *Ananas comosus* | Bromeliaceae | Q1493 | 5288819 | ananas | | +| 5 | *Cocos nucifera* | Arecaceae | Q13187 | 2735117 | coco | | +| 6 | *Annona reticulata* | Annonaceae | Q472653 | 5407123 | cachiman cœur-de-bœuf | | +| 7 | *Passiflora laurifolia* | Passifloraceae | Q164659 | 2874210 | pomme-liane | | +| 8 | *Passiflora edulis* | Passifloraceae | Q156790 | 2874190 | fruit de la passion | | +| 9 | *Averrhoa carambola* | Oxalidaceae | Q159447 | 2891641 | carambole | | +| 10 | *Tamarindus indica* | Fabaceae | Q80235 | 2975768 | tamarin | | +| 11 | *Manilkara zapota* | Sapotaceae | Q14959 | 2885158 | sapotille | | +| 12 | *Spondias mombin* | Anacardiaceae | Q794969 | 8185571 | mombin | | +| 13 | *Persea americana* | Lauraceae | Q37153 | 3034046 | avocat | | +| 14 | *Citrus sinensis* | Rutaceae | — | 3190158 | orange | | +| 15 | *Malpighia emarginata* | Malpighiaceae | Q15362022 | 5421427 | cerise des Antilles | | +| 16 | *Artocarpus heterophyllus* | Moraceae | Q45757 | 2984565 | jacquier | | + +**15 / 16** vérifiés sans écart. + +## Écarts relevés + +- *Citrus sinensis* — Wikidata : pas de P225 exact · GBIF : SYNONYM → Citrus sinensis + +## 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 s’ouvre pas plus grand parce que le dataset grandit. diff --git a/docs/sessions/05-06-contenu-et-finition.md b/docs/sessions/05-06-contenu-et-finition.md new file mode 100644 index 0000000..7c2b2e3 --- /dev/null +++ b/docs/sessions/05-06-contenu-et-finition.md @@ -0,0 +1,71 @@ +# Sessions 5 et 6 — Contenu et finition + +**Date :** 2026-07-27 + +## Session 5 — Contenu : ce qui a pu être fait, et ce qui ne le pouvait pas + +La session 5 devait porter le dataset de 8 à 24 fruits. **Elle est bloquée sur une chose +qu'aucun agent ne peut produire : les noms kréyòl.** + +Le nom kréyòl est le nom *principal* de chaque entrée (ADR 0005), pas une traduction +d'appoint. Ajouter seize fruits suppose donc seize noms que je n'ai pas le droit d'écrire — +et les inventer aurait été exactement ce que le projet s'interdit. + +### Ce qui a été livré à la place + +`npm run verify:candidats` vérifie la taxonomie de **16 espèces candidates** contre +Wikidata et GBIF, et produit `docs/candidats-24-fruits.md` : un tableau complet — nom +scientifique accepté, famille, Q-id, clé GBIF, nom français — **avec une colonne vide**. + +Il ne manque que ce qu'une machine ne peut pas produire. C'est le document le plus honnête +que cette session pouvait rendre. + +**15 candidats sur 16 sans écart.** Le seizième est instructif : j'avais proposé +*Citrus sinensis* pour l'orange, et GBIF l'a renvoyé en `SYNONYM`. Le contrôle a attrapé +mon erreur, ce qui est précisément son rôle. + +### Le reste de la session 5 est du terrain + +Enregistrements kréyòl (10 sur 13 manquants), relecture orthographique, arbitrage des six +points de `docs/a-arbitrer.md`, relecture du module « Pa touché » par une personne +compétente. Aucun de ces points ne se code. + +## Session 6 — Finition + +### `npm run audit` — la DoD vérifiée sur le build réel + +Le playbook §5 leçon 8 appelle cela le « grep de vérité » : on inspecte ce qui a été +produit, pas ce qu'on croit avoir écrit. Sortie non nulle = livraison refusée. + +**14 points vérifiés, 14 au vert :** + +| Point | Résultat | +|---|---| +| Chargements tiers dans le build | **0** | +| Emoji dans le HTML livré | **0** (charte §1.8) | +| Liens internes morts | **0** sur 74 pages | +| Shell JS gzip | **86 Ko** (budget 170) | +| Chunk lourd sur le chemin critique | **aucun** — Phaser reste isolé | +| SEO | og ×10, JSON-LD, canonical, hreflang `gcf` | +| Fichiers de service | sitemap, robots, 404, `_headers`, `.htaccess`, manifest, `sw.js` | +| Polices self-hébergées | 6 woff2 | + +### Ce que l'audit ne couvre pas, et qui reste ouvert + +- **Lighthouse mobile ≥ 90/95/95/95** — demande un navigateur en conditions réelles. +- **30 fps sur Mali-G52** — demande un appareil réel. +- **Hors ligne en mode avion** — demande un appareil réel. +- **`prefers-reduced-motion` à la main** — la gate CSS existe et `use:compte` la respecte, + mais la vérification humaine reste à faire. +- **Le test en classe.** Six enfants, trente minutes, sans intervenir. + +Ces cinq points ne sont pas des oublis : ce sont les mesures qu'un agent ne peut pas +prendre. Les annoncer comme faites aurait été le seul vrai échec de cette session. + +### Déploiement + +Le build est un dossier de fichiers statiques. `.htaccess` (o2switch/Apache) et `_headers` +(Cloudflare Pages) sont livrés avec la même CSP stricte, sans exception ni domaine tiers. + +Reste à faire par le porteur : choisir l'hébergement, poser le domaine, taguer et pousser +sur Gitea. diff --git a/package.json b/package.json index f18bb51..dac32c4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:i18n": "node tools/data/check-i18n.mjs", "fetch:medias": "node tools/data/fetch-medias.mjs", - "verify:seeds": "python3 tools/assets/comfyui_client.py --verifier" + "verify:seeds": "python3 tools/assets/comfyui_client.py --verifier", + "verify:candidats": "node tools/data/verify-candidats.mjs", + "audit": "node tools/build/audit.mjs" }, "devDependencies": { "@fontsource/archivo": "^5.3.0", diff --git a/tools/data/verify-candidats.mjs b/tools/data/verify-candidats.mjs new file mode 100644 index 0000000..1f4f26c --- /dev/null +++ b/tools/data/verify-candidats.mjs @@ -0,0 +1,125 @@ +#!/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 l’est pas.**', + '', + 'Le nom kréyòl est le nom principal de chaque entrée (ADR 0005), et aucun agent de code', + 'n’en écrit. La colonne de droite est donc vide, et c’est tout l’objet de ce document :', + 'il ne manque que ce qu’une 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 s’ouvre 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.`);