confluent/ConfluentTranslator/morphologicalDecomposer.js
StillHammer 889cd24319 Amélioration système de traduction: normalisation, lexique et couverture
Corrections majeures:
- Normalisation ligatures (œ→oe, æ→ae) pour éviter fragmentation tokens
- Normalisation complète lexique (clés + synonymes) sans accents
- Correction faux positif "dansent"→"dans" (longueur radical ≥5)

Enrichissement lexique (+212 entrées):
- Verbes: battre (pulum), penser/réfléchir (umis), voler (aliuk)
- Mots grammaticaux: nous (tanu), possessifs (sa/mon→na), démonstratifs (ce→ko)
- Temporels: hier/avant (at), demain/après (ok), autour (no)
- Formes conjuguées ajoutées pour manger, battre, penser

Améliorations techniques:
- Lemmatisation verbale améliorée (radical ≥5 lettres)
- Système normalizeText() dans lexiqueLoader.js
- Liaisons sacrées pour compositions culturelles

Note: Problème connu de lemmatisation à investiguer (formes fléchies non trouvées)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 22:37:31 +08:00

211 lines
6.9 KiB
JavaScript

// morphologicalDecomposer.js
// Système de décomposition morphologique pour le Confluent
// Permet de décomposer les mots composés selon le pattern Racine-Liaison-Racine
const lexique = require('../data/lexique.json');
// ============================================================================
// CHARGEMENT DYNAMIQUE DES LIAISONS DEPUIS LE LEXIQUE
// ============================================================================
/**
* Charge les liaisons sacrées depuis le lexique JSON
* @returns {Object} Dictionnaire des liaisons {liaison: {domaine, concept, description}}
*/
function loadSacredLiaisons() {
const liaisons = {};
if (lexique.liaisons) {
for (const [liaison, data] of Object.entries(lexique.liaisons)) {
liaisons[liaison] = {
domaine: data.domaine,
concept: data.concept,
description: data.description,
base: data.base
};
}
}
return liaisons;
}
// Charger les liaisons depuis le lexique
const SACRED_LIAISONS = loadSacredLiaisons();
console.log(`[morphologicalDecomposer] Chargé ${Object.keys(SACRED_LIAISONS).length} liaisons sacrées depuis lexique.json`);
// ============================================================================
// VALIDATION DES RACINES
// ============================================================================
/**
* Vérifie si une partie ressemble à une racine valide du Confluent
* @param {string} part - Partie à valider
* @param {Object} reverseIndex - Index de recherche (optionnel)
* @returns {{isValid: boolean, found: boolean, confidence: number}}
*/
function validateRoot(part, reverseIndex = null) {
// Critères de base
if (part.length < 2) {
return { isValid: false, found: false, confidence: 0 };
}
let confidence = 0.5; // base
let found = false;
// 1. Vérifier si la partie existe dans l'index de recherche
if (reverseIndex) {
// Recherche exacte
if (reverseIndex.byWord && reverseIndex.byWord[part]) {
found = true;
confidence = 1.0;
return { isValid: true, found: true, confidence };
}
// Recherche par forme liée (enlever dernière voyelle)
if (reverseIndex.byFormeLiee) {
const formeLiee = part.endsWith('a') || part.endsWith('e') ||
part.endsWith('i') || part.endsWith('o') ||
part.endsWith('u')
? part.slice(0, -1)
: part;
if (reverseIndex.byFormeLiee[formeLiee]) {
found = true;
confidence = 0.95;
return { isValid: true, found: true, confidence };
}
}
}
// 2. Heuristiques morphologiques du Confluent
// Les racines finissent généralement par CV (consonne + voyelle)
const vowels = 'aeiou';
const lastChar = part[part.length - 1];
const secondLastChar = part.length > 1 ? part[part.length - 2] : '';
// Finit par voyelle = probable racine
if (vowels.includes(lastChar)) {
confidence += 0.2;
// Pattern CV en fin = très probable
if (secondLastChar && !vowels.includes(secondLastChar)) {
confidence += 0.2;
}
}
// 3. Longueur typique (3-4 caractères pour racines)
if (part.length >= 3 && part.length <= 5) {
confidence += 0.1;
}
return {
isValid: confidence >= 0.5,
found: false,
confidence: Math.min(confidence, 1.0)
};
}
// ============================================================================
// DÉCOMPOSITION MORPHOLOGIQUE
// ============================================================================
/**
* Décompose un mot composé non trouvé
* @param {string} word - Mot composé en confluent
* @param {Object} reverseIndex - Index de recherche (optionnel, pour validation)
* @returns {Array<{part1: string, liaison: string, liaisonMeaning: string, part2: string, pattern: string, confidence: number, part1Valid: boolean, part2Valid: boolean}>}
*/
function decomposeWord(word, reverseIndex = null) {
const decompositions = [];
// Trier les liaisons par longueur décroissante (essayer 'aa' avant 'a')
const liaisonsSorted = Object.keys(SACRED_LIAISONS).sort((a, b) => b.length - a.length);
// Essayer chaque liaison sacrée
for (const liaison of liaisonsSorted) {
const index = word.indexOf(liaison);
// La liaison doit être au milieu du mot, pas au début ni à la fin
if (index > 0 && index < word.length - liaison.length) {
const part1 = word.substring(0, index);
const part2 = word.substring(index + liaison.length);
// Valider les deux parties
const part1Validation = validateRoot(part1, reverseIndex);
const part2Validation = validateRoot(part2, reverseIndex);
// Les deux parties doivent ressembler à des racines
if (part1Validation.isValid && part2Validation.isValid) {
const liaisonData = SACRED_LIAISONS[liaison];
decompositions.push({
part1,
part1Found: part1Validation.found,
part1Confidence: part1Validation.confidence,
liaison,
liaisonDomaine: liaisonData.domaine,
liaisonConcept: liaisonData.concept,
liaisonDescription: liaisonData.description,
part2,
part2Found: part2Validation.found,
part2Confidence: part2Validation.confidence,
pattern: `${part1}-${liaison}-${part2}`,
confidence: calculateConfidence(
part1,
liaison,
part2,
part1Validation,
part2Validation
)
});
}
}
}
// Trier par confiance décroissante
return decompositions.sort((a, b) => b.confidence - a.confidence);
}
/**
* Calcule la confiance d'une décomposition
* @param {string} part1 - Première partie (racine)
* @param {string} liaison - Liaison sacrée
* @param {string} part2 - Deuxième partie (racine)
* @param {Object} part1Validation - Résultat de validation de part1
* @param {Object} part2Validation - Résultat de validation de part2
* @returns {number} Score de confiance entre 0 et 1
*/
function calculateConfidence(part1, liaison, part2, part1Validation, part2Validation) {
let score = 0.3; // base plus conservative
// BONUS MAJEUR : Si les deux parties sont trouvées dans le lexique
if (part1Validation.found && part2Validation.found) {
score = 0.95; // Très haute confiance !
} else if (part1Validation.found || part2Validation.found) {
score = 0.75; // Une partie trouvée = bonne confiance
} else {
// Utiliser la confiance des validations heuristiques
score = (part1Validation.confidence + part2Validation.confidence) / 2;
}
// Bonus si liaison courante (i, u, a sont plus fréquentes)
if (['i', 'u', 'a'].includes(liaison)) {
score += 0.05;
} else if (['aa', 'ii'].includes(liaison)) {
score += 0.03;
}
// Bonus si longueurs de parties équilibrées
const ratio = Math.min(part1.length, part2.length) / Math.max(part1.length, part2.length);
score += ratio * 0.05;
return Math.min(score, 1.0);
}
module.exports = {
decomposeWord,
SACRED_LIAISONS,
validateRoot
};