n8n-nodes-docx-filler 2.0.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -197,6 +197,32 @@ function validateFieldValue(fieldType, value) {
|
|
|
197
197
|
const cleanValue = value.trim();
|
|
198
198
|
return field.valuePatterns.some(pattern => pattern.test(cleanValue));
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Détecte le type de document à partir des paragraphes
|
|
202
|
+
*/
|
|
203
|
+
function detectDocumentType(paragraphs) {
|
|
204
|
+
for (const p of paragraphs) {
|
|
205
|
+
const text = p.textNormalized;
|
|
206
|
+
// AE - Acte d'Engagement (vérifier en premier car plus spécifique)
|
|
207
|
+
if (text.includes('acte d\'engagement') || text.includes('acte d engagement') ||
|
|
208
|
+
text.includes('engagement du titulaire')) {
|
|
209
|
+
return 'AE';
|
|
210
|
+
}
|
|
211
|
+
// DC1 - Lettre de candidature
|
|
212
|
+
if (text.includes('dc1') || text.includes('lettre de candidature')) {
|
|
213
|
+
return 'DC1';
|
|
214
|
+
}
|
|
215
|
+
// DC2 - Déclaration du candidat
|
|
216
|
+
if (text.includes('dc2') || text.includes('déclaration du candidat individuel')) {
|
|
217
|
+
return 'DC2';
|
|
218
|
+
}
|
|
219
|
+
// ATTRI1 - Attribution
|
|
220
|
+
if (text.includes('attri1')) {
|
|
221
|
+
return 'ATTRI1';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return 'unknown';
|
|
225
|
+
}
|
|
200
226
|
/**
|
|
201
227
|
* Extrait les données d'un document source
|
|
202
228
|
*/
|
|
@@ -209,12 +235,21 @@ function extractSourceData(paragraphs) {
|
|
|
209
235
|
for (let i = 0; i < paragraphs.length; i++) {
|
|
210
236
|
const p = paragraphs[i];
|
|
211
237
|
const text = p.text.trim();
|
|
212
|
-
// Détecter le type de document
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
238
|
+
// Détecter le type de document (priorité au premier match explicite)
|
|
239
|
+
if (documentType === 'unknown') {
|
|
240
|
+
if (p.textNormalized.includes('acte d\'engagement') || p.textNormalized.includes('acte d engagement') ||
|
|
241
|
+
(p.textNormalized.includes('ae') && p.textNormalized.includes('formulaire'))) {
|
|
242
|
+
documentType = 'AE';
|
|
243
|
+
}
|
|
244
|
+
else if (p.textNormalized.includes('dc1') || p.textNormalized.includes('lettre de candidature')) {
|
|
245
|
+
documentType = 'DC1';
|
|
246
|
+
}
|
|
247
|
+
else if (p.textNormalized.includes('dc2') || p.textNormalized.includes('déclaration du candidat')) {
|
|
248
|
+
documentType = 'DC2';
|
|
249
|
+
}
|
|
250
|
+
else if (p.textNormalized.includes('attri1') || p.textNormalized.includes('attribution')) {
|
|
251
|
+
documentType = 'ATTRI1';
|
|
252
|
+
}
|
|
218
253
|
}
|
|
219
254
|
// Extraire les checkboxes
|
|
220
255
|
if (p.hasCheckbox) {
|
|
@@ -393,14 +428,78 @@ function validateDocxBuffer(buffer, source) {
|
|
|
393
428
|
throw new Error(`Erreur lors de la lecture du fichier "${source}": ${zipError.message}`);
|
|
394
429
|
}
|
|
395
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Trouve tous les binaires DOCX disponibles dans l'item
|
|
433
|
+
*/
|
|
434
|
+
function findAllDocxBinaries(item) {
|
|
435
|
+
if (!item.binary)
|
|
436
|
+
return [];
|
|
437
|
+
const results = [];
|
|
438
|
+
for (const [key, binaryData] of Object.entries(item.binary)) {
|
|
439
|
+
// Vérifier le type MIME ou l'extension
|
|
440
|
+
const isDocx = (binaryData.mimeType && DOCX_MIME_TYPES.includes(binaryData.mimeType)) ||
|
|
441
|
+
(binaryData.fileName && binaryData.fileName.toLowerCase().endsWith('.docx'));
|
|
442
|
+
if (isDocx) {
|
|
443
|
+
results.push({ key, data: binaryData });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return results;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Trouve le premier binaire DOCX disponible dans l'item
|
|
450
|
+
*/
|
|
451
|
+
function findFirstDocxBinary(item) {
|
|
452
|
+
const all = findAllDocxBinaries(item);
|
|
453
|
+
return all.length > 0 ? all[0] : null;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Trouve le deuxième binaire DOCX disponible dans l'item
|
|
457
|
+
*/
|
|
458
|
+
function findSecondDocxBinary(item) {
|
|
459
|
+
const all = findAllDocxBinaries(item);
|
|
460
|
+
return all.length > 1 ? all[1] : null;
|
|
461
|
+
}
|
|
396
462
|
/**
|
|
397
463
|
* Charge un document DOCX depuis différentes sources
|
|
398
464
|
*/
|
|
399
465
|
async function getDocumentBuffer(context, itemIndex, inputType, inputValue, items, documentName) {
|
|
400
466
|
let buffer = null;
|
|
401
467
|
let source = inputValue;
|
|
402
|
-
// Mode binaire
|
|
403
|
-
if (inputType === '
|
|
468
|
+
// Mode "Premier DOCX" - trouve automatiquement le premier binaire DOCX
|
|
469
|
+
if (inputType === 'firstDocx') {
|
|
470
|
+
const found = findFirstDocxBinary(items[itemIndex]);
|
|
471
|
+
if (found) {
|
|
472
|
+
buffer = await context.helpers.getBinaryDataBuffer(itemIndex, found.key);
|
|
473
|
+
source = found.data.fileName || found.key;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
const availableBinaries = items[itemIndex].binary
|
|
477
|
+
? Object.keys(items[itemIndex].binary)
|
|
478
|
+
: [];
|
|
479
|
+
throw new Error(`Aucun fichier DOCX trouvé dans les binaires pour ${documentName}. ` +
|
|
480
|
+
`Binaires disponibles: ${availableBinaries.length > 0 ? availableBinaries.join(', ') : 'aucun'}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Mode "Second DOCX" - trouve le deuxième binaire DOCX
|
|
484
|
+
if (inputType === 'secondDocx') {
|
|
485
|
+
const found = findSecondDocxBinary(items[itemIndex]);
|
|
486
|
+
if (found) {
|
|
487
|
+
buffer = await context.helpers.getBinaryDataBuffer(itemIndex, found.key);
|
|
488
|
+
source = found.data.fileName || found.key;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
const allDocx = findAllDocxBinaries(items[itemIndex]);
|
|
492
|
+
if (allDocx.length === 0) {
|
|
493
|
+
throw new Error(`Aucun fichier DOCX trouvé pour ${documentName}.`);
|
|
494
|
+
}
|
|
495
|
+
else if (allDocx.length === 1) {
|
|
496
|
+
throw new Error(`Un seul fichier DOCX trouvé (${allDocx[0].data.fileName || allDocx[0].key}). ` +
|
|
497
|
+
`Le mode "Second DOCX" nécessite au moins 2 fichiers DOCX.`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Mode binaire (avec nom spécifique)
|
|
502
|
+
if (!buffer && (inputType === 'binary' || inputType === 'auto')) {
|
|
404
503
|
if (items[itemIndex].binary && items[itemIndex].binary[inputValue]) {
|
|
405
504
|
const binaryData = items[itemIndex].binary[inputValue];
|
|
406
505
|
if (binaryData.mimeType && !DOCX_MIME_TYPES.includes(binaryData.mimeType)) {
|
|
@@ -466,6 +565,122 @@ async function getDocumentBuffer(context, itemIndex, inputType, inputValue, item
|
|
|
466
565
|
validateDocxBuffer(buffer, source);
|
|
467
566
|
return buffer;
|
|
468
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* Génère un rapport de vérification basique (sans LLM)
|
|
570
|
+
*/
|
|
571
|
+
function generateBasicVerificationReport(sourceData, templateDocType, filledFields, fillPositions) {
|
|
572
|
+
const fieldDetails = [];
|
|
573
|
+
const warnings = [];
|
|
574
|
+
const suggestions = [];
|
|
575
|
+
// Analyser les champs remplis
|
|
576
|
+
for (const field of sourceData.fields) {
|
|
577
|
+
const wasFilled = filledFields.some(f => f.includes(field.fieldType));
|
|
578
|
+
fieldDetails.push({
|
|
579
|
+
fieldType: field.fieldType,
|
|
580
|
+
label: field.label,
|
|
581
|
+
filledValue: field.value,
|
|
582
|
+
confidence: field.confidence >= 0.9 ? 'high' : field.confidence >= 0.7 ? 'medium' : 'low',
|
|
583
|
+
doubt: field.confidence < 0.7 ? 'Confiance faible dans l\'extraction' : null,
|
|
584
|
+
wasEmpty: !wasFilled,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
// Identifier les champs non remplis
|
|
588
|
+
const filledTypes = new Set(sourceData.fields.map(f => f.fieldType));
|
|
589
|
+
const expectedFields = ['nom_commercial', 'siret', 'adresse', 'email', 'telephone'];
|
|
590
|
+
const unfilledFields = expectedFields.filter(f => !filledTypes.has(f));
|
|
591
|
+
if (unfilledFields.length > 0) {
|
|
592
|
+
warnings.push(`Champs attendus non trouvés: ${unfilledFields.join(', ')}`);
|
|
593
|
+
}
|
|
594
|
+
// Vérifier la compatibilité
|
|
595
|
+
const compatiblePairs = [
|
|
596
|
+
['DC1', 'DC1'], ['DC1', 'DC2'], ['DC2', 'DC1'], ['DC2', 'DC2'],
|
|
597
|
+
['AE', 'AE'], ['AE', 'DC1'], ['AE', 'DC2'],
|
|
598
|
+
['DC1', 'AE'], ['DC2', 'AE'],
|
|
599
|
+
];
|
|
600
|
+
const isCompatible = compatiblePairs.some(([s, t]) => sourceData.documentType.includes(s) && templateDocType.includes(t)) || sourceData.documentType === 'unknown' || templateDocType === 'unknown';
|
|
601
|
+
if (!isCompatible) {
|
|
602
|
+
warnings.push(`Types de documents potentiellement incompatibles: ${sourceData.documentType} → ${templateDocType}`);
|
|
603
|
+
}
|
|
604
|
+
// Suggestions
|
|
605
|
+
if (sourceData.fields.length < 3) {
|
|
606
|
+
suggestions.push('Peu de champs extraits. Vérifiez que le document source contient bien les données entreprise.');
|
|
607
|
+
}
|
|
608
|
+
if (fillPositions.length > sourceData.fields.length) {
|
|
609
|
+
suggestions.push('Le template contient plus de champs que le source. Certains resteront vides.');
|
|
610
|
+
}
|
|
611
|
+
const highConfCount = fieldDetails.filter(f => f.confidence === 'high').length;
|
|
612
|
+
const medConfCount = fieldDetails.filter(f => f.confidence === 'medium').length;
|
|
613
|
+
const lowConfCount = fieldDetails.filter(f => f.confidence === 'low').length;
|
|
614
|
+
return {
|
|
615
|
+
documentsCompatible: isCompatible,
|
|
616
|
+
compatibilityScore: isCompatible ? (sourceData.fields.length > 3 ? 85 : 60) : 30,
|
|
617
|
+
compatibilityReason: isCompatible
|
|
618
|
+
? 'Documents compatibles pour le mapping des données entreprise'
|
|
619
|
+
: 'Types de documents potentiellement incompatibles',
|
|
620
|
+
sourceDocType: sourceData.documentType,
|
|
621
|
+
templateDocType,
|
|
622
|
+
fieldsAnalysis: {
|
|
623
|
+
total: fillPositions.length,
|
|
624
|
+
filled: filledFields.length,
|
|
625
|
+
unfilled: fillPositions.length - filledFields.length,
|
|
626
|
+
highConfidence: highConfCount,
|
|
627
|
+
mediumConfidence: medConfCount,
|
|
628
|
+
lowConfidence: lowConfCount,
|
|
629
|
+
},
|
|
630
|
+
fieldDetails,
|
|
631
|
+
checkboxesAnalysis: {
|
|
632
|
+
total: sourceData.checkboxes.length,
|
|
633
|
+
checked: sourceData.checkboxes.filter(c => c.isChecked).length,
|
|
634
|
+
unchecked: sourceData.checkboxes.filter(c => !c.isChecked).length,
|
|
635
|
+
},
|
|
636
|
+
warnings,
|
|
637
|
+
suggestions,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Effectue une vérification avec le LLM
|
|
642
|
+
*/
|
|
643
|
+
async function performLLMVerification(llm, sourceText, templateText, sourceDocType, templateDocType, mappedFields) {
|
|
644
|
+
var _a, _b, _c, _d, _e, _f;
|
|
645
|
+
const prompt = VERIFICATION_PROMPT
|
|
646
|
+
.replace('{source_type}', sourceDocType)
|
|
647
|
+
.replace('{template_type}', templateDocType)
|
|
648
|
+
.replace('{source_text}', sourceText.substring(0, 3000))
|
|
649
|
+
.replace('{template_text}', templateText.substring(0, 3000))
|
|
650
|
+
.replace('{mapped_fields}', JSON.stringify(mappedFields, null, 2));
|
|
651
|
+
try {
|
|
652
|
+
const response = await llm.invoke(prompt);
|
|
653
|
+
const responseText = typeof response === 'string'
|
|
654
|
+
? response
|
|
655
|
+
: response.content || response.text || JSON.stringify(response);
|
|
656
|
+
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
657
|
+
if (jsonMatch) {
|
|
658
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
659
|
+
return {
|
|
660
|
+
documentsCompatible: (_a = parsed.documentsCompatible) !== null && _a !== void 0 ? _a : true,
|
|
661
|
+
compatibilityScore: (_b = parsed.compatibilityScore) !== null && _b !== void 0 ? _b : 70,
|
|
662
|
+
compatibilityReason: (_c = parsed.compatibilityReason) !== null && _c !== void 0 ? _c : 'Analyse IA effectuée',
|
|
663
|
+
warnings: (_d = parsed.warnings) !== null && _d !== void 0 ? _d : [],
|
|
664
|
+
suggestions: (_e = parsed.suggestions) !== null && _e !== void 0 ? _e : [],
|
|
665
|
+
fieldDetails: ((_f = parsed.fieldVerifications) !== null && _f !== void 0 ? _f : []).map((fv) => ({
|
|
666
|
+
fieldType: fv.fieldType,
|
|
667
|
+
label: fv.label,
|
|
668
|
+
filledValue: fv.filledValue,
|
|
669
|
+
confidence: fv.confidence || 'medium',
|
|
670
|
+
doubt: fv.doubt,
|
|
671
|
+
wasEmpty: false,
|
|
672
|
+
})),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
// En cas d'erreur, retourner un rapport partiel
|
|
678
|
+
return {
|
|
679
|
+
warnings: [`Erreur lors de la vérification IA: ${error.message}`],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return {};
|
|
683
|
+
}
|
|
469
684
|
/**
|
|
470
685
|
* Convertit le document en texte structuré pour le LLM
|
|
471
686
|
*/
|
|
@@ -545,6 +760,68 @@ RÈGLES:
|
|
|
545
760
|
1. Fais le mapping SÉMANTIQUE (Raison sociale = Dénomination = Nom commercial)
|
|
546
761
|
2. Utilise les [EMPTY] du template comme positions cibles
|
|
547
762
|
3. Le templateIndex doit correspondre à un paragraphe [EMPTY] qui suit un [LABEL]`;
|
|
763
|
+
const VERIFICATION_PROMPT = `Tu es un expert en vérification de documents administratifs français (DC1, DC2, AE, ATTRI1).
|
|
764
|
+
|
|
765
|
+
CONTEXTE:
|
|
766
|
+
- Document SOURCE (contient les données de l'entreprise): {source_type}
|
|
767
|
+
- Document TEMPLATE (formulaire à remplir): {template_type}
|
|
768
|
+
|
|
769
|
+
DOCUMENT SOURCE (avec données):
|
|
770
|
+
"""
|
|
771
|
+
{source_text}
|
|
772
|
+
"""
|
|
773
|
+
|
|
774
|
+
DOCUMENT TEMPLATE (à remplir):
|
|
775
|
+
"""
|
|
776
|
+
{template_text}
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
DONNÉES EXTRAITES ET MAPPÉES:
|
|
780
|
+
{mapped_fields}
|
|
781
|
+
|
|
782
|
+
ANALYSE DEMANDÉE:
|
|
783
|
+
1. Les deux documents sont-ils compatibles pour un mapping de données entreprise ?
|
|
784
|
+
- Un DC1/DC2/AE rempli peut servir de source pour un autre DC1/DC2/AE vide
|
|
785
|
+
- Les données d'entreprise (SIRET, adresse, nom, etc.) sont transférables entre formulaires similaires
|
|
786
|
+
|
|
787
|
+
2. Pour chaque champ mappé, évalue:
|
|
788
|
+
- La CONFIANCE du mapping (high/medium/low)
|
|
789
|
+
- Un éventuel DOUTE si le mapping semble incorrect
|
|
790
|
+
|
|
791
|
+
3. Identifie:
|
|
792
|
+
- Les champs qui DEVRAIENT être remplis mais ne le sont pas
|
|
793
|
+
- Les WARNINGS (incohérences potentielles)
|
|
794
|
+
- Les SUGGESTIONS d'amélioration
|
|
795
|
+
|
|
796
|
+
IMPORTANT - Distingue bien:
|
|
797
|
+
- Données de l'ACHETEUR (nom du marché, références, dates limites) = NE PAS mapper
|
|
798
|
+
- Données du CANDIDAT (entreprise: SIRET, adresse, nom commercial, etc.) = À mapper
|
|
799
|
+
|
|
800
|
+
Retourne UNIQUEMENT un JSON valide:
|
|
801
|
+
{
|
|
802
|
+
"documentsCompatible": true,
|
|
803
|
+
"compatibilityScore": 85,
|
|
804
|
+
"compatibilityReason": "Les deux documents sont des formulaires de marchés publics partageant les mêmes champs entreprise",
|
|
805
|
+
"fieldVerifications": [
|
|
806
|
+
{
|
|
807
|
+
"fieldType": "siret",
|
|
808
|
+
"label": "Numéro SIRET",
|
|
809
|
+
"filledValue": "89198692900018",
|
|
810
|
+
"confidence": "high",
|
|
811
|
+
"doubt": null
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
"fieldType": "adresse",
|
|
815
|
+
"label": "Adresse postale",
|
|
816
|
+
"filledValue": "13 rue exemple",
|
|
817
|
+
"confidence": "medium",
|
|
818
|
+
"doubt": "L'adresse semble incomplète (code postal manquant)"
|
|
819
|
+
}
|
|
820
|
+
],
|
|
821
|
+
"unfilledFields": ["capital", "code_naf"],
|
|
822
|
+
"warnings": ["Le SIRET source ne correspond pas au format attendu"],
|
|
823
|
+
"suggestions": ["Vérifier manuellement le champ TVA intracommunautaire"]
|
|
824
|
+
}`;
|
|
548
825
|
// ============================================================================
|
|
549
826
|
// Main Node Class
|
|
550
827
|
// ============================================================================
|
|
@@ -600,19 +877,28 @@ class DocxFillerAI {
|
|
|
600
877
|
type: 'options',
|
|
601
878
|
displayOptions: { show: { operation: ['fill'] } },
|
|
602
879
|
options: [
|
|
603
|
-
{ name: 'Binary', value: '
|
|
880
|
+
{ name: 'First DOCX Binary', value: 'firstDocx', description: 'Premier fichier DOCX trouvé (idéal après Merge)' },
|
|
881
|
+
{ name: 'Second DOCX Binary', value: 'secondDocx', description: 'Deuxième fichier DOCX trouvé' },
|
|
882
|
+
{ name: 'Binary Property', value: 'binary', description: 'Propriété binaire spécifique' },
|
|
604
883
|
{ name: 'File Path', value: 'path', description: 'Chemin fichier' },
|
|
605
|
-
{ name: 'Auto-Detect', value: 'auto', description: 'Détection auto' },
|
|
884
|
+
{ name: 'Auto-Detect', value: 'auto', description: 'Détection auto (nom requis)' },
|
|
606
885
|
],
|
|
607
|
-
default: '
|
|
886
|
+
default: 'firstDocx',
|
|
608
887
|
},
|
|
609
888
|
{
|
|
610
889
|
displayName: 'Source Document',
|
|
611
890
|
name: 'sourceDocument',
|
|
612
891
|
type: 'string',
|
|
613
892
|
default: '',
|
|
614
|
-
required:
|
|
615
|
-
displayOptions: {
|
|
893
|
+
required: false,
|
|
894
|
+
displayOptions: {
|
|
895
|
+
show: {
|
|
896
|
+
operation: ['fill'],
|
|
897
|
+
},
|
|
898
|
+
hide: {
|
|
899
|
+
sourceInputType: ['firstDocx', 'secondDocx'],
|
|
900
|
+
},
|
|
901
|
+
},
|
|
616
902
|
description: 'Document source (propriété binaire, chemin, ou base64)',
|
|
617
903
|
placeholder: 'sourceDoc ou /path/to/source.docx',
|
|
618
904
|
},
|
|
@@ -623,19 +909,28 @@ class DocxFillerAI {
|
|
|
623
909
|
type: 'options',
|
|
624
910
|
displayOptions: { show: { operation: ['fill'] } },
|
|
625
911
|
options: [
|
|
626
|
-
{ name: 'Binary', value: '
|
|
912
|
+
{ name: 'Second DOCX Binary', value: 'secondDocx', description: 'Deuxième fichier DOCX trouvé (idéal après Merge)' },
|
|
913
|
+
{ name: 'First DOCX Binary', value: 'firstDocx', description: 'Premier fichier DOCX trouvé' },
|
|
914
|
+
{ name: 'Binary Property', value: 'binary', description: 'Propriété binaire spécifique' },
|
|
627
915
|
{ name: 'File Path', value: 'path', description: 'Chemin fichier' },
|
|
628
|
-
{ name: 'Auto-Detect', value: 'auto', description: 'Détection auto' },
|
|
916
|
+
{ name: 'Auto-Detect', value: 'auto', description: 'Détection auto (nom requis)' },
|
|
629
917
|
],
|
|
630
|
-
default: '
|
|
918
|
+
default: 'secondDocx',
|
|
631
919
|
},
|
|
632
920
|
{
|
|
633
921
|
displayName: 'Template Document',
|
|
634
922
|
name: 'templateDocument',
|
|
635
923
|
type: 'string',
|
|
636
924
|
default: '',
|
|
637
|
-
required:
|
|
638
|
-
displayOptions: {
|
|
925
|
+
required: false,
|
|
926
|
+
displayOptions: {
|
|
927
|
+
show: {
|
|
928
|
+
operation: ['fill'],
|
|
929
|
+
},
|
|
930
|
+
hide: {
|
|
931
|
+
templateInputType: ['firstDocx', 'secondDocx'],
|
|
932
|
+
},
|
|
933
|
+
},
|
|
639
934
|
description: 'Template à remplir (propriété binaire, chemin, ou base64)',
|
|
640
935
|
placeholder: 'templateDoc ou /path/to/template.docx',
|
|
641
936
|
},
|
|
@@ -656,6 +951,14 @@ class DocxFillerAI {
|
|
|
656
951
|
displayOptions: { show: { operation: ['fill'] } },
|
|
657
952
|
description: 'Utiliser le LLM connecté pour un mapping plus intelligent (plus lent)',
|
|
658
953
|
},
|
|
954
|
+
{
|
|
955
|
+
displayName: 'AI Verification',
|
|
956
|
+
name: 'enableVerification',
|
|
957
|
+
type: 'boolean',
|
|
958
|
+
default: true,
|
|
959
|
+
displayOptions: { show: { operation: ['fill'] } },
|
|
960
|
+
description: 'Activer la vérification IA pour valider le mapping et détecter les doutes',
|
|
961
|
+
},
|
|
659
962
|
// Extract
|
|
660
963
|
{
|
|
661
964
|
displayName: 'Document Input Type',
|
|
@@ -663,19 +966,23 @@ class DocxFillerAI {
|
|
|
663
966
|
type: 'options',
|
|
664
967
|
displayOptions: { show: { operation: ['extract'] } },
|
|
665
968
|
options: [
|
|
666
|
-
{ name: 'Binary', value: '
|
|
667
|
-
{ name: '
|
|
668
|
-
{ name: '
|
|
969
|
+
{ name: 'First DOCX Binary', value: 'firstDocx', description: 'Premier fichier DOCX trouvé' },
|
|
970
|
+
{ name: 'Binary Property', value: 'binary', description: 'Propriété binaire spécifique' },
|
|
971
|
+
{ name: 'File Path', value: 'path', description: 'Chemin fichier' },
|
|
972
|
+
{ name: 'Auto-Detect', value: 'auto', description: 'Détection auto' },
|
|
669
973
|
],
|
|
670
|
-
default: '
|
|
974
|
+
default: 'firstDocx',
|
|
671
975
|
},
|
|
672
976
|
{
|
|
673
977
|
displayName: 'Document',
|
|
674
978
|
name: 'extractDocument',
|
|
675
979
|
type: 'string',
|
|
676
980
|
default: '',
|
|
677
|
-
required:
|
|
678
|
-
displayOptions: {
|
|
981
|
+
required: false,
|
|
982
|
+
displayOptions: {
|
|
983
|
+
show: { operation: ['extract'] },
|
|
984
|
+
hide: { extractInputType: ['firstDocx'] },
|
|
985
|
+
},
|
|
679
986
|
description: 'Document à analyser',
|
|
680
987
|
},
|
|
681
988
|
],
|
|
@@ -698,11 +1005,12 @@ class DocxFillerAI {
|
|
|
698
1005
|
try {
|
|
699
1006
|
if (operation === 'fill') {
|
|
700
1007
|
const sourceInputType = this.getNodeParameter('sourceInputType', i);
|
|
701
|
-
const sourceValue = this.getNodeParameter('sourceDocument', i);
|
|
1008
|
+
const sourceValue = this.getNodeParameter('sourceDocument', i, '');
|
|
702
1009
|
const templateInputType = this.getNodeParameter('templateInputType', i);
|
|
703
|
-
const templateValue = this.getNodeParameter('templateDocument', i);
|
|
1010
|
+
const templateValue = this.getNodeParameter('templateDocument', i, '');
|
|
704
1011
|
const outputProperty = this.getNodeParameter('outputProperty', i);
|
|
705
1012
|
const useLLM = this.getNodeParameter('useLLM', i);
|
|
1013
|
+
const enableVerification = this.getNodeParameter('enableVerification', i);
|
|
706
1014
|
// Charger les documents
|
|
707
1015
|
const sourceBuffer = await getDocumentBuffer(this, i, sourceInputType, sourceValue, items, 'source');
|
|
708
1016
|
const templateBuffer = await getDocumentBuffer(this, i, templateInputType, templateValue, items, 'template');
|
|
@@ -739,17 +1047,14 @@ class DocxFillerAI {
|
|
|
739
1047
|
// Fallback au mode sans LLM
|
|
740
1048
|
sourceData = extractSourceData(sourceParagraphs);
|
|
741
1049
|
}
|
|
742
|
-
// Mapping avec LLM
|
|
1050
|
+
// Mapping avec LLM (pour amélioration future)
|
|
743
1051
|
const fillPrompt = FILL_PROMPT
|
|
744
1052
|
.replace('{source_data}', JSON.stringify(sourceData, null, 2))
|
|
745
1053
|
.replace('{template_text}', templateStructured);
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
// Parser les instructions et appliquer
|
|
751
|
-
// ... (logique similaire à avant)
|
|
752
|
-
// Pour simplifier, on utilise aussi la logique standard
|
|
1054
|
+
// Note: LLM mapping response peut être utilisé pour un mapping plus intelligent
|
|
1055
|
+
// Pour l'instant, on utilise la logique standard de mapping par patterns
|
|
1056
|
+
await llm.invoke(fillPrompt);
|
|
1057
|
+
// Utiliser la logique standard de mapping
|
|
753
1058
|
const fillPositions = findFillPositions(templateParagraphs);
|
|
754
1059
|
const result = fillTemplateXml(templateXml, templateParagraphs, sourceData, fillPositions);
|
|
755
1060
|
templateXml = result.xml;
|
|
@@ -771,22 +1076,87 @@ class DocxFillerAI {
|
|
|
771
1076
|
type: 'nodebuffer',
|
|
772
1077
|
compression: 'DEFLATE',
|
|
773
1078
|
});
|
|
1079
|
+
// Détecter le type du template (document de sortie)
|
|
1080
|
+
const templateDocType = detectDocumentType(templateParagraphs);
|
|
1081
|
+
const finalDocType = templateDocType !== 'unknown' ? templateDocType : sourceData.documentType;
|
|
1082
|
+
// Générer le rapport de vérification
|
|
1083
|
+
const fillPositionsForReport = findFillPositions(templateParagraphs);
|
|
1084
|
+
let verificationReport = generateBasicVerificationReport(sourceData, finalDocType, filledFields, fillPositionsForReport);
|
|
1085
|
+
// Si vérification IA activée et LLM disponible, enrichir le rapport
|
|
1086
|
+
if (enableVerification && llm) {
|
|
1087
|
+
const sourceStructuredText = docxToStructuredText(sourceParagraphs);
|
|
1088
|
+
const templateStructuredText = docxToStructuredText(templateParagraphs);
|
|
1089
|
+
const llmVerification = await performLLMVerification(llm, sourceStructuredText, templateStructuredText, sourceData.documentType, finalDocType, filledFields);
|
|
1090
|
+
// Fusionner les résultats LLM avec le rapport basique
|
|
1091
|
+
if (llmVerification.documentsCompatible !== undefined) {
|
|
1092
|
+
verificationReport.documentsCompatible = llmVerification.documentsCompatible;
|
|
1093
|
+
}
|
|
1094
|
+
if (llmVerification.compatibilityScore !== undefined) {
|
|
1095
|
+
verificationReport.compatibilityScore = llmVerification.compatibilityScore;
|
|
1096
|
+
}
|
|
1097
|
+
if (llmVerification.compatibilityReason) {
|
|
1098
|
+
verificationReport.compatibilityReason = llmVerification.compatibilityReason;
|
|
1099
|
+
}
|
|
1100
|
+
if (llmVerification.warnings && llmVerification.warnings.length > 0) {
|
|
1101
|
+
verificationReport.warnings = [...verificationReport.warnings, ...llmVerification.warnings];
|
|
1102
|
+
}
|
|
1103
|
+
if (llmVerification.suggestions && llmVerification.suggestions.length > 0) {
|
|
1104
|
+
verificationReport.suggestions = [...verificationReport.suggestions, ...llmVerification.suggestions];
|
|
1105
|
+
}
|
|
1106
|
+
if (llmVerification.fieldDetails && llmVerification.fieldDetails.length > 0) {
|
|
1107
|
+
// Enrichir avec les détails LLM (doutes, confiance)
|
|
1108
|
+
for (const llmField of llmVerification.fieldDetails) {
|
|
1109
|
+
const existingField = verificationReport.fieldDetails.find(f => f.fieldType === llmField.fieldType);
|
|
1110
|
+
if (existingField && llmField.doubt) {
|
|
1111
|
+
existingField.doubt = llmField.doubt;
|
|
1112
|
+
existingField.confidence = llmField.confidence;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
774
1117
|
// Générer le nom de fichier
|
|
775
1118
|
const date = new Date().toISOString().split('T')[0];
|
|
776
1119
|
const companyName = (sourceData.companyName || 'Document').replace(/[^a-zA-Z0-9]/g, '_');
|
|
777
|
-
const outputFilename = `${companyName}_${
|
|
1120
|
+
const outputFilename = `${companyName}_${finalDocType}_${date}.docx`;
|
|
778
1121
|
const binaryData = await this.helpers.prepareBinaryData(outputBuffer, outputFilename, DOCX_MIME_TYPE);
|
|
1122
|
+
// Extraire les champs avec doutes pour un accès facile
|
|
1123
|
+
const fieldsWithDoubts = verificationReport.fieldDetails
|
|
1124
|
+
.filter(f => f.doubt !== null)
|
|
1125
|
+
.map(f => ({ field: f.fieldType, label: f.label, doubt: f.doubt }));
|
|
779
1126
|
returnData.push({
|
|
780
1127
|
json: {
|
|
781
1128
|
success: true,
|
|
782
1129
|
filename: outputFilename,
|
|
783
|
-
documentType:
|
|
1130
|
+
documentType: finalDocType,
|
|
1131
|
+
sourceDocumentType: sourceData.documentType,
|
|
784
1132
|
companyName: sourceData.companyName,
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1133
|
+
// Résumé du remplissage
|
|
1134
|
+
summary: {
|
|
1135
|
+
filledFields: filledFields.length,
|
|
1136
|
+
unfilledFields: verificationReport.fieldsAnalysis.unfilled,
|
|
1137
|
+
modifiedCheckboxes,
|
|
1138
|
+
extractedFieldsCount: sourceData.fields.length,
|
|
1139
|
+
compatibilityScore: verificationReport.compatibilityScore,
|
|
1140
|
+
},
|
|
1141
|
+
// Détails des champs remplis
|
|
1142
|
+
filledFieldsList: filledFields,
|
|
1143
|
+
// Rapport de vérification complet
|
|
1144
|
+
verification: {
|
|
1145
|
+
documentsCompatible: verificationReport.documentsCompatible,
|
|
1146
|
+
compatibilityScore: verificationReport.compatibilityScore,
|
|
1147
|
+
compatibilityReason: verificationReport.compatibilityReason,
|
|
1148
|
+
fieldsAnalysis: verificationReport.fieldsAnalysis,
|
|
1149
|
+
checkboxesAnalysis: verificationReport.checkboxesAnalysis,
|
|
1150
|
+
fieldsWithDoubts,
|
|
1151
|
+
warnings: verificationReport.warnings,
|
|
1152
|
+
suggestions: verificationReport.suggestions,
|
|
1153
|
+
},
|
|
1154
|
+
// Détails complets des champs (pour debug)
|
|
1155
|
+
fieldDetails: verificationReport.fieldDetails,
|
|
1156
|
+
// Métadonnées
|
|
788
1157
|
usedLLM: useLLM && !!llm,
|
|
789
|
-
|
|
1158
|
+
usedVerification: enableVerification && !!llm,
|
|
1159
|
+
message: `Rempli: ${filledFields.length} champs, ${modifiedCheckboxes} checkboxes. Score: ${verificationReport.compatibilityScore}%`,
|
|
790
1160
|
},
|
|
791
1161
|
binary: {
|
|
792
1162
|
[outputProperty]: binaryData,
|
|
@@ -795,7 +1165,7 @@ class DocxFillerAI {
|
|
|
795
1165
|
}
|
|
796
1166
|
else if (operation === 'extract') {
|
|
797
1167
|
const inputType = this.getNodeParameter('extractInputType', i);
|
|
798
|
-
const inputValue = this.getNodeParameter('extractDocument', i);
|
|
1168
|
+
const inputValue = this.getNodeParameter('extractDocument', i, '');
|
|
799
1169
|
const docBuffer = await getDocumentBuffer(this, i, inputType, inputValue, items, 'document');
|
|
800
1170
|
const zip = new pizzip_1.default(docBuffer);
|
|
801
1171
|
const xml = ((_c = zip.file('word/document.xml')) === null || _c === void 0 ? void 0 : _c.asText()) || '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-docx-filler",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "n8n node to automatically fill DOCX documents (French DC1, DC2, AE forms) using AI for semantic understanding and field mapping.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|