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>
11 KiB
11 KiB
Plan : Système de recherche par radicaux et décomposition morphologique
Problème actuel
Le traducteur Confluent→Français ne trouve pas les mots conjugués ou composés non documentés explicitement dans le lexique.
Exemple concret
- Texte :
vokan(forme conjuguée de "voki" = voix) - Lexique contient :
"confluent": "voki","forme_liee": "vok" - Résultat actuel : ❌ NOT FOUND
- Résultat attendu : ✓ Trouve "voki" via le radical "vok"
Statistiques du dernier test
- Coverage actuel : 83% (101/122 mots)
- Mots non trouvés : 21
- 11 ont des racines existantes mais formes conjuguées manquantes
- 5 sont totalement absents du lexique
- 5 pourraient être des particules grammaticales
Objectif
Augmenter le coverage de 83% à ~95% en implémentant :
- Recherche par radicaux pour les verbes conjugués
- Décomposition morphologique pour les compositions non documentées
- Index par forme_liee en plus de l'index par mot complet
Phase 1 : Analyse et mapping des patterns
1.1 Patterns de conjugaison verbale
Suffixes verbaux identifiés :
const VERBAL_SUFFIXES = [
'ak', // forme standard : mirak (voir), pasak (prendre), urak (être)
'an', // conjugaison : takan (porter), vokan (parler?)
'un', // conjugaison : kisun (transmettre), pasun (prendre?)
'is', // conjugaison : vokis (parler?)
'am', // conjugaison : sukam (forger)
'im', // conjugaison : verim (vérifier?)
'ok', // impératif : marqueur temporel
'ul', // passé? : marqueur temporel
'iran', // dérivé nominal : kisiran (enseignement/transmission?)
];
Racines de test connues :
vok→voki(voix) : formes attenduesvokan,vokiskis→kisu(transmettre) : formes attendueskisun,kisiranpas→pasa(prendre) : formes attenduespasak,pasuntak→taka(porter) : formes attenduestakan,taku
1.2 Patterns de liaisons sacrées
16 liaisons à gérer :
const SACRED_LIAISONS = {
// Agentivité
'i': 'agent',
'ie': 'agent_processus',
'ii': 'agent_répété',
'iu': 'agent_possédant',
// Appartenance
'u': 'appartenance',
'ui': 'possession_agentive',
// Relation
'a': 'relation',
'aa': 'relation_forte',
'ae': 'relation_dimensionnelle',
'ao': 'relation_tendue',
// Tension
'o': 'tension',
'oa': 'tension_relationnelle',
// Dimension
'e': 'dimension',
'ei': 'dimension_agentive',
'ea': 'dimension_relationnelle',
'eo': 'dimension_tendue'
};
1.3 Structure morphologique du Confluent
Règle générale : Racine + Liaison + Racine
sekavoki = sek + a + voki
= conseil + relation + voix
= "conseil de la voix"
Pattern de composition :
^([a-z]{2,4})(i|ie|ii|iu|u|ui|a|aa|ae|ao|o|oa|e|ei|ea|eo)([a-z]{2,4})$
Phase 2 : Implémentation
2.1 Nouveau fichier : radicalMatcher.js
Fonction principale :
/**
* Extrait tous les radicaux possibles d'un mot
* @param {string} word - Mot en confluent
* @returns {Array<{radical: string, suffix: string, confidence: number}>}
*/
function extractRadicals(word) {
const candidates = [];
// Essayer chaque suffixe verbal connu
for (const suffix of VERBAL_SUFFIXES) {
if (word.endsWith(suffix) && word.length > suffix.length + 1) {
const radical = word.slice(0, -suffix.length);
candidates.push({
radical,
suffix,
type: 'verbal',
confidence: 0.9
});
}
}
// Essayer sans suffixe (forme racine directe)
if (word.length >= 3) {
candidates.push({
radical: word,
suffix: '',
type: 'root',
confidence: 0.7
});
}
// Essayer d'enlever dernière voyelle (forme liée -> forme pleine)
// mako → mak, voki → vok
if (word.length >= 4 && 'aeiou'.includes(word[word.length - 1])) {
candidates.push({
radical: word.slice(0, -1),
suffix: word[word.length - 1],
type: 'liaison',
confidence: 0.8
});
}
return candidates.sort((a, b) => b.confidence - a.confidence);
}
2.2 Nouveau fichier : morphologicalDecomposer.js
Fonction de décomposition :
/**
* Décompose un mot composé non trouvé
* @param {string} word - Mot composé
* @returns {Array<{part1: string, liaison: string, part2: string}>}
*/
function decomposeWord(word) {
const decompositions = [];
for (const [liaison, meaning] of Object.entries(SACRED_LIAISONS)) {
const index = word.indexOf(liaison);
if (index > 0 && index < word.length - liaison.length) {
const part1 = word.substring(0, index);
const part2 = word.substring(index + liaison.length);
// Valider que les deux parties ressemblent à des racines
if (part1.length >= 2 && part2.length >= 2) {
decompositions.push({
part1,
liaison,
liaisonMeaning: meaning,
part2,
pattern: `${part1}-${liaison}-${part2}`,
confidence: calculateConfidence(part1, liaison, part2)
});
}
}
}
return decompositions.sort((a, b) => b.confidence - a.confidence);
}
function calculateConfidence(part1, liaison, part2) {
let score = 0.5; // base
// Bonus si les parties finissent/commencent par des consonnes
if (!'aeiou'.includes(part1[part1.length - 1])) score += 0.1;
if (!'aeiou'.includes(part2[0])) score += 0.1;
// Bonus si liaison courante (i, u, a sont plus fréquentes)
if (['i', 'u', 'a'].includes(liaison)) score += 0.2;
// Bonus si longueurs de parties équilibrées
const ratio = Math.min(part1.length, part2.length) / Math.max(part1.length, part2.length);
score += ratio * 0.2;
return Math.min(score, 1.0);
}
2.3 Modification : reverseIndexBuilder.js
Ajouter index par forme_liee :
function buildConfluentIndex(lexique) {
const index = {
byWord: {}, // Index existant
byRoot: {}, // NOUVEAU : index par radical
byFormeLiee: {} // NOUVEAU : index par forme_liee
};
// ... code existant pour byWord ...
// NOUVEAU : Indexer par forme_liee
for (const entry of allEntries) {
const formeLiee = entry.forme_liee || entry.racine;
if (formeLiee) {
if (!index.byFormeLiee[formeLiee]) {
index.byFormeLiee[formeLiee] = [];
}
index.byFormeLiee[formeLiee].push({
...entry,
matchType: 'forme_liee'
});
}
}
return index;
}
2.4 Modification : confluentToFrench.js
Nouvelle logique de recherche en cascade :
function findWordWithRadicals(word, confluentIndex) {
// 1. Recherche exacte (existant)
if (confluentIndex.byWord[word]) {
return {
...confluentIndex.byWord[word][0],
matchType: 'exact',
confidence: 1.0
};
}
// 2. NOUVEAU : Recherche par radicaux verbaux
const radicals = extractRadicals(word);
for (const {radical, suffix, type, confidence} of radicals) {
// Chercher dans l'index par forme_liee
if (confluentIndex.byFormeLiee[radical]) {
return {
...confluentIndex.byFormeLiee[radical][0],
matchType: 'radical',
originalWord: word,
radical,
suffix,
suffixType: type,
confidence
};
}
}
// 3. NOUVEAU : Décomposition morphologique
const decompositions = decomposeWord(word);
for (const decomp of decompositions) {
const part1Match = findWordWithRadicals(decomp.part1, confluentIndex);
const part2Match = findWordWithRadicals(decomp.part2, confluentIndex);
if (part1Match && part2Match) {
return {
matchType: 'composition_inferred',
originalWord: word,
composition: `${decomp.part1}-${decomp.liaison}-${decomp.part2}`,
parts: {
part1: part1Match,
liaison: decomp.liaison,
liaisonMeaning: decomp.liaisonMeaning,
part2: part2Match
},
confidence: decomp.confidence * 0.7 // Pénalité pour inférence
};
}
}
// 4. Vraiment inconnu
return null;
}
Phase 3 : Tests et validation
3.1 Cas de test prioritaires
Verbes conjugués :
const testCases = [
{ word: 'vokan', expectedRoot: 'vok', expectedMeaning: 'voix' },
{ word: 'vokis', expectedRoot: 'vok', expectedMeaning: 'voix' },
{ word: 'kisiran', expectedRoot: 'kis', expectedMeaning: 'transmettre' },
{ word: 'pasun', expectedRoot: 'pas', expectedMeaning: 'prendre' },
{ word: 'taku', expectedRoot: 'tak', expectedMeaning: 'porter' },
];
Compositions inférées :
const compositionTests = [
{
word: 'sukamori',
expected: { part1: 'suk', liaison: 'a', part2: 'mori' },
// Si 'mori' existe dans le lexique
},
{
word: 'uraal',
expected: { part1: 'ur', liaison: 'aa', part2: 'al' }
}
];
3.2 Métriques de succès
Objectif : Passer de 83% à 95% de coverage
Métriques à suivre :
- Coverage total (% de mots trouvés)
- Précision (% de correspondances correctes)
- Type de match (exact / radical / composition)
- Niveau de confiance moyen
Phase 4 : Fichiers à créer/modifier
Fichiers à CRÉER :
ConfluentTranslator/radicalMatcher.jsConfluentTranslator/morphologicalDecomposer.jsConfluentTranslator/tests/radicalMatching.test.js
Fichiers à MODIFIER :
-
ConfluentTranslator/reverseIndexBuilder.js- Ajouter index
byFormeLiee - Ajouter index
byRoot
- Ajouter index
-
ConfluentTranslator/confluentToFrench.js- Importer
radicalMatcheretmorphologicalDecomposer - Modifier
translateToken()pour utiliser recherche en cascade - Ajouter champs de métadonnées (matchType, confidence, etc.)
- Importer
-
ConfluentTranslator/server.js- Aucune modification nécessaire (compatibilité backwards)
Phase 5 : Améliorations futures
5.1 Machine Learning léger
- Apprendre les patterns de suffixes depuis le corpus
- Scorer automatiquement la confiance des décompositions
5.2 Support des nombres
- Charger
22-nombres.jsondans le lexique - Gérer les nombres composés (ex: "diku tolu iko" = 25)
5.3 Particules grammaticales
- Documenter
ve,eol, et autres particules manquantes - Créer un fichier
particules.json
5.4 Interface de validation
- UI pour valider/corriger les correspondances inférées
- Export des nouvelles correspondances pour enrichir le lexique
Ordre d'implémentation recommandé
- ✅ Créer
radicalMatcher.jsavec fonctionextractRadicals() - ✅ Modifier
reverseIndexBuilder.jspour ajouterbyFormeLiee - ✅ Modifier
confluentToFrench.jspour recherche par radical - ✅ Tester avec les 11 cas de verbes conjugués
- ✅ Créer
morphologicalDecomposer.jsavec fonctiondecomposeWord() - ✅ Intégrer décomposition dans
confluentToFrench.js - ✅ Tester avec les compositions inférées
- ✅ Ajouter support des nombres (
22-nombres.json) - 🔄 Valider sur le texte complet (objectif: 95% coverage)
Impact attendu
Sur le texte de test (122 tokens)
Avant :
- Coverage: 83% (101/122)
- Inconnus: 21
Après (estimation) :
- Coverage: ~95% (116/122)
- Inconnus: ~6
- Avec confiance: ~110/122
- Inférés: ~6/122
Bénéfices
- ✅ Meilleure compréhension des textes réels
- ✅ Découverte automatique de nouvelles formes
- ✅ Base pour enrichissement du lexique
- ✅ System plus robuste et adaptatif