confluent/scripts/audit-lexique.js
2025-12-04 20:12:10 +08:00

459 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Script d'audit du lexique Confluent
* Vérifie que tous les mots respectent les règles linguistiques
*/
const fs = require('fs');
const path = require('path');
const LEXIQUE_DIR = path.join(__dirname, '../ancien-confluent/lexique');
// Règles phonétiques
const CONSONNES_STANDARD = ['b', 'k', 'l', 'm', 'n', 'p', 's', 't', 'v', 'z'];
const CONSONNES_RARES = ['r', 'd', 'h', 'g']; // Sons "bruts" à éviter mais tolérés
const CONSONNES_INTERDITES = ['c', 'f', 'j', 'q', 'w', 'x', 'y'];
const VOYELLES_ACTIVES = ['a', 'e', 'i', 'o', 'u'];
const VOYELLES_RESERVEES = ['y', 'é', 'è'];
// Les 16 liaisons sacrées
const LIAISONS_SACREES = [
'i', 'ie', 'ii', 'iu', // I - Agentivité
'u', 'ui', // U - Appartenance
'a', 'aa', 'ae', 'ao', // A - Relation
'o', 'oa', // O - Tension
'e', 'ei', 'ea', 'eo' // E - Dimension
];
let errors = [];
let warnings = [];
let stats = {
total_mots: 0,
racines_sacrees: 0,
racines_standards: 0,
compositions: 0,
erreurs: 0,
avertissements: 0,
consonnes_rares_utilisees: 0,
mots_avec_consonnes_rares: []
};
/**
* Vérifie si un caractère est une consonne valide
*/
function estConsonneValide(c) {
return CONSONNES_STANDARD.includes(c) || CONSONNES_RARES.includes(c);
}
/**
* Vérifie si un caractère est une consonne rare
*/
function estConsonneRare(c) {
return CONSONNES_RARES.includes(c);
}
/**
* Vérifie si un caractère est une consonne interdite
*/
function estConsonneInterdite(c) {
return CONSONNES_INTERDITES.includes(c);
}
/**
* Vérifie si un caractère est une voyelle active
*/
function estVoyelleActive(c) {
return VOYELLES_ACTIVES.includes(c);
}
/**
* Vérifie si un caractère est une voyelle réservée
*/
function estVoyelleReservee(c) {
return VOYELLES_RESERVEES.includes(c);
}
/**
* Vérifie le format d'un mot (CV pour racines, CVCVC pour verbes)
*/
function verifierFormatCV(mot, file, motFr, type) {
const chars = mot.split('');
let hasConsonneRare = false;
// Vérifier les espaces (invalides)
if (mot.includes(' ')) {
errors.push(`[${file}] "${motFr}" → "${mot}": Caractère invalide ' '`);
return false;
}
// Vérifier les caractères invalides
for (let i = 0; i < chars.length; i++) {
const c = chars[i];
if (c === '-') continue; // Tirets OK pour compositions
if (estConsonneRare(c)) {
hasConsonneRare = true;
} else if (estConsonneInterdite(c)) {
errors.push(`[${file}] "${motFr}" → "${mot}": Consonne interdite '${c}'`);
return false;
} else if (estVoyelleReservee(c)) {
errors.push(`[${file}] "${motFr}" → "${mot}": Voyelle réservée '${c}' (y, é, è interdits)`);
return false;
} else if (!estConsonneValide(c) && !estVoyelleActive(c)) {
errors.push(`[${file}] "${motFr}" → "${mot}": Caractère invalide '${c}'`);
return false;
}
}
// Compter les consonnes rares
if (hasConsonneRare) {
stats.consonnes_rares_utilisees++;
stats.mots_avec_consonnes_rares.push({mot, file, motFr});
}
// Retirer les tirets (pour les compositions)
const motSansTirets = mot.replace(/-/g, '');
if (motSansTirets.length < 2) {
errors.push(`[${file}] "${motFr}" → "${mot}": Trop court (minimum 2 caractères)`);
return false;
}
const avantDernier = motSansTirets[motSansTirets.length - 2];
const dernier = motSansTirets[motSansTirets.length - 1];
// Les VERBES finissent par CVCVC (consonne finale)
// Les RACINES finissent par CV (voyelle finale)
if (type === 'verbe' || type === 'verbe_irregulier') {
// Verbes: structure CVCVC (5 lettres, finit par consonne)
if (!estConsonneValide(dernier)) {
errors.push(`[${file}] "${motFr}" → "${mot}": Verbe doit finir par consonne (CVCVC), mais dernier caractère '${dernier}' n'est pas une consonne`);
return false;
}
} else {
// Racines et compositions: finissent par CV (voyelle)
if (!estConsonneValide(avantDernier)) {
errors.push(`[${file}] "${motFr}" → "${mot}": Doit finir par CV (consonne+voyelle), mais avant-dernier caractère '${avantDernier}' n'est pas une consonne`);
return false;
}
if (!estVoyelleActive(dernier)) {
errors.push(`[${file}] "${motFr}" → "${mot}": Doit finir par CV (consonne+voyelle), mais dernier caractère '${dernier}' n'est pas une voyelle`);
return false;
}
}
return true;
}
/**
* Vérifie si une racine est sacrée (commence par voyelle)
*/
function estRacineSacree(mot) {
return estVoyelleActive(mot[0]);
}
/**
* Charge toutes les racines du lexique
*/
function chargerToutesLesRacines() {
const racines = new Map();
const files = fs.readdirSync(LEXIQUE_DIR).filter(f => f.endsWith('.json'));
files.forEach(file => {
const filePath = path.join(LEXIQUE_DIR, file);
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (!content.dictionnaire) return;
Object.entries(content.dictionnaire).forEach(([motFr, data]) => {
if (!data.traductions) return;
data.traductions.forEach(trad => {
// Charger les racines explicites
if (trad.type === 'racine' || trad.type === 'racine_sacree') {
racines.set(trad.confluent, {
mot_fr: motFr,
forme_liee: trad.forme_liee,
file: file
});
}
// Charger les VERBES eux-mêmes comme racines (milak, kitan, etc.)
if (trad.type === 'verbe') {
racines.set(trad.confluent, {
mot_fr: motFr,
forme_liee: trad.forme_liee,
file: file,
source: 'verbe'
});
// Charger aussi la racine du verbe si elle existe
if (trad.racine) {
racines.set(trad.racine, {
mot_fr: motFr,
forme_liee: trad.forme_liee,
file: file,
source: 'verbe_racine'
});
}
}
// Charger les COMPOSITIONS comme racines valides (uraakota, etc.)
if (trad.type === 'composition' || trad.type === 'racine_sacree_composee') {
racines.set(trad.confluent, {
mot_fr: motFr,
forme_liee: trad.forme_liee || trad.confluent.slice(0, -1),
file: file,
source: 'composition'
});
}
// Charger les mots grammaticaux (négation, particules, démonstratifs, etc.) comme racines valides
if (trad.type === 'negation' || trad.type === 'particule' || trad.type === 'interrogation' || trad.type === 'demonstratif') {
racines.set(trad.confluent, {
mot_fr: motFr,
forme_liee: trad.forme_liee || trad.confluent.slice(0, -1),
file: file,
source: 'grammaire'
});
}
});
});
});
return racines;
}
/**
* Vérifie une composition
*/
function verifierComposition(trad, file, motFr, toutesLesRacines) {
if (!trad.composition) {
warnings.push(`[${file}] "${motFr}" → "${trad.confluent}": Type 'composition' mais pas de champ 'composition'`);
return;
}
if (!trad.racines || trad.racines.length === 0) {
warnings.push(`[${file}] "${motFr}" → "${trad.confluent}": Type 'composition' mais pas de champ 'racines'`);
return;
}
// Vérifier que les racines existent
trad.racines.forEach(racine => {
if (!toutesLesRacines.has(racine)) {
errors.push(`[${file}] "${motFr}" → "${trad.confluent}": Utilise racine inexistante "${racine}"`);
}
});
// Vérifier le format de composition
const parties = trad.composition.split('-');
// Vérifier les liaisons (les parties avec 1-2 lettres entre les racines)
parties.forEach((partie, index) => {
if (partie.length <= 2 && index > 0 && index < parties.length - 1) {
// C'est probablement une liaison
if (!LIAISONS_SACREES.includes(partie) && partie !== 'u' && partie !== 'a' && partie !== 'i' && partie !== 'o' && partie !== 'e') {
warnings.push(`[${file}] "${motFr}" → "${trad.confluent}": Liaison "${partie}" dans composition "${trad.composition}" n'est pas une liaison sacrée standard`);
}
}
});
}
/**
* Vérifie la forme liée
*/
function verifierFormeLiee(trad, file, motFr) {
if (trad.type !== 'racine' && trad.type !== 'racine_sacree') {
return;
}
if (!trad.forme_liee) {
warnings.push(`[${file}] "${motFr}" → "${trad.confluent}": Racine sans 'forme_liee'`);
return;
}
// La forme liée devrait être la racine sans la dernière voyelle
const attendu = trad.confluent.slice(0, -1);
if (trad.forme_liee !== attendu) {
warnings.push(`[${file}] "${motFr}" → "${trad.confluent}": forme_liee="${trad.forme_liee}" devrait être "${attendu}"`);
}
}
/**
* Audit d'un fichier
*/
function auditerFichier(file, toutesLesRacines) {
const filePath = path.join(LEXIQUE_DIR, file);
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (!content.dictionnaire) return;
Object.entries(content.dictionnaire).forEach(([motFr, data]) => {
if (!data.traductions) return;
data.traductions.forEach(trad => {
stats.total_mots++;
// Vérifier le format CV/CVCVC selon le type
if (!verifierFormatCV(trad.confluent, file, motFr, trad.type)) {
stats.erreurs++;
return;
}
// Vérifier le type de racine
if (trad.type === 'racine' || trad.type === 'racine_sacree') {
const estSacree = estRacineSacree(trad.confluent);
if (trad.type === 'racine_sacree' && !estSacree) {
errors.push(`[${file}] "${motFr}" → "${trad.confluent}": Marqué 'racine_sacree' mais commence par consonne`);
stats.erreurs++;
}
if (trad.type === 'racine' && estSacree) {
warnings.push(`[${file}] "${motFr}" → "${trad.confluent}": Marqué 'racine' mais commence par voyelle (devrait être 'racine_sacree')`);
stats.avertissements++;
}
if (estSacree) {
stats.racines_sacrees++;
} else {
stats.racines_standards++;
}
// Vérifier forme liée
verifierFormeLiee(trad, file, motFr);
}
// Vérifier les compositions
if (trad.type === 'composition' || trad.type === 'racine_sacree_composee') {
stats.compositions++;
verifierComposition(trad, file, motFr, toutesLesRacines);
}
});
});
}
/**
* Détecte les doublons de mots Confluent
*/
function detecterDoublons() {
const motsCF = new Map(); // mot -> [{file, motFr, type}]
const files = fs.readdirSync(LEXIQUE_DIR).filter(f => f.endsWith('.json'));
files.forEach(file => {
const filePath = path.join(LEXIQUE_DIR, file);
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (!content.dictionnaire) return;
Object.entries(content.dictionnaire).forEach(([motFr, data]) => {
if (!data.traductions) return;
data.traductions.forEach(trad => {
const motCF = trad.confluent;
if (!motCF) return;
if (!motsCF.has(motCF)) {
motsCF.set(motCF, []);
}
motsCF.get(motCF).push({
file: file,
motFr: motFr,
type: trad.type || 'unknown'
});
});
});
});
// Trouver les doublons
const doublons = [];
motsCF.forEach((occurrences, motCF) => {
if (occurrences.length > 1) {
doublons.push({ motCF, occurrences });
}
});
return doublons;
}
/**
* Fonction principale
*/
function main() {
console.log('🔍 Audit du lexique Confluent\n');
// Charger toutes les racines d'abord
console.log('📖 Chargement des racines...');
const toutesLesRacines = chargerToutesLesRacines();
console.log(` ${toutesLesRacines.size} racines chargées\n`);
// Auditer chaque fichier (sauf 00-grammaire pour l'audit, mais on l'a chargé pour les racines)
const files = fs.readdirSync(LEXIQUE_DIR)
.filter(f => f.endsWith('.json') && !f.startsWith('00-grammaire'));
console.log('🔎 Audit des fichiers...\n');
files.forEach(file => {
auditerFichier(file, toutesLesRacines);
});
// Détecter les doublons
console.log('🔍 Détection des doublons...\n');
const doublons = detecterDoublons();
if (doublons.length > 0) {
console.log(`⚠️ DOUBLONS DÉTECTÉS: ${doublons.length} mots Confluent apparaissent plusieurs fois\n`);
doublons.forEach(({motCF, occurrences}) => {
errors.push(`DOUBLON: "${motCF}" apparaît ${occurrences.length} fois:`);
occurrences.forEach(occ => {
errors.push(` → [${occ.file}] "${occ.motFr}" (${occ.type})`);
});
});
}
// Vérifier le ratio de consonnes rares
const ratioConsonnesRares = (stats.consonnes_rares_utilisees / stats.total_mots) * 100;
if (ratioConsonnesRares > 10) {
warnings.push(`⚠️ ATTENTION: ${ratioConsonnesRares.toFixed(1)}% des mots utilisent des consonnes rares (r, d, h, g). Recommandé: <10%`);
}
// Afficher les résultats
console.log('\n📊 STATISTIQUES:\n');
console.log(` Total de mots vérifiés: ${stats.total_mots}`);
console.log(` Racines sacrées: ${stats.racines_sacrees} (${Math.round(stats.racines_sacrees / (stats.racines_sacrees + stats.racines_standards) * 100)}%)`);
console.log(` Racines standards: ${stats.racines_standards} (${Math.round(stats.racines_standards / (stats.racines_sacrees + stats.racines_standards) * 100)}%)`);
console.log(` Compositions: ${stats.compositions}`);
console.log(` Consonnes rares utilisées: ${stats.consonnes_rares_utilisees} (${ratioConsonnesRares.toFixed(1)}%)`);
console.log(` Erreurs: ${errors.length}`);
console.log(` Avertissements: ${warnings.length}`);
if (errors.length > 0) {
console.log('\n❌ ERREURS:\n');
errors.forEach(err => console.log(` ${err}`));
}
if (warnings.length > 0) {
console.log('\n⚠ AVERTISSEMENTS:\n');
warnings.forEach(warn => console.log(` ${warn}`));
}
if (errors.length === 0 && warnings.length === 0) {
console.log('\n✅ Aucune erreur détectée ! Le lexique est conforme.\n');
} else {
console.log('');
}
// Code de sortie
process.exit(errors.length > 0 ? 1 : 0);
}
if (require.main === module) {
main();
}
module.exports = { verifierFormatCV, estRacineSacree };