@arela/uploader 1.1.4 → 1.2.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.
Files changed (95) hide show
  1. package/.claude/worktrees/agent-multi-profile/.env.template +224 -0
  2. package/.claude/worktrees/agent-multi-profile/.prettierrc +13 -0
  3. package/.claude/worktrees/agent-multi-profile/README.md +405 -0
  4. package/.claude/worktrees/agent-multi-profile/package-lock.json +7096 -0
  5. package/.claude/worktrees/agent-multi-profile/package.json +78 -0
  6. package/.claude/worktrees/agent-multi-profile/scripts/cleanup-ds-store.js +109 -0
  7. package/.claude/worktrees/agent-multi-profile/scripts/cleanup-system-files.js +69 -0
  8. package/.claude/worktrees/agent-multi-profile/scripts/scoring-compare.js +243 -0
  9. package/.claude/worktrees/agent-multi-profile/scripts/scoring-phase4-check.js +96 -0
  10. package/.claude/worktrees/agent-multi-profile/scripts/tests/phase-7-features.test.js +415 -0
  11. package/.claude/worktrees/agent-multi-profile/scripts/tests/signal-handling.test.js +275 -0
  12. package/.claude/worktrees/agent-multi-profile/scripts/tests/smart-watch-integration.test.js +554 -0
  13. package/.claude/worktrees/agent-multi-profile/scripts/tests/watch-service-integration.test.js +584 -0
  14. package/.claude/worktrees/agent-multi-profile/src/commands/AgentCommand.js +229 -0
  15. package/.claude/worktrees/agent-multi-profile/src/commands/AgentInitCommand.js +316 -0
  16. package/.claude/worktrees/agent-multi-profile/src/commands/DatastageCommand.js +164 -0
  17. package/.claude/worktrees/agent-multi-profile/src/commands/GDriveSyncCommand.js +475 -0
  18. package/.claude/worktrees/agent-multi-profile/src/commands/IdentifyCommand.js +708 -0
  19. package/.claude/worktrees/agent-multi-profile/src/commands/PollWorkerCommand.js +169 -0
  20. package/.claude/worktrees/agent-multi-profile/src/commands/PropagateCommand.js +636 -0
  21. package/.claude/worktrees/agent-multi-profile/src/commands/PushCommand.js +743 -0
  22. package/.claude/worktrees/agent-multi-profile/src/commands/ScanCommand.js +722 -0
  23. package/.claude/worktrees/agent-multi-profile/src/commands/UploadCommand.js +587 -0
  24. package/.claude/worktrees/agent-multi-profile/src/commands/WatchCommand.js +1342 -0
  25. package/.claude/worktrees/agent-multi-profile/src/commands/WorkerCommand.js +337 -0
  26. package/.claude/worktrees/agent-multi-profile/src/config/config.js +862 -0
  27. package/.claude/worktrees/agent-multi-profile/src/document-type-shared.js +131 -0
  28. package/.claude/worktrees/agent-multi-profile/src/document-types/_pedimento-shared-extractors.js +348 -0
  29. package/.claude/worktrees/agent-multi-profile/src/document-types/doda-pdf.js +121 -0
  30. package/.claude/worktrees/agent-multi-profile/src/document-types/doda-xml.js +118 -0
  31. package/.claude/worktrees/agent-multi-profile/src/document-types/factura-inter-agencia.js +186 -0
  32. package/.claude/worktrees/agent-multi-profile/src/document-types/facturas-comerciales.js +233 -0
  33. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo-xml.js +372 -0
  34. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo.js +108 -0
  35. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-simplificado.js +76 -0
  36. package/.claude/worktrees/agent-multi-profile/src/document-types/proforma.js +29 -0
  37. package/.claude/worktrees/agent-multi-profile/src/document-types/support-document.js +200 -0
  38. package/.claude/worktrees/agent-multi-profile/src/errors/ErrorHandler.js +278 -0
  39. package/.claude/worktrees/agent-multi-profile/src/errors/ErrorTypes.js +104 -0
  40. package/.claude/worktrees/agent-multi-profile/src/file-detection.js +338 -0
  41. package/.claude/worktrees/agent-multi-profile/src/index.js +890 -0
  42. package/.claude/worktrees/agent-multi-profile/src/scoring/db-matcher-adapter.js +98 -0
  43. package/.claude/worktrees/agent-multi-profile/src/scoring/matchers-seed.js +386 -0
  44. package/.claude/worktrees/agent-multi-profile/src/scoring/scoring-engine.js +251 -0
  45. package/.claude/worktrees/agent-multi-profile/src/services/AdvancedFilterService.js +505 -0
  46. package/.claude/worktrees/agent-multi-profile/src/services/AutoProcessingService.js +749 -0
  47. package/.claude/worktrees/agent-multi-profile/src/services/BenchmarkingService.js +381 -0
  48. package/.claude/worktrees/agent-multi-profile/src/services/DatabaseService.js +2173 -0
  49. package/.claude/worktrees/agent-multi-profile/src/services/DatastageApiService.js +240 -0
  50. package/.claude/worktrees/agent-multi-profile/src/services/ErrorMonitor.js +275 -0
  51. package/.claude/worktrees/agent-multi-profile/src/services/GoogleDriveService.js +217 -0
  52. package/.claude/worktrees/agent-multi-profile/src/services/LoggingService.js +649 -0
  53. package/.claude/worktrees/agent-multi-profile/src/services/MonitoringService.js +401 -0
  54. package/.claude/worktrees/agent-multi-profile/src/services/PerformanceOptimizer.js +511 -0
  55. package/.claude/worktrees/agent-multi-profile/src/services/PipelineApiService.js +274 -0
  56. package/.claude/worktrees/agent-multi-profile/src/services/PipelineJobRunner.js +389 -0
  57. package/.claude/worktrees/agent-multi-profile/src/services/ProfileManager.js +164 -0
  58. package/.claude/worktrees/agent-multi-profile/src/services/ReportingService.js +511 -0
  59. package/.claude/worktrees/agent-multi-profile/src/services/ScanApiService.js +775 -0
  60. package/.claude/worktrees/agent-multi-profile/src/services/SignalHandler.js +255 -0
  61. package/.claude/worktrees/agent-multi-profile/src/services/SmartWatchDatabaseService.js +527 -0
  62. package/.claude/worktrees/agent-multi-profile/src/services/WatchService.js +783 -0
  63. package/.claude/worktrees/agent-multi-profile/src/services/upload/ApiUploadService.js +676 -0
  64. package/.claude/worktrees/agent-multi-profile/src/services/upload/BaseUploadService.js +36 -0
  65. package/.claude/worktrees/agent-multi-profile/src/services/upload/MultiApiUploadService.js +233 -0
  66. package/.claude/worktrees/agent-multi-profile/src/services/upload/SupabaseUploadService.js +148 -0
  67. package/.claude/worktrees/agent-multi-profile/src/services/upload/UploadServiceFactory.js +100 -0
  68. package/.claude/worktrees/agent-multi-profile/src/utils/CleanupManager.js +262 -0
  69. package/.claude/worktrees/agent-multi-profile/src/utils/FileOperations.js +192 -0
  70. package/.claude/worktrees/agent-multi-profile/src/utils/FileSanitizer.js +99 -0
  71. package/.claude/worktrees/agent-multi-profile/src/utils/PathDetector.js +198 -0
  72. package/.claude/worktrees/agent-multi-profile/src/utils/PathNormalizer.js +274 -0
  73. package/.claude/worktrees/agent-multi-profile/src/utils/WatchEventHandler.js +522 -0
  74. package/.claude/worktrees/agent-multi-profile/supabase/migrations/001_create_initial_schema.sql +366 -0
  75. package/.claude/worktrees/agent-multi-profile/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
  76. package/.claude/worktrees/agent-multi-profile/tests/commands/IdentifyCommand.test.js +570 -0
  77. package/.claude/worktrees/agent-multi-profile/tests/commands/PropagateCommand.test.js +568 -0
  78. package/.claude/worktrees/agent-multi-profile/tests/commands/PushCommand.test.js +754 -0
  79. package/.claude/worktrees/agent-multi-profile/tests/commands/ScanCommand.test.js +382 -0
  80. package/.claude/worktrees/agent-multi-profile/tests/unit/PathAndTableNameGeneration.test.js +1211 -0
  81. package/.claude/worktrees/agent-multi-profile/tests/unit/factura-inter-agencia.test.js +218 -0
  82. package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-completo-xml-matcher.test.js +271 -0
  83. package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-simplificado-matcher.test.js +185 -0
  84. package/.claude/worktrees/agent-multi-profile/tests/unit/scoring-engine.test.js +221 -0
  85. package/README.md +65 -0
  86. package/package.json +1 -1
  87. package/src/commands/AgentCommand.js +210 -0
  88. package/src/commands/AgentInitCommand.js +316 -0
  89. package/src/commands/PollWorkerCommand.js +11 -322
  90. package/src/config/config.js +44 -6
  91. package/src/index.js +55 -0
  92. package/src/services/LoggingService.js +35 -0
  93. package/src/services/PipelineApiService.js +7 -1
  94. package/src/services/PipelineJobRunner.js +389 -0
  95. package/src/services/ProfileManager.js +164 -0
@@ -0,0 +1,338 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { PDFParse } from 'pdf-parse';
4
+
5
+ import { extractDocumentFields } from './document-type-shared.js';
6
+ import { classifyDocument } from './scoring/scoring-engine.js';
7
+
8
+ // pdf.js can reject internal worker promises on corrupt PDFs (e.g.
9
+ // XRefEntryException) that no try/catch around getText() can reach. Track the
10
+ // last PDF handed to the parser so the global unhandledRejection handler can
11
+ // name it; the event fires after finally blocks run, so it is never cleared.
12
+ let currentPdfFile = null;
13
+ function getCurrentPdfFile() {
14
+ return currentPdfFile;
15
+ }
16
+
17
+ // Document types that participate in arela_path composition.
18
+ const ARELA_PATH_TYPES = new Set([
19
+ 'pedimento_simplificado',
20
+ 'pedimento_completo',
21
+ 'pedimento_completo_xml',
22
+ ]);
23
+
24
+ /**
25
+ * For `pedimento_completo_xml` the patente is not present in the XML body —
26
+ * it must be parsed from the filename. Three known patterns are tried.
27
+ */
28
+ function patenteFromXmlFilename(filePath) {
29
+ if (!filePath) return null;
30
+ const fileName = path.basename(filePath);
31
+
32
+ let m = fileName.match(/^VU_(\d{4})_\d{3}_\d{7}\.xml$/i);
33
+ if (m) return m[1];
34
+
35
+ m = fileName.match(/^\d{3}-(\d{4})-\d{7}\.xml$/i);
36
+ if (m) return m[1];
37
+
38
+ m = fileName.match(/^\d{4}(\d{4})\d{7}(?:_\d{15})?\.xml$/i);
39
+ if (m) return m[1];
40
+
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Compose arela_path from extracted pedimento fields
46
+ * Format: RFC/Year/Patente/Aduana/Pedimento/
47
+ * Example: PED781129JT6/2023/3429/07/3019796/
48
+ */
49
+ function composeArelaPath(
50
+ detectedType,
51
+ fields,
52
+ detectedPedimentoYear,
53
+ filePath,
54
+ ) {
55
+ if (!ARELA_PATH_TYPES.has(detectedType)) {
56
+ return null;
57
+ }
58
+
59
+ // The CoveFact annex resolves to pedimento_completo without payment
60
+ // evidence (see pedimento-completo.js resolveType), so it must never anchor
61
+ // an operation on its own: with no self-composed path it only becomes
62
+ // pushable when a PAID sibling pedimento propagates the folder's
63
+ // arela_path. Unpaid operations therefore never reach Arela through it.
64
+ if (/covefact/i.test(filePath ?? '')) {
65
+ return null;
66
+ }
67
+
68
+ const rfc = fields?.find((f) => f.name === 'rfc')?.value;
69
+ let patente = fields?.find((f) => f.name === 'patente')?.value;
70
+ const aduana = fields?.find((f) => f.name === 'aduanaEntradaSalida')?.value;
71
+ const pedimento = fields?.find((f) => f.name === 'numPedimento')?.value;
72
+ const year = detectedPedimentoYear;
73
+
74
+ // XML matcher does not extract patente from the body — derive from filename.
75
+ if (!patente && detectedType === 'pedimento_completo_xml') {
76
+ patente = patenteFromXmlFilename(filePath);
77
+ }
78
+
79
+ // All components are required for a valid arela_path
80
+ if (!rfc || !year || !patente || !aduana || !pedimento) {
81
+ console.log('⚠️ Missing required fields for arela_path composition:', {
82
+ rfc: !!rfc,
83
+ year: !!year,
84
+ patente: !!patente,
85
+ aduana: !!aduana,
86
+ pedimento: !!pedimento,
87
+ });
88
+ return null;
89
+ }
90
+
91
+ // Ensure aduana is padded to 2 digits if needed (07 instead of 7)
92
+ const aduanaFormatted = aduana.toString().padStart(2, '0');
93
+
94
+ // arela_path should be the folder structure only, without filename
95
+ const arelaPath = `${rfc}/${year}/${patente}/${aduanaFormatted}/${pedimento}/`;
96
+
97
+ console.log(`✅ Composed arela_path: ${arelaPath}`);
98
+ return arelaPath;
99
+ }
100
+
101
+ /**
102
+ * File Detection Service
103
+ * Detects document types and extracts metadata from files
104
+ */
105
+ export class FileDetectionService {
106
+ constructor() {
107
+ // Best-match matchers (adapted from the API). When set, classification uses
108
+ // the scoring engine; otherwise it falls back to legacy first-match-wins.
109
+ this.matchers = null;
110
+ // Per-run classification tally for observability (distribution + unmatched).
111
+ this.classificationStats = {
112
+ total: 0,
113
+ matched: 0,
114
+ unmatched: 0,
115
+ byType: {},
116
+ };
117
+ }
118
+
119
+ /** Provide the resolved+adapted matcher set for scoring-based classification. */
120
+ setMatchers(matchers) {
121
+ this.matchers = matchers && matchers.length ? matchers : null;
122
+ }
123
+
124
+ /**
125
+ * Detect document type, tallying the outcome for a per-run summary. Wraps the
126
+ * implementation so every return path is counted from one place.
127
+ */
128
+ async detectFile(filePath) {
129
+ const result = await this.#detectFileImpl(filePath);
130
+ const type = result?.detectedType ?? null;
131
+ this.classificationStats.total += 1;
132
+ if (type) {
133
+ this.classificationStats.matched += 1;
134
+ this.classificationStats.byType[type] =
135
+ (this.classificationStats.byType[type] ?? 0) + 1;
136
+ } else {
137
+ this.classificationStats.unmatched += 1;
138
+ }
139
+ return result;
140
+ }
141
+
142
+ /** Log the classification distribution for the run (call after processing). */
143
+ logClassificationSummary(logger) {
144
+ const s = this.classificationStats;
145
+ if (!s.total) return;
146
+ const mode = this.matchers ? 'scoring' : 'legacy';
147
+ const dist = Object.entries(s.byType)
148
+ .sort((a, b) => b[1] - a[1])
149
+ .map(([t, n]) => `${t}=${n}`)
150
+ .join(', ');
151
+ const log = logger?.info ? logger : console;
152
+ log.info(
153
+ `🧩 Classification summary (${mode}): ${s.total} docs, ${s.matched} matched, ` +
154
+ `${s.unmatched} unmatched${dist ? ` — ${dist}` : ''}`,
155
+ );
156
+ }
157
+
158
+ /**
159
+ * Detect document type from a file
160
+ * @param {string} filePath - Path to the file to analyze
161
+ * @returns {Promise<{detectedType: string|null, fields: Array, detectedPedimento: string|null, detectedPedimentoYear: number|null, text: string}>}
162
+ */
163
+ async #detectFileImpl(filePath) {
164
+ try {
165
+ const fileExtension = path
166
+ .extname(filePath)
167
+ .toLowerCase()
168
+ .replace('.', '');
169
+ const fileName = path.basename(filePath);
170
+
171
+ console.log(`🔍 Analyzing file: ${fileName} (${fileExtension})`);
172
+
173
+ let text = '';
174
+
175
+ // Extract text based on file type
176
+ switch (fileExtension) {
177
+ case 'pdf':
178
+ text = await this.extractTextFromPDF(filePath);
179
+ break;
180
+ case 'txt':
181
+ text = fs.readFileSync(filePath, 'utf8');
182
+ break;
183
+ case 'xml':
184
+ text = fs.readFileSync(filePath, 'utf8');
185
+ break;
186
+ default:
187
+ console.log(`⚠️ Unsupported file type: ${fileExtension}`);
188
+ return {
189
+ detectedType: null,
190
+ fields: [],
191
+ detectedPedimento: null,
192
+ detectedPedimentoYear: null,
193
+ rfc: null,
194
+ arelaPath: null,
195
+ text: '',
196
+ error: `Unsupported file type: ${fileExtension}`,
197
+ };
198
+ }
199
+
200
+ if (!text || text.trim().length === 0) {
201
+ console.log('⚠️ No text extracted from file');
202
+ return {
203
+ detectedType: null,
204
+ fields: [],
205
+ detectedPedimento: null,
206
+ detectedPedimentoYear: null,
207
+ rfc: null,
208
+ arelaPath: null,
209
+ text: '',
210
+ error: 'No text could be extracted from file',
211
+ };
212
+ }
213
+
214
+ // Extract document fields and detect type. Use the best-match scoring
215
+ // engine when matchers are configured; otherwise legacy first-match-wins.
216
+ let detectedType, fields, detectedPedimento, detectedPedimentoYear;
217
+ if (this.matchers) {
218
+ const r = classifyDocument(this.matchers, {
219
+ source: text,
220
+ extension: fileExtension,
221
+ filePath,
222
+ });
223
+ detectedType = r.detectedType;
224
+ fields = r.fields;
225
+ detectedPedimento = r.detectedPedimento;
226
+ detectedPedimentoYear = r.detectedPedimentoYear;
227
+ } else {
228
+ [detectedType, fields, detectedPedimento, detectedPedimentoYear] =
229
+ extractDocumentFields(text, fileExtension, filePath);
230
+ }
231
+
232
+ // Extract RFC from fields
233
+ const rfc = fields?.find((f) => f.name === 'rfc')?.value ?? null;
234
+
235
+ // Compose arela_path for pedimento_simplificado documents
236
+ const arelaPath = composeArelaPath(
237
+ detectedType,
238
+ fields,
239
+ detectedPedimentoYear,
240
+ filePath,
241
+ );
242
+
243
+ return {
244
+ detectedType,
245
+ fields,
246
+ detectedPedimento,
247
+ detectedPedimentoYear,
248
+ rfc,
249
+ arelaPath,
250
+ text,
251
+ error: null,
252
+ };
253
+ } catch (error) {
254
+ console.error(`❌ Error detecting file ${filePath}:`, error.message);
255
+ return {
256
+ detectedType: null,
257
+ fields: [],
258
+ detectedPedimento: null,
259
+ rfc: null,
260
+ detectedPedimentoYear: null,
261
+ arelaPath: null,
262
+ text: '',
263
+ error: error.message,
264
+ };
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Extract text from PDF file using pdf-parse
270
+ * More reliable for concurrent operations than office-text-extractor
271
+ * @param {string} filePath - Path to PDF file
272
+ * @returns {Promise<string>} - Extracted text
273
+ */
274
+ async extractTextFromPDF(filePath) {
275
+ let parser;
276
+ currentPdfFile = filePath;
277
+ try {
278
+ const dataBuffer = fs.readFileSync(filePath);
279
+ const uint8Array = new Uint8Array(dataBuffer);
280
+ parser = new PDFParse({ data: uint8Array });
281
+ const result = await parser.getText();
282
+ return result.text;
283
+ } catch (error) {
284
+ console.error(
285
+ `Error extracting text from PDF ${filePath}:`,
286
+ error.message,
287
+ );
288
+ throw new Error(`Failed to extract text from PDF: ${error.message}`);
289
+ } finally {
290
+ if (parser) {
291
+ await parser.destroy();
292
+ }
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Detect multiple files in a directory
298
+ * @param {Array<string>} filePaths - Array of file paths to analyze
299
+ * @returns {Promise<Array>} - Array of detection results
300
+ */
301
+ async detectFiles(filePaths) {
302
+ const results = [];
303
+
304
+ for (const filePath of filePaths) {
305
+ const result = await this.detectFile(filePath);
306
+ results.push({
307
+ filePath,
308
+ ...result,
309
+ });
310
+ }
311
+
312
+ return results;
313
+ }
314
+
315
+ /**
316
+ * Check if file type is supported for detection
317
+ * @param {string} filePath - Path to file
318
+ * @returns {boolean} - True if file type is supported
319
+ */
320
+ isSupportedFileType(filePath) {
321
+ const fileExtension = path.extname(filePath).toLowerCase().replace('.', '');
322
+ // Support PDF (main documents), XML (metadata/supporting docs), and TXT
323
+ const supportedExtensions = ['pdf', 'xml', 'txt'];
324
+ return supportedExtensions.includes(fileExtension);
325
+ }
326
+
327
+ /**
328
+ * Filter files to only include supported types
329
+ * @param {Array<string>} filePaths - Array of file paths
330
+ * @returns {Array<string>} - Filtered array of supported file paths
331
+ */
332
+ filterSupportedFiles(filePaths) {
333
+ return filePaths.filter((filePath) => this.isSupportedFileType(filePath));
334
+ }
335
+ }
336
+
337
+ export default FileDetectionService;
338
+ export { composeArelaPath, getCurrentPdfFile };