Files
ki-fwi/tools/data/build-credits.mjs
T
OKIandClaude Opus 5 945a524b44 feat(session-0): cadre juridique, porte de validation et dataset vérifié
Session 0 du plan — aucun code applicatif.

Cadre
- 5 ADR : licences, Phaser 4 hors budget initial, frontière Svelte/Phaser,
  filières A/B des assets, kréyòl langue principale (norme gwadloupéyenne).
- LICENSE (AGPL-3.0-or-later, texte canonique gnu.org), LICENSE-CONTENT
  (CC BY-SA 4.0). Le jeu est une œuvre collective : aucun compositing sur un
  média SA ou NC, contrainte vérifiée par script.
- Audit des 7 sources avec endpoints réellement appelés. TRAMIL vérifié en
  direct : « all rights reserved » et page de mentions légales en 404.
  iNaturalist mesuré à 93 % de CC BY-NC sur un échantillon de 305 photos.

Outillage (tools/data/, hors bundle, zéro appel réseau au runtime)
- verify:taxa, ingest:commons, build:dataset, validate:data, test:porte,
  build:credits, check:audio.
- validate.mjs est la porte exigée par le brief §2 : le build échoue si une
  épreuve s'appuie sur un média non validé, non photographique ou de filière B,
  sur une attribution sans auteur, sur un compositing SA/NC, ou sur une entrée
  publiée sans relecture kréyòl. test:porte prouve les 9 refus + 1 témoin.

Données
- 13 entrées (8 fruits + 5 plantes du module Pa touché) vérifiées contre
  Wikidata ET GBIF : 13/13 ACCEPTED, 0 divergence avec la graine.
- 130 candidats Commons sous licence libre, 39 pré-inscrits, 0 validé —
  aucun agent ne peut valider un média.

Écarts signalés, non corrigés en silence
- Le champ danger de la graine a deux formes (note vs resume) : normalisé.
- 3 identifiants divergent du nom kréyòl au-delà des accents : non tranchés.
- comestible:false sur akée et pomme-cajou : sous-estimation prudente assumée.
- Le mancenillier n'a aucune photographie moderne dans ses 3 premiers
  candidats — l'entrée la plus critique est la moins bien servie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 23:47:27 -04:00

132 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* build-credits.mjs — génère CREDITS.md depuis les données.
*
* Le brief §3 exige un CREDITS.md « généré depuis les données ». Un fichier de crédits
* tenu à la main se désynchronise le jour où l'on remplace une photo, et une attribution
* fausse est une violation de licence. Ce fichier n'est donc jamais édité : il se rejoue.
*
* Les médias sous clause non commerciale sont isolés dans leur propre section, comme
* l'ADR 0001 l'impose.
*/
import { readFile, readdir, 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 DATA = join(ROOT, 'src', 'lib', 'data', 'fruits');
const LIBELLE = {
'CC0-1.0': 'CC0 1.0 (domaine public volontaire)',
PD: 'domaine public',
'CC-BY-2.0': 'CC BY 2.0',
'CC-BY-3.0': 'CC BY 3.0',
'CC-BY-4.0': 'CC BY 4.0',
'CC-BY-SA-2.0': 'CC BY-SA 2.0',
'CC-BY-SA-2.5': 'CC BY-SA 2.5',
'CC-BY-SA-3.0': 'CC BY-SA 3.0',
'CC-BY-SA-4.0': 'CC BY-SA 4.0',
'CC-BY-NC-4.0': 'CC BY-NC 4.0',
'CC-BY-NC-SA-4.0': 'CC BY-NC-SA 4.0',
'OKI-CC-BY-SA-4.0': 'ORGANISATION KA INTERNATIONALE, CC BY-SA 4.0'
};
const echapper = (s) => String(s ?? '—').replace(/\|/g, '\\|').replace(/\n/g, ' ');
async function main() {
const fichiers = (await readdir(DATA)).filter((f) => f.endsWith('.json')).sort();
const entrees = [];
for (const f of fichiers) entrees.push(JSON.parse(await readFile(join(DATA, f), 'utf8')));
const tous = entrees.flatMap((e) => e.medias.map((m) => ({ ...m, entree: e })));
const nc = tous.filter((m) => m.usage_non_commercial);
const sa = tous.filter((m) => m.partage_identique);
const l = [
'# Crédits — KI FWI SA YÉ ?',
'',
'> **Fichier généré.** Ne pas éditer à la main : `npm run build:credits`.',
`> Dernière génération : ${new Date().toISOString().slice(0, 10)}.`,
'',
'## Ce que couvre ce fichier',
'',
'Chaque média embarqué dans le jeu ou dans lencyclopédie est listé ci-dessous avec son',
'auteur, sa licence et le lien vers sa page de description dorigine. Rien nest',
'« libre de droit » : chaque ligne est une obligation contractuelle.',
'',
'Le code du projet est sous **AGPL-3.0-or-later** (`LICENSE`). Les textes rédigés par',
'ORGANISATION KA INTERNATIONALE sont sous **CC BY-SA 4.0** (`LICENSE-CONTENT`). Les médias',
'tiers restent sous leur licence propre : le jeu est une **œuvre collective**, pas une',
'œuvre dérivée — voir `docs/adr/0001-licences.md`.',
'',
`## Chiffres`,
'',
`- ${entrees.length} entrées · ${tous.length} médias`,
`- ${tous.filter((m) => m.valide).length} médias validés par un humain`,
`- ${sa.length} médias en partage à lidentique (CC BY-SA) — **aucun compositing autorisé**`,
`- ${nc.length} médias sous clause non commerciale (CC BY-NC)`,
''
];
if (nc.length) {
l.push(
'## ⚠ Médias sous clause non commerciale',
'',
'La présence de ces médias engage le projet à ne **jamais** être distribué à titre',
'commercial — ni vente, ni version payante, ni licence à un éditeur. Décision prise et',
'documentée dans `docs/adr/0001-licences.md`. Retirer ces médias est la seule façon de',
'revenir en arrière.',
'',
'| entrée | média | auteur | licence | source |',
'|---|---|---|---|---|'
);
for (const m of nc)
l.push(
`| \`${m.entree.id}\` | ${echapper(m.id)} | ${echapper(m.auteur)} | ${LIBELLE[m.licence] ?? m.licence} | [page](${m.url_description}) |`
);
l.push('');
}
l.push('## Médias par entrée', '');
for (const e of entrees) {
l.push(`### ${e.noms.gcf} — *${e.taxon.scientifique}* (\`${e.id}\`)`, '');
l.push(
`Taxonomie vérifiée le ${e.taxon.verifie_le} contre [Wikidata ${e.taxon.wikidata}](https://www.wikidata.org/wiki/${e.taxon.wikidata})`,
`et [GBIF ${e.taxon.gbif}](https://www.gbif.org/species/${e.taxon.gbif}).`,
''
);
if (!e.medias.length) {
l.push('_Aucun média._', '');
continue;
}
l.push('| média | vue | auteur | licence | validé | page dorigine |');
l.push('|---|---|---|---|---|---|');
for (const m of e.medias)
l.push(
`| ${echapper(m.id)} | ${m.vue ?? '—'} | ${echapper(m.auteur)} | ${LIBELLE[m.licence] ?? m.licence} | ${m.valide ? `oui (${echapper(m.valide_par)})` : 'non'} | [voir](${m.url_description}) |`
);
l.push('');
}
l.push(
'## Sources factuelles',
'',
'- **Wikidata** — identifiants et taxonomie (CC0). https://www.wikidata.org',
'- **GBIF** — noms scientifiques acceptés et clés dusage. https://www.gbif.org',
'- **Wikimedia Commons** — photographies. https://commons.wikimedia.org',
'',
'Voir `SOURCES.md` pour le détail des régimes de licence et `docs/audit-sources.md`',
'pour laudit complet, TRAMIL et CIRAD compris.',
''
);
await writeFile(join(ROOT, 'CREDITS.md'), l.join('\n'));
console.log(`✔ CREDITS.md — ${entrees.length} entrées, ${tous.length} médias, ${nc.length} sous clause NC.`);
}
main().catch((e) => {
console.error('✘', e.stack ?? e.message);
process.exit(1);
});