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 (p.textNormalized.includes('dc1') || p.textNormalized.includes('lettre de candidature')) {
214
- documentType = 'DC1';
215
- }
216
- else if (p.textNormalized.includes('dc2') || p.textNormalized.includes('déclaration du candidat')) {
217
- documentType = 'DC2';
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 === 'binary' || inputType === 'auto') {
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: 'binary', description: 'Propriété binaire' },
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: 'auto',
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: true,
615
- displayOptions: { show: { operation: ['fill'] } },
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: 'binary', description: 'Propriété binaire' },
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: 'auto',
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: true,
638
- displayOptions: { show: { operation: ['fill'] } },
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: 'binary' },
667
- { name: 'File Path', value: 'path' },
668
- { name: 'Auto-Detect', value: 'auto' },
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: 'auto',
974
+ default: 'firstDocx',
671
975
  },
672
976
  {
673
977
  displayName: 'Document',
674
978
  name: 'extractDocument',
675
979
  type: 'string',
676
980
  default: '',
677
- required: true,
678
- displayOptions: { show: { operation: ['extract'] } },
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
- const fillResponse = await llm.invoke(fillPrompt);
747
- const fillText = typeof fillResponse === 'string'
748
- ? fillResponse
749
- : fillResponse.content || fillResponse.text || JSON.stringify(fillResponse);
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}_${sourceData.documentType}_${date}.docx`;
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: sourceData.documentType,
1130
+ documentType: finalDocType,
1131
+ sourceDocumentType: sourceData.documentType,
784
1132
  companyName: sourceData.companyName,
785
- filledFields,
786
- modifiedCheckboxes,
787
- extractedFieldsCount: sourceData.fields.length,
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
- message: `Rempli: ${filledFields.length} champs, ${modifiedCheckboxes} checkboxes`,
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.1",
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",