@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.
- package/.claude/worktrees/agent-multi-profile/.env.template +224 -0
- package/.claude/worktrees/agent-multi-profile/.prettierrc +13 -0
- package/.claude/worktrees/agent-multi-profile/README.md +405 -0
- package/.claude/worktrees/agent-multi-profile/package-lock.json +7096 -0
- package/.claude/worktrees/agent-multi-profile/package.json +78 -0
- package/.claude/worktrees/agent-multi-profile/scripts/cleanup-ds-store.js +109 -0
- package/.claude/worktrees/agent-multi-profile/scripts/cleanup-system-files.js +69 -0
- package/.claude/worktrees/agent-multi-profile/scripts/scoring-compare.js +243 -0
- package/.claude/worktrees/agent-multi-profile/scripts/scoring-phase4-check.js +96 -0
- package/.claude/worktrees/agent-multi-profile/scripts/tests/phase-7-features.test.js +415 -0
- package/.claude/worktrees/agent-multi-profile/scripts/tests/signal-handling.test.js +275 -0
- package/.claude/worktrees/agent-multi-profile/scripts/tests/smart-watch-integration.test.js +554 -0
- package/.claude/worktrees/agent-multi-profile/scripts/tests/watch-service-integration.test.js +584 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/AgentCommand.js +229 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/AgentInitCommand.js +316 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/DatastageCommand.js +164 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/GDriveSyncCommand.js +475 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/IdentifyCommand.js +708 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/PollWorkerCommand.js +169 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/PropagateCommand.js +636 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/PushCommand.js +743 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/ScanCommand.js +722 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/UploadCommand.js +587 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/WatchCommand.js +1342 -0
- package/.claude/worktrees/agent-multi-profile/src/commands/WorkerCommand.js +337 -0
- package/.claude/worktrees/agent-multi-profile/src/config/config.js +862 -0
- package/.claude/worktrees/agent-multi-profile/src/document-type-shared.js +131 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/_pedimento-shared-extractors.js +348 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/doda-pdf.js +121 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/doda-xml.js +118 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/factura-inter-agencia.js +186 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/facturas-comerciales.js +233 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo-xml.js +372 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo.js +108 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-simplificado.js +76 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/proforma.js +29 -0
- package/.claude/worktrees/agent-multi-profile/src/document-types/support-document.js +200 -0
- package/.claude/worktrees/agent-multi-profile/src/errors/ErrorHandler.js +278 -0
- package/.claude/worktrees/agent-multi-profile/src/errors/ErrorTypes.js +104 -0
- package/.claude/worktrees/agent-multi-profile/src/file-detection.js +338 -0
- package/.claude/worktrees/agent-multi-profile/src/index.js +890 -0
- package/.claude/worktrees/agent-multi-profile/src/scoring/db-matcher-adapter.js +98 -0
- package/.claude/worktrees/agent-multi-profile/src/scoring/matchers-seed.js +386 -0
- package/.claude/worktrees/agent-multi-profile/src/scoring/scoring-engine.js +251 -0
- package/.claude/worktrees/agent-multi-profile/src/services/AdvancedFilterService.js +505 -0
- package/.claude/worktrees/agent-multi-profile/src/services/AutoProcessingService.js +749 -0
- package/.claude/worktrees/agent-multi-profile/src/services/BenchmarkingService.js +381 -0
- package/.claude/worktrees/agent-multi-profile/src/services/DatabaseService.js +2173 -0
- package/.claude/worktrees/agent-multi-profile/src/services/DatastageApiService.js +240 -0
- package/.claude/worktrees/agent-multi-profile/src/services/ErrorMonitor.js +275 -0
- package/.claude/worktrees/agent-multi-profile/src/services/GoogleDriveService.js +217 -0
- package/.claude/worktrees/agent-multi-profile/src/services/LoggingService.js +649 -0
- package/.claude/worktrees/agent-multi-profile/src/services/MonitoringService.js +401 -0
- package/.claude/worktrees/agent-multi-profile/src/services/PerformanceOptimizer.js +511 -0
- package/.claude/worktrees/agent-multi-profile/src/services/PipelineApiService.js +274 -0
- package/.claude/worktrees/agent-multi-profile/src/services/PipelineJobRunner.js +389 -0
- package/.claude/worktrees/agent-multi-profile/src/services/ProfileManager.js +164 -0
- package/.claude/worktrees/agent-multi-profile/src/services/ReportingService.js +511 -0
- package/.claude/worktrees/agent-multi-profile/src/services/ScanApiService.js +775 -0
- package/.claude/worktrees/agent-multi-profile/src/services/SignalHandler.js +255 -0
- package/.claude/worktrees/agent-multi-profile/src/services/SmartWatchDatabaseService.js +527 -0
- package/.claude/worktrees/agent-multi-profile/src/services/WatchService.js +783 -0
- package/.claude/worktrees/agent-multi-profile/src/services/upload/ApiUploadService.js +676 -0
- package/.claude/worktrees/agent-multi-profile/src/services/upload/BaseUploadService.js +36 -0
- package/.claude/worktrees/agent-multi-profile/src/services/upload/MultiApiUploadService.js +233 -0
- package/.claude/worktrees/agent-multi-profile/src/services/upload/SupabaseUploadService.js +148 -0
- package/.claude/worktrees/agent-multi-profile/src/services/upload/UploadServiceFactory.js +100 -0
- package/.claude/worktrees/agent-multi-profile/src/utils/CleanupManager.js +262 -0
- package/.claude/worktrees/agent-multi-profile/src/utils/FileOperations.js +192 -0
- package/.claude/worktrees/agent-multi-profile/src/utils/FileSanitizer.js +99 -0
- package/.claude/worktrees/agent-multi-profile/src/utils/PathDetector.js +198 -0
- package/.claude/worktrees/agent-multi-profile/src/utils/PathNormalizer.js +274 -0
- package/.claude/worktrees/agent-multi-profile/src/utils/WatchEventHandler.js +522 -0
- package/.claude/worktrees/agent-multi-profile/supabase/migrations/001_create_initial_schema.sql +366 -0
- package/.claude/worktrees/agent-multi-profile/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
- package/.claude/worktrees/agent-multi-profile/tests/commands/IdentifyCommand.test.js +570 -0
- package/.claude/worktrees/agent-multi-profile/tests/commands/PropagateCommand.test.js +568 -0
- package/.claude/worktrees/agent-multi-profile/tests/commands/PushCommand.test.js +754 -0
- package/.claude/worktrees/agent-multi-profile/tests/commands/ScanCommand.test.js +382 -0
- package/.claude/worktrees/agent-multi-profile/tests/unit/PathAndTableNameGeneration.test.js +1211 -0
- package/.claude/worktrees/agent-multi-profile/tests/unit/factura-inter-agencia.test.js +218 -0
- package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-completo-xml-matcher.test.js +271 -0
- package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-simplificado-matcher.test.js +185 -0
- package/.claude/worktrees/agent-multi-profile/tests/unit/scoring-engine.test.js +221 -0
- package/README.md +65 -0
- package/package.json +1 -1
- package/src/commands/AgentCommand.js +210 -0
- package/src/commands/AgentInitCommand.js +316 -0
- package/src/commands/PollWorkerCommand.js +11 -322
- package/src/config/config.js +44 -6
- package/src/index.js +55 -0
- package/src/services/LoggingService.js +35 -0
- package/src/services/PipelineApiService.js +7 -1
- package/src/services/PipelineJobRunner.js +389 -0
- 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 };
|