@heripo/document-processor 0.1.48 → 0.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/README.ko.md +2 -0
- package/README.md +2 -0
- package/dist/index.d.ts +4 -8
- package/dist/index.js +103 -86
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.ko.md
CHANGED
|
@@ -265,6 +265,8 @@ DocumentProcessor는 다음 5단계 파이프라인으로 문서를 처리합니
|
|
|
265
265
|
- PDF 페이지와 문서 논리적 페이지 매핑
|
|
266
266
|
- 스캔 오류로 인한 페이지 번호 불일치 처리
|
|
267
267
|
|
|
268
|
+
PDF 물리 페이지는 1부터 시작하며, 논리 페이지는 본문 1쪽을 기준으로 연속해서 매깁니다. 앞쪽은 음수와 0을 사용하며, 자동 매핑·검토된 입력·목차·챕터 배정에서 이 값을 보존합니다. 추출 실패는 내부적으로 정상 번호인 `0`과 구분합니다. 알려진 페이지의 패턴으로도 번호를 추론할 수 없으면 `PageRangeParseError`를 발생시키며, 실패한 페이지를 논리 `0`으로 출력하지 않습니다.
|
|
269
|
+
|
|
268
270
|
### 3. TOC 추출 (5단계 파이프라인)
|
|
269
271
|
|
|
270
272
|
#### Stage 1: TocFinder (규칙 기반)
|
package/README.md
CHANGED
|
@@ -280,6 +280,8 @@ DocumentProcessor processes documents through a 5-stage pipeline:
|
|
|
280
280
|
- PDF page to document logical page mapping
|
|
281
281
|
- Handle page number mismatches due to scanning errors
|
|
282
282
|
|
|
283
|
+
Physical PDF pages start at 1; logical pages count continuously relative to body page 1. Front matter can use negative values and zero, which are preserved in automatic mapping, reviewed inputs, TOC entries, and chapter assignment. Extraction failures are tracked separately from valid logical zero. If known page patterns cannot resolve a missing number, parsing throws `PageRangeParseError` rather than outputting logical zero for an unknown page.
|
|
284
|
+
|
|
283
285
|
### 3. TOC Extraction (5-Stage Pipeline)
|
|
284
286
|
|
|
285
287
|
#### Stage 1: TocFinder (Rule-Based)
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ interface TocEntry {
|
|
|
19
19
|
*/
|
|
20
20
|
level: number;
|
|
21
21
|
/**
|
|
22
|
-
* Starting page number
|
|
22
|
+
* Starting logical page number (signed integer, relative to body page 1)
|
|
23
23
|
*/
|
|
24
24
|
pageNo: number;
|
|
25
25
|
/**
|
|
@@ -620,7 +620,7 @@ declare enum PagePattern {
|
|
|
620
620
|
* 2. For each group:
|
|
621
621
|
* - If ≤3 pages: send all to LLM at once
|
|
622
622
|
* - If >3 pages: random sample 3 pages, detect pattern, apply to all
|
|
623
|
-
* 3. Post-process: handle drops
|
|
623
|
+
* 3. Post-process: handle drops and infer missing logical pages, including zero and negatives
|
|
624
624
|
*/
|
|
625
625
|
declare class PageRangeParser extends VisionLLMComponent {
|
|
626
626
|
private readonly SAMPLE_SIZE;
|
|
@@ -713,11 +713,7 @@ declare class PageRangeParser extends VisionLLMComponent {
|
|
|
713
713
|
*/
|
|
714
714
|
private detectAndHandleDrops;
|
|
715
715
|
/**
|
|
716
|
-
*
|
|
717
|
-
*/
|
|
718
|
-
private normalizeNegatives;
|
|
719
|
-
/**
|
|
720
|
-
* Backfill pages marked with 0 using detected pattern
|
|
716
|
+
* Infer unresolved pages from the logical numbering pattern
|
|
721
717
|
*/
|
|
722
718
|
private backfillFailedPages;
|
|
723
719
|
/**
|
|
@@ -1493,7 +1489,7 @@ declare class ChapterConverter {
|
|
|
1493
1489
|
* Uses next chapter's start page as end boundary
|
|
1494
1490
|
*
|
|
1495
1491
|
* Front Matter (ch-000) gets special handling:
|
|
1496
|
-
* - startPage:
|
|
1492
|
+
* - startPage: the first logical page
|
|
1497
1493
|
* - endPage: first TOC entry's page - 1 (or 0 if TOC starts at page 1)
|
|
1498
1494
|
*/
|
|
1499
1495
|
private calculatePageRanges;
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
hasToolCall,
|
|
7
7
|
tool
|
|
8
8
|
} from "ai";
|
|
9
|
+
import { wrapLanguageModel } from "ai";
|
|
10
|
+
import { wrapLanguageModel as wrapLanguageModel2 } from "ai";
|
|
9
11
|
var BatchProcessor = class {
|
|
10
12
|
/**
|
|
11
13
|
* Splits an array into batches of specified size.
|
|
@@ -135,9 +137,23 @@ var LLMCaller = class {
|
|
|
135
137
|
cacheWriteTokens: allWrites - (write1h ?? 0)
|
|
136
138
|
},
|
|
137
139
|
...write1h !== void 0 && { cacheWrite1hTokens: write1h },
|
|
140
|
+
...usage?.outputTokenDetails?.reasoningTokens != null && {
|
|
141
|
+
reasoningTokens: usage.outputTokenDetails.reasoningTokens
|
|
142
|
+
},
|
|
138
143
|
metadata: config.metadata
|
|
139
144
|
};
|
|
140
145
|
}
|
|
146
|
+
/** Finish reason and structured-output attempts, when known. */
|
|
147
|
+
static callDetails(response) {
|
|
148
|
+
return {
|
|
149
|
+
...response.finishReason !== void 0 && {
|
|
150
|
+
finishReason: response.finishReason
|
|
151
|
+
},
|
|
152
|
+
...response.structuredAttempts !== void 0 && {
|
|
153
|
+
structuredAttempts: response.structuredAttempts
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
141
157
|
/**
|
|
142
158
|
* Maximum number of retries when structured output generation fails.
|
|
143
159
|
* Total attempts = MAX_STRUCTURED_OUTPUT_RETRIES + 1.
|
|
@@ -178,7 +194,9 @@ var LLMCaller = class {
|
|
|
178
194
|
return {
|
|
179
195
|
output: toolCall.input,
|
|
180
196
|
usage: lastResult.usage,
|
|
181
|
-
steps: lastResult.steps
|
|
197
|
+
steps: lastResult.steps,
|
|
198
|
+
finishReason: lastResult.finishReason,
|
|
199
|
+
structuredAttempts: attempt + 1
|
|
182
200
|
};
|
|
183
201
|
}
|
|
184
202
|
}
|
|
@@ -212,11 +230,18 @@ var LLMCaller = class {
|
|
|
212
230
|
let lastError;
|
|
213
231
|
for (let attempt = 0; attempt <= this.MAX_STRUCTURED_OUTPUT_RETRIES; attempt++) {
|
|
214
232
|
try {
|
|
215
|
-
|
|
233
|
+
const result = await generateText({
|
|
216
234
|
model,
|
|
217
235
|
output: Output.object({ schema }),
|
|
218
236
|
...promptParams
|
|
219
237
|
});
|
|
238
|
+
return {
|
|
239
|
+
output: result.output,
|
|
240
|
+
usage: result.usage,
|
|
241
|
+
steps: result.steps,
|
|
242
|
+
finishReason: result.finishReason,
|
|
243
|
+
structuredAttempts: attempt + 1
|
|
244
|
+
};
|
|
220
245
|
} catch (error) {
|
|
221
246
|
if (NoObjectGeneratedError.isInstance(error)) {
|
|
222
247
|
lastError = error;
|
|
@@ -240,7 +265,8 @@ var LLMCaller = class {
|
|
|
240
265
|
return {
|
|
241
266
|
output: response.output,
|
|
242
267
|
usage: this.buildUsage(config, primaryModelName, response, false),
|
|
243
|
-
usedFallback: false
|
|
268
|
+
usedFallback: false,
|
|
269
|
+
...this.callDetails(response)
|
|
244
270
|
};
|
|
245
271
|
} catch (primaryError) {
|
|
246
272
|
if (config.abortSignal?.aborted) {
|
|
@@ -254,7 +280,8 @@ var LLMCaller = class {
|
|
|
254
280
|
return {
|
|
255
281
|
output: response.output,
|
|
256
282
|
usage: this.buildUsage(config, fallbackModelName, response, true),
|
|
257
|
-
usedFallback: true
|
|
283
|
+
usedFallback: true,
|
|
284
|
+
...this.callDetails(response)
|
|
258
285
|
};
|
|
259
286
|
}
|
|
260
287
|
}
|
|
@@ -277,8 +304,17 @@ var LLMCaller = class {
|
|
|
277
304
|
return this.executeWithFallback(
|
|
278
305
|
config,
|
|
279
306
|
(model) => this.generateStructuredOutput(model, config.schema, {
|
|
280
|
-
instructions: config.
|
|
307
|
+
instructions: config.cacheSystemPrompt ? {
|
|
308
|
+
role: "system",
|
|
309
|
+
content: config.systemPrompt,
|
|
310
|
+
providerOptions: {
|
|
311
|
+
anthropic: { cacheControl: { type: "ephemeral" } }
|
|
312
|
+
}
|
|
313
|
+
} : config.systemPrompt,
|
|
281
314
|
prompt: config.userPrompt,
|
|
315
|
+
...config.providerOptions && {
|
|
316
|
+
providerOptions: config.providerOptions
|
|
317
|
+
},
|
|
282
318
|
temperature: config.temperature,
|
|
283
319
|
maxRetries: config.maxRetries,
|
|
284
320
|
abortSignal: config.abortSignal
|
|
@@ -548,6 +584,7 @@ var LLMTokenUsageAggregator = class {
|
|
|
548
584
|
this.usage = {};
|
|
549
585
|
}
|
|
550
586
|
};
|
|
587
|
+
var encoder = new TextEncoder();
|
|
551
588
|
|
|
552
589
|
// src/utils/ref-resolver.ts
|
|
553
590
|
var RefResolver = class {
|
|
@@ -1037,7 +1074,7 @@ var ChapterConverter = class _ChapterConverter {
|
|
|
1037
1074
|
*/
|
|
1038
1075
|
convert(tocEntries, textItems, pageRangeMap, images, tables, footnotes) {
|
|
1039
1076
|
this.logger.info("[ChapterConverter] Starting chapter conversion...");
|
|
1040
|
-
const frontMatter = this.createFrontMatterChapter();
|
|
1077
|
+
const frontMatter = this.createFrontMatterChapter(pageRangeMap);
|
|
1041
1078
|
const tocChapters = this.buildChapterTree(tocEntries);
|
|
1042
1079
|
this.logger.info(
|
|
1043
1080
|
`[ChapterConverter] Built ${tocChapters.length} TOC chapters + Front Matter`
|
|
@@ -1069,12 +1106,15 @@ var ChapterConverter = class _ChapterConverter {
|
|
|
1069
1106
|
/**
|
|
1070
1107
|
* Create Front Matter chapter for pre-TOC content
|
|
1071
1108
|
*/
|
|
1072
|
-
createFrontMatterChapter() {
|
|
1109
|
+
createFrontMatterChapter(pageRangeMap) {
|
|
1110
|
+
const logicalPages = Object.values(pageRangeMap).map(
|
|
1111
|
+
(range) => range.startPageNo
|
|
1112
|
+
);
|
|
1073
1113
|
return {
|
|
1074
1114
|
id: _ChapterConverter.FRONT_MATTER_ID,
|
|
1075
1115
|
originTitle: _ChapterConverter.FRONT_MATTER_TITLE,
|
|
1076
1116
|
title: _ChapterConverter.FRONT_MATTER_TITLE,
|
|
1077
|
-
pageNo: 1,
|
|
1117
|
+
pageNo: logicalPages.length > 0 ? Math.min(...logicalPages) : 1,
|
|
1078
1118
|
level: 1,
|
|
1079
1119
|
sourceRefs: [],
|
|
1080
1120
|
textBlocks: [],
|
|
@@ -1133,7 +1173,7 @@ var ChapterConverter = class _ChapterConverter {
|
|
|
1133
1173
|
* Uses next chapter's start page as end boundary
|
|
1134
1174
|
*
|
|
1135
1175
|
* Front Matter (ch-000) gets special handling:
|
|
1136
|
-
* - startPage:
|
|
1176
|
+
* - startPage: the first logical page
|
|
1137
1177
|
* - endPage: first TOC entry's page - 1 (or 0 if TOC starts at page 1)
|
|
1138
1178
|
*/
|
|
1139
1179
|
calculatePageRanges(flatChapters, tocEntries) {
|
|
@@ -1147,7 +1187,7 @@ var ChapterConverter = class _ChapterConverter {
|
|
|
1147
1187
|
);
|
|
1148
1188
|
const sorted = [...tocChapters].sort((a, b) => a.tocPageNo - b.tocPageNo);
|
|
1149
1189
|
ranges.set(_ChapterConverter.FRONT_MATTER_ID, {
|
|
1150
|
-
startPage:
|
|
1190
|
+
startPage: flatChapters[0].tocPageNo,
|
|
1151
1191
|
endPage: firstTocPage - 1
|
|
1152
1192
|
});
|
|
1153
1193
|
sorted.forEach((current, i) => {
|
|
@@ -1211,7 +1251,7 @@ var ChapterConverter = class _ChapterConverter {
|
|
|
1211
1251
|
*/
|
|
1212
1252
|
findChapterForPage(actualPageNo, chapterRanges) {
|
|
1213
1253
|
let bestMatch = null;
|
|
1214
|
-
let bestStartPage = -
|
|
1254
|
+
let bestStartPage = -Infinity;
|
|
1215
1255
|
for (const [chapterId, range] of chapterRanges) {
|
|
1216
1256
|
if (actualPageNo >= range.startPage && actualPageNo <= range.endPage) {
|
|
1217
1257
|
if (range.startPage > bestStartPage) {
|
|
@@ -1612,7 +1652,7 @@ ${details}`,
|
|
|
1612
1652
|
* Recursively validate entries
|
|
1613
1653
|
*/
|
|
1614
1654
|
validateEntries(entries, parentPath, parentEntry, seenKeys) {
|
|
1615
|
-
let prevPageNo = parentEntry?.pageNo ??
|
|
1655
|
+
let prevPageNo = parentEntry?.pageNo ?? -Infinity;
|
|
1616
1656
|
for (let i = 0; i < entries.length; i++) {
|
|
1617
1657
|
const entry = entries[i];
|
|
1618
1658
|
const path4 = parentPath ? `${parentPath}.children[${i}]` : `[${i}]`;
|
|
@@ -1662,10 +1702,10 @@ ${details}`,
|
|
|
1662
1702
|
* V002: Validate page number range
|
|
1663
1703
|
*/
|
|
1664
1704
|
validatePageRange(entry, path4) {
|
|
1665
|
-
if (entry.pageNo
|
|
1705
|
+
if (!Number.isInteger(entry.pageNo)) {
|
|
1666
1706
|
this.addIssue({
|
|
1667
1707
|
code: "V002",
|
|
1668
|
-
message: `Page number must be
|
|
1708
|
+
message: `Page number must be an integer, got ${entry.pageNo}`,
|
|
1669
1709
|
path: path4,
|
|
1670
1710
|
entry
|
|
1671
1711
|
});
|
|
@@ -2242,7 +2282,7 @@ var TextLLMComponent = class extends BaseLLMComponent {
|
|
|
2242
2282
|
var DEFAULT_MAX_VALIDATION_RETRIES = 3;
|
|
2243
2283
|
var VALIDATION_CODE_DESCRIPTIONS = {
|
|
2244
2284
|
V001: "Page numbers must be in non-decreasing order within the same level. A decrease usually means a hierarchy or page number error.",
|
|
2245
|
-
V002: "Page number
|
|
2285
|
+
V002: "Page number must be an integer within the configured logical page range.",
|
|
2246
2286
|
V003: "Title is empty or contains only whitespace.",
|
|
2247
2287
|
V004: "Title exceeds the maximum allowed length.",
|
|
2248
2288
|
V005: "Child page number is before parent page number. Children must start on or after the parent page.",
|
|
@@ -2253,7 +2293,7 @@ function buildTocEntrySchema(maxDepth) {
|
|
|
2253
2293
|
const base = {
|
|
2254
2294
|
title: z.string().describe("Chapter or section title"),
|
|
2255
2295
|
level: z.number().int().min(1).describe("Hierarchy depth (1 = top level)"),
|
|
2256
|
-
pageNo: z.number().int().
|
|
2296
|
+
pageNo: z.number().int().describe("Starting logical page number")
|
|
2257
2297
|
};
|
|
2258
2298
|
if (maxDepth <= 0) {
|
|
2259
2299
|
return z.object(base);
|
|
@@ -3180,7 +3220,7 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3180
3220
|
"info",
|
|
3181
3221
|
`Found ${sizeGroups.length} size group(s), total ${pages.length} pages`
|
|
3182
3222
|
);
|
|
3183
|
-
const
|
|
3223
|
+
const extractedPageRanges = {};
|
|
3184
3224
|
const usageList = [];
|
|
3185
3225
|
for (let i = 0; i < sizeGroups.length; i++) {
|
|
3186
3226
|
const group = sizeGroups[i];
|
|
@@ -3189,11 +3229,20 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3189
3229
|
`Processing group ${i + 1}/${sizeGroups.length}: ${group.pageNos.length} pages`
|
|
3190
3230
|
);
|
|
3191
3231
|
const groupResult = await this.processGroup(pages, group, this.model);
|
|
3192
|
-
Object.assign(
|
|
3232
|
+
Object.assign(extractedPageRanges, groupResult.pageRangeMap);
|
|
3193
3233
|
usageList.push(...groupResult.usage);
|
|
3194
3234
|
}
|
|
3195
3235
|
usageList.forEach((usage) => this.trackUsage(usage));
|
|
3196
|
-
this.postProcess(
|
|
3236
|
+
this.postProcess(extractedPageRanges);
|
|
3237
|
+
const pageRangeMap = {};
|
|
3238
|
+
for (const [pdfPage, range] of Object.entries(extractedPageRanges)) {
|
|
3239
|
+
if (range === null) {
|
|
3240
|
+
throw new PageRangeParseError(
|
|
3241
|
+
`Could not determine a logical page number for PDF page ${pdfPage}`
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
pageRangeMap[Number(pdfPage)] = range;
|
|
3245
|
+
}
|
|
3197
3246
|
this.log(
|
|
3198
3247
|
"info",
|
|
3199
3248
|
`Completed: ${Object.keys(pageRangeMap).length} pages mapped`
|
|
@@ -3321,7 +3370,7 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3321
3370
|
`Could not detect page pattern after ${consecutiveAllNullAttempts >= MAX_ALL_NULL_ATTEMPTS ? consecutiveAllNullAttempts : this.MAX_PATTERN_RETRIES + 1} attempts for group with ${pageNos.length} pages, marking as failed`
|
|
3322
3371
|
);
|
|
3323
3372
|
const fallbackMap = Object.fromEntries(
|
|
3324
|
-
pageNos.map((pageNo) => [pageNo,
|
|
3373
|
+
pageNos.map((pageNo) => [pageNo, null])
|
|
3325
3374
|
);
|
|
3326
3375
|
return { pageRangeMap: fallbackMap, usage: usageList };
|
|
3327
3376
|
}
|
|
@@ -3363,8 +3412,8 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3363
3412
|
pages: z4.array(
|
|
3364
3413
|
z4.object({
|
|
3365
3414
|
imageIndex: z4.number().describe("0-based index of the image in the request"),
|
|
3366
|
-
startPageNo: z4.number().nullable().describe("Start page number (null if not found)"),
|
|
3367
|
-
endPageNo: z4.number().nullable().describe(
|
|
3415
|
+
startPageNo: z4.number().int().nullable().describe("Start page number (null if not found)"),
|
|
3416
|
+
endPageNo: z4.number().int().nullable().describe(
|
|
3368
3417
|
"End page number for double-sided scans (null for single page)"
|
|
3369
3418
|
)
|
|
3370
3419
|
})
|
|
@@ -3477,7 +3526,7 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3477
3526
|
break;
|
|
3478
3527
|
}
|
|
3479
3528
|
default:
|
|
3480
|
-
result[pdfPageNo] =
|
|
3529
|
+
result[pdfPageNo] = null;
|
|
3481
3530
|
}
|
|
3482
3531
|
});
|
|
3483
3532
|
return result;
|
|
@@ -3492,7 +3541,7 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3492
3541
|
sample.startPageNo !== null ? {
|
|
3493
3542
|
startPageNo: sample.startPageNo,
|
|
3494
3543
|
endPageNo: sample.endPageNo ?? sample.startPageNo
|
|
3495
|
-
} :
|
|
3544
|
+
} : null
|
|
3496
3545
|
])
|
|
3497
3546
|
);
|
|
3498
3547
|
}
|
|
@@ -3502,7 +3551,6 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3502
3551
|
postProcess(pageRangeMap) {
|
|
3503
3552
|
this.detectAndHandleOutliers(pageRangeMap);
|
|
3504
3553
|
this.detectAndHandleDrops(pageRangeMap);
|
|
3505
|
-
this.normalizeNegatives(pageRangeMap);
|
|
3506
3554
|
this.backfillFailedPages(pageRangeMap);
|
|
3507
3555
|
}
|
|
3508
3556
|
/**
|
|
@@ -3528,8 +3576,9 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3528
3576
|
let hasOutliers = false;
|
|
3529
3577
|
for (let i = 0; i < normalSequenceStart; i++) {
|
|
3530
3578
|
const pdfPage = pdfPages[i];
|
|
3531
|
-
const
|
|
3532
|
-
if (
|
|
3579
|
+
const range = pageRangeMap[pdfPage];
|
|
3580
|
+
if (range === null) continue;
|
|
3581
|
+
const pageNo = range.startPageNo;
|
|
3533
3582
|
const pdfDiff = normalStartPdfPage - pdfPage;
|
|
3534
3583
|
const isDoubleSided = this.isDoubleSidedRange(
|
|
3535
3584
|
pageRangeMap[normalStartPdfPage]
|
|
@@ -3540,7 +3589,7 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3540
3589
|
"info",
|
|
3541
3590
|
`Outlier detected: PDF ${pdfPage}=${pageNo} (expected ~${expectedPageNo})`
|
|
3542
3591
|
);
|
|
3543
|
-
pageRangeMap[pdfPage] =
|
|
3592
|
+
pageRangeMap[pdfPage] = null;
|
|
3544
3593
|
hasOutliers = true;
|
|
3545
3594
|
}
|
|
3546
3595
|
}
|
|
@@ -3567,7 +3616,7 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3567
3616
|
const nextPdfPage = pdfPages[startIdx + i + 1];
|
|
3568
3617
|
const currRange = pageRangeMap[currPdfPage];
|
|
3569
3618
|
const nextRange = pageRangeMap[nextPdfPage];
|
|
3570
|
-
if (currRange
|
|
3619
|
+
if (currRange === null || nextRange === null) {
|
|
3571
3620
|
isValidSequence = false;
|
|
3572
3621
|
break;
|
|
3573
3622
|
}
|
|
@@ -3608,40 +3657,32 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3608
3657
|
for (let i = 1; i < pdfPages.length; i++) {
|
|
3609
3658
|
const prevPdfPage = pdfPages[i - 1];
|
|
3610
3659
|
const currPdfPage = pdfPages[i];
|
|
3611
|
-
const
|
|
3612
|
-
const
|
|
3613
|
-
if (
|
|
3614
|
-
|
|
3660
|
+
const prevRange = pageRangeMap[prevPdfPage];
|
|
3661
|
+
const currRange = pageRangeMap[currPdfPage];
|
|
3662
|
+
if (prevRange === null || currRange === null) continue;
|
|
3663
|
+
const prevPageNo = prevRange.startPageNo;
|
|
3664
|
+
const currPageNo = currRange.startPageNo;
|
|
3665
|
+
if (prevPageNo > currPageNo && prevPageNo - currPageNo > 1) {
|
|
3615
3666
|
this.log(
|
|
3616
3667
|
"info",
|
|
3617
3668
|
`Page drop detected: PDF ${prevPdfPage}=${prevPageNo} -> PDF ${currPdfPage}=${currPageNo}`
|
|
3618
3669
|
);
|
|
3619
|
-
const isDoubleSided = this.isDoubleSidedRange(
|
|
3620
|
-
pageRangeMap[currPdfPage]
|
|
3621
|
-
);
|
|
3670
|
+
const isDoubleSided = this.isDoubleSidedRange(currRange);
|
|
3622
3671
|
for (let j = i - 1; j >= 0; j--) {
|
|
3623
3672
|
const pdfPage = pdfPages[j];
|
|
3624
3673
|
const distance = currPdfPage - pdfPage;
|
|
3625
3674
|
if (isDoubleSided) {
|
|
3626
3675
|
const expectedStartPageNo = currPageNo - distance * 2;
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
startPageNo: expectedStartPageNo,
|
|
3632
|
-
endPageNo: expectedStartPageNo + 1
|
|
3633
|
-
};
|
|
3634
|
-
}
|
|
3676
|
+
pageRangeMap[pdfPage] = {
|
|
3677
|
+
startPageNo: expectedStartPageNo,
|
|
3678
|
+
endPageNo: expectedStartPageNo + 1
|
|
3679
|
+
};
|
|
3635
3680
|
} else {
|
|
3636
3681
|
const expectedPageNo = currPageNo - distance;
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
startPageNo: expectedPageNo,
|
|
3642
|
-
endPageNo: expectedPageNo
|
|
3643
|
-
};
|
|
3644
|
-
}
|
|
3682
|
+
pageRangeMap[pdfPage] = {
|
|
3683
|
+
startPageNo: expectedPageNo,
|
|
3684
|
+
endPageNo: expectedPageNo
|
|
3685
|
+
};
|
|
3645
3686
|
}
|
|
3646
3687
|
this.log(
|
|
3647
3688
|
"info",
|
|
@@ -3652,32 +3693,19 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3652
3693
|
}
|
|
3653
3694
|
}
|
|
3654
3695
|
/**
|
|
3655
|
-
*
|
|
3656
|
-
*/
|
|
3657
|
-
normalizeNegatives(pageRangeMap) {
|
|
3658
|
-
for (const [pdfPageStr, range] of Object.entries(pageRangeMap)) {
|
|
3659
|
-
if (range.startPageNo < 0 || range.endPageNo < 0) {
|
|
3660
|
-
this.log("info", `Normalizing negative: PDF ${pdfPageStr} -> 0`);
|
|
3661
|
-
pageRangeMap[Number(pdfPageStr)] = { startPageNo: 0, endPageNo: 0 };
|
|
3662
|
-
}
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
/**
|
|
3666
|
-
* Backfill pages marked with 0 using detected pattern
|
|
3696
|
+
* Infer unresolved pages from the logical numbering pattern
|
|
3667
3697
|
*/
|
|
3668
3698
|
backfillFailedPages(pageRangeMap) {
|
|
3669
3699
|
const pdfPages = Object.keys(pageRangeMap).map(Number).sort((a, b) => a - b);
|
|
3670
|
-
const failedPages = pdfPages.filter(
|
|
3671
|
-
(p) => pageRangeMap[p].startPageNo === 0
|
|
3672
|
-
);
|
|
3700
|
+
const failedPages = pdfPages.filter((p) => pageRangeMap[p] === null);
|
|
3673
3701
|
if (failedPages.length === 0) return;
|
|
3674
|
-
const successfulPages = pdfPages.filter((p) => pageRangeMap[p]
|
|
3702
|
+
const successfulPages = pdfPages.filter((p) => pageRangeMap[p] !== null).map((p) => ({
|
|
3675
3703
|
pdfPage: p,
|
|
3676
3704
|
pageNo: pageRangeMap[p].startPageNo,
|
|
3677
3705
|
isDoubleSided: this.isDoubleSidedRange(pageRangeMap[p])
|
|
3678
3706
|
}));
|
|
3679
|
-
if (successfulPages.length
|
|
3680
|
-
this.log("warn", "
|
|
3707
|
+
if (successfulPages.length === 0) {
|
|
3708
|
+
this.log("warn", "No known logical pages for backfill");
|
|
3681
3709
|
return;
|
|
3682
3710
|
}
|
|
3683
3711
|
const doubleSidedCount = successfulPages.filter(
|
|
@@ -3695,16 +3723,9 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3695
3723
|
);
|
|
3696
3724
|
for (const pdfPage of failedPages) {
|
|
3697
3725
|
const expectedStartPageNo = pdfPage * 2 + avgOffset;
|
|
3698
|
-
if (expectedStartPageNo < 1) {
|
|
3699
|
-
this.log(
|
|
3700
|
-
"info",
|
|
3701
|
-
`Backfill skipped for PDF ${pdfPage} (would be ${expectedStartPageNo})`
|
|
3702
|
-
);
|
|
3703
|
-
continue;
|
|
3704
|
-
}
|
|
3705
3726
|
this.log(
|
|
3706
3727
|
"info",
|
|
3707
|
-
`Backfill PDF ${pdfPage}:
|
|
3728
|
+
`Backfill PDF ${pdfPage}: unknown -> ${expectedStartPageNo}-${expectedStartPageNo + 1}`
|
|
3708
3729
|
);
|
|
3709
3730
|
pageRangeMap[pdfPage] = {
|
|
3710
3731
|
startPageNo: expectedStartPageNo,
|
|
@@ -3722,14 +3743,10 @@ var PageRangeParser = class extends VisionLLMComponent {
|
|
|
3722
3743
|
);
|
|
3723
3744
|
for (const pdfPage of failedPages) {
|
|
3724
3745
|
const expectedPageNo = pdfPage + avgOffset;
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
);
|
|
3730
|
-
continue;
|
|
3731
|
-
}
|
|
3732
|
-
this.log("info", `Backfill PDF ${pdfPage}: 0 -> ${expectedPageNo}`);
|
|
3746
|
+
this.log(
|
|
3747
|
+
"info",
|
|
3748
|
+
`Backfill PDF ${pdfPage}: unknown -> ${expectedPageNo}`
|
|
3749
|
+
);
|
|
3733
3750
|
pageRangeMap[pdfPage] = {
|
|
3734
3751
|
startPageNo: expectedPageNo,
|
|
3735
3752
|
endPageNo: expectedPageNo
|