confluent/ConfluentTranslator/reverseIndexBuilder.js
StillHammer e8d17ab0d5 Implement radical lookup system for Confluent translator (83% → 92% coverage)
Major features:
- Radical-based word matching for conjugated verbs
- Morphological decomposition for compound words
- Multi-index search (byWord + byFormeLiee)
- Cascade search strategy with confidence scoring

New files:
- ConfluentTranslator/radicalMatcher.js: Extract radicals from conjugated forms
- ConfluentTranslator/morphologicalDecomposer.js: Decompose compound words
- ConfluentTranslator/plans/radical-lookup-system.md: Implementation plan
- ConfluentTranslator/test-results-radical-system.md: Test results and analysis
- ancien-confluent/lexique/00-grammaire.json: Grammar particles
- ancien-confluent/lexique/lowercase-confluent.js: Lowercase utility

Modified files:
- ConfluentTranslator/reverseIndexBuilder.js: Added byFormeLiee index
- ConfluentTranslator/confluentToFrench.js: Cascade search with radicals
- Multiple lexique JSON files: Enhanced entries with forme_liee

Test results:
- Before: 83% coverage (101/122 tokens)
- After: 92% coverage (112/122 tokens)
- Improvement: +9 percentage points

Remaining work to reach 95%+:
- Add missing particles (ve, eol)
- Enrich VERBAL_SUFFIXES (aran, vis)
- Document missing words (tiru, kala, vulu)

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 22:24:56 +08:00

148 lines
4.6 KiB
JavaScript

/**
* Reverse Index Builder - Génère un dictionnaire Confluent → Français
*
* Structure générée:
* {
* "Akoazana": { francais: "Faucon Chasseur", type: "nom_propre", ... },
* "zanatori": { francais: "chasseur", type: "composition", ... },
* "aki": { francais: "faucon", type: "racine_sacree", forme_liee: "ak" },
* "va": { francais: "[SUJET]", type: "particule" }
* }
*/
// Les particules sont maintenant dans le lexique 00-grammaire.json
// Elles seront chargées automatiquement par buildReverseIndex()
/**
* Construit l'index inversé à partir d'un lexique
* @param {Object} lexique - Lexique au format {dictionnaire: {...}, meta: {...}}
* @returns {Object} - Index inversé Confluent → Français avec multiples index
*/
function buildReverseIndex(lexique) {
const reverseIndex = {
byWord: {}, // Index par mot complet (existant)
byFormeLiee: {}, // NOUVEAU : Index par forme_liee (radicaux)
};
if (!lexique || !lexique.dictionnaire) {
return reverseIndex;
}
// 2. Parcourir toutes les entrées du lexique
for (const [motFrancais, entry] of Object.entries(lexique.dictionnaire)) {
if (!entry.traductions || entry.traductions.length === 0) continue;
// Traiter chaque traduction
entry.traductions.forEach(trad => {
const motConfluent = trad.confluent;
if (!motConfluent) return;
// IMPORTANT: En Confluent, pas de distinction majuscule/minuscule
// Toujours stocker en lowercase
const motConfluentLower = motConfluent.toLowerCase();
const entryData = {
francais: motFrancais,
type: trad.type || 'inconnu',
forme_liee: trad.forme_liee || null,
composition: trad.composition || null,
racines: trad.racines || [],
domaine: trad.domaine || null,
note: trad.note || null
};
// Ajouter les synonymes français si présents
if (entry.synonymes_fr && entry.synonymes_fr.length > 0) {
entryData.synonymes_fr = [...entry.synonymes_fr];
}
// INDEX PAR MOT COMPLET (byWord)
if (reverseIndex.byWord[motConfluentLower]) {
// Vérifier si c'est un doublon
if (reverseIndex.byWord[motConfluentLower].francais !== motFrancais) {
if (!reverseIndex.byWord[motConfluentLower].synonymes_fr) {
reverseIndex.byWord[motConfluentLower].synonymes_fr = [reverseIndex.byWord[motConfluentLower].francais];
}
if (!reverseIndex.byWord[motConfluentLower].synonymes_fr.includes(motFrancais)) {
reverseIndex.byWord[motConfluentLower].synonymes_fr.push(motFrancais);
}
}
} else {
reverseIndex.byWord[motConfluentLower] = entryData;
}
// NOUVEAU : INDEX PAR FORME_LIEE (byFormeLiee)
const formeLiee = trad.forme_liee;
if (formeLiee) {
const formeLieeLower = formeLiee.toLowerCase();
if (!reverseIndex.byFormeLiee[formeLieeLower]) {
reverseIndex.byFormeLiee[formeLieeLower] = [];
}
reverseIndex.byFormeLiee[formeLieeLower].push({
...entryData,
matchType: 'forme_liee'
});
}
});
}
return reverseIndex;
}
/**
* Génère les statistiques de l'index inversé
* @param {Object} reverseIndex - Index inversé (avec byWord et byFormeLiee)
* @returns {Object} - Statistiques
*/
function getReverseIndexStats(reverseIndex) {
const stats = {
total: 0,
particules: 0,
racines_sacrees: 0,
racines: 0,
compositions: 0,
noms_propres: 0,
verbes: 0,
autres: 0,
by_forme_liee: 0 // NOUVEAU : nombre d'entrées indexées par forme_liee
};
// Travailler sur byWord pour les stats principales (compatibilité)
const indexToCount = reverseIndex.byWord || reverseIndex;
for (const [conf, entry] of Object.entries(indexToCount)) {
stats.total++;
const type = entry.type;
if (type === 'particule' || type === 'marqueur_temps' || type === 'negation' ||
type === 'quantite' || type === 'interrogation' || type === 'demonstratif') {
stats.particules++;
} else if (type === 'racine_sacree') {
stats.racines_sacrees++;
} else if (type === 'racine') {
stats.racines++;
} else if (type === 'composition') {
stats.compositions++;
} else if (type === 'nom_propre') {
stats.noms_propres++;
} else if (type.includes('verbe')) {
stats.verbes++;
} else {
stats.autres++;
}
}
// Compter les entrées par forme_liee
if (reverseIndex.byFormeLiee) {
stats.by_forme_liee = Object.keys(reverseIndex.byFormeLiee).length;
}
return stats;
}
module.exports = {
buildReverseIndex,
getReverseIndexStats
};