@heripo/pdf-parser 0.1.29 → 0.1.30

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.md CHANGED
@@ -20,7 +20,7 @@
20
20
  - [Prerequisites](#prerequisites)
21
21
  - [Installation](#installation)
22
22
  - [Usage](#usage)
23
- - [OCR Strategy System](#ocr-strategy-system)
23
+ - [Mandatory Correction Pipeline](#mandatory-correction-pipeline)
24
24
  - [Review Assistance](#review-assistance)
25
25
  - [Document Type Validation](#document-type-validation)
26
26
  - [Large PDF Chunked Conversion](#large-pdf-chunked-conversion)
@@ -36,15 +36,16 @@
36
36
 
37
37
  ## Key Features
38
38
 
39
- - **High-Quality OCR**: Document recognition using Docling SDK (ocrmac / Apple Vision Framework)
40
- - **Korean Report VLM Correction**: Automatically detects Korean reports and applies VLM text correction to all pages
39
+ - **Fixed ocrmac OCR**: Docling conversion always uses ocrmac / Apple Vision Framework
40
+ - **Mandatory VLM Correction**: Post-Docling correction always runs with text correction, page gating, and structural review models
41
41
  - **Apple Silicon Optimized**: GPU acceleration on M1/M2/M3/M4/M5 chips
42
42
  - **Automatic Environment Setup**: Automatic Python virtual environment and docling-serve installation
43
43
  - **Image Extraction**: Automatic extraction and saving of images from PDFs
44
44
  - **Document Type Validation**: Optional LLM-based validation that a PDF is an archaeological report
45
45
  - **Chunked Conversion**: Split large PDFs into chunks for reliable processing
46
46
  - **Image PDF Fallback**: Automatic fallback to image-based PDF when conversion fails
47
- - **Review Assistance**: Optional page-level VLM review that writes audit proposals and can auto-apply high-confidence fixes
47
+ - **Review Assistance**: Page-level VLM review writes audit proposals, skips low-value pages with observable reasons, and can auto-apply high-confidence fixes
48
+ - **Table Correction**: Table-specific work items validate cells, spans, headers, units, footnotes, and adjacent-page continuations
48
49
  - **AbortSignal Support**: Cancel ongoing parsing operations
49
50
  - **Server Crash Recovery**: Automatic restart of docling-serve on ECONNREFUSED
50
51
 
@@ -84,7 +85,7 @@ python3.11 --version
84
85
 
85
86
  #### 4. poppler (PDF text extraction)
86
87
 
87
- Required for PDF page counting (`pdfinfo`) and text layer extraction (`pdftotext`), used by the OCR strategy system's text layer pre-check.
88
+ Required for PDF page counting (`pdfinfo`) and text layer extraction (`pdftotext`), used as a post-Docling correction reference.
88
89
 
89
90
  ```bash
90
91
  brew install poppler
@@ -140,6 +141,7 @@ yarn add @heripo/pdf-parser @heripo/logger
140
141
  ### Basic Usage
141
142
 
142
143
  ```typescript
144
+ import { openai } from '@ai-sdk/openai';
143
145
  import { Logger } from '@heripo/logger';
144
146
  import { PDFParser } from '@heripo/pdf-parser';
145
147
 
@@ -156,6 +158,8 @@ const pdfParser = new PDFParser({
156
158
  logger,
157
159
  });
158
160
 
161
+ const correctionModel = openai('gpt-5.1');
162
+
159
163
  // Initialize (environment setup and start docling-serve)
160
164
  await pdfParser.init();
161
165
 
@@ -168,7 +172,15 @@ const tokenUsageReport = await pdfParser.parse(
168
172
  console.log('PDF conversion complete:', outputPath);
169
173
  },
170
174
  false, // cleanupAfterCallback
171
- {}, // PDFConvertOptions
175
+ {
176
+ correction: {
177
+ models: {
178
+ textCorrection: correctionModel,
179
+ pageGate: correctionModel,
180
+ reviewAssistance: correctionModel,
181
+ },
182
+ },
183
+ }, // PDFConvertOptions
172
184
  );
173
185
 
174
186
  // Token usage report (null when no LLM usage)
@@ -201,23 +213,41 @@ const tokenUsageReport = await pdfParser.parse(
201
213
  async (outputPath) => console.log(outputPath),
202
214
  false,
203
215
  {
204
- // OCR strategy options
205
- strategySamplerModel: openai('gpt-5.1'),
206
- vlmProcessorModel: openai('gpt-5.1'),
207
- vlmConcurrency: 3,
208
-
209
- // Document validation
210
- documentValidationModel: openai('gpt-5.1'),
211
-
212
- // Optional page-level review assistance
213
- reviewAssistance: {
214
- enabled: true,
216
+ // Mandatory post-Docling correction
217
+ correction: {
218
+ models: {
219
+ textCorrection: openai('gpt-5.1'),
220
+ pageGate: openai('gpt-5.1'),
221
+ reviewAssistance: openai('gpt-5.1'),
222
+ tableCorrection: openai('gpt-5.1'),
223
+ reviewAssistanceTasks: {
224
+ text_ocr_hanja: openai('gpt-5.1'),
225
+ tables: openai('gpt-5.1'),
226
+ },
227
+ },
228
+ concurrency: {
229
+ pages: 1,
230
+ reviewTasks: 4,
231
+ tables: 1,
232
+ },
233
+ localModelConcurrency: 1,
234
+ workItemTimeoutMs: 900000,
235
+ maxRetries: {
236
+ textCorrection: 3,
237
+ pageGate: 3,
238
+ reviewAssistance: 3,
239
+ tableCorrection: 3,
240
+ },
215
241
  autoApplyThreshold: 0.85,
216
242
  proposalThreshold: 0.5,
243
+ temperature: 0,
217
244
  outputLanguage: 'en-US',
218
245
  },
219
246
  onReviewAssistanceProgress: (event) => console.log(event),
220
247
 
248
+ // Document validation
249
+ documentValidationModel: openai('gpt-5.1'),
250
+
221
251
  // Chunked conversion for large PDFs
222
252
  chunkedConversion: true,
223
253
  chunkSize: 50,
@@ -244,58 +274,52 @@ Clean up resources after work is complete:
244
274
  await pdfParser.dispose();
245
275
  ```
246
276
 
247
- ## OCR Strategy System
277
+ ## Mandatory Correction Pipeline
248
278
 
249
- ### Why This Strategy?
279
+ ### Why ocrmac Is Fixed
250
280
 
251
281
  **ocrmac (Apple Vision Framework) is an excellent OCR engine** -- it's free, GPU-accelerated, and delivers high-quality results. For processing thousands to millions of archaeological reports, there's no better solution.
252
282
 
253
- **However, Korean archaeological reports often need script-aware correction.** Hanja restoration, CJK mojibake, phonetic substitutions, and institution names can be unreliable with standard OCR alone. Rather than using VLM as the primary OCR engine, the system runs the fast ocrmac pipeline first and then applies VLM text correction to Korean reports.
283
+ `@heripo/pdf-parser` no longer samples OCR strategies or switches to a VLM OCR path. Docling conversion always uses ocrmac. VLMs are used only after Docling conversion as a mandatory correction stage.
254
284
 
255
- ### Two-Stage Korean Detection (`OcrStrategySampler`)
285
+ ### Required Correction Contract
256
286
 
257
- 1. **Text Layer Pre-Check** (zero cost): Extracts the document's text layer using `pdftotext` and checks for Hangul. If Hangul is present, the document is immediately treated as Korean.
258
- 2. **VLM Sampling** (only when needed): Samples up to 15 pages (trimming 10% from front/back to skip covers and appendices) and analyzes them with a Vision LLM. Uses early exit when `ko-KR` is detected.
287
+ Every `parse()` call must provide `correction.models.textCorrection`, `correction.models.pageGate`, and `correction.models.reviewAssistance`. If any required correction model is missing, parsing fails before conversion callback wrapping.
259
288
 
260
- ### Full-Document Correction (`VlmTextCorrector`)
289
+ The correction stage runs in this order:
261
290
 
262
- When a Korean report is detected, every page is sent to the VLM for correction:
291
+ 1. Save `result_ocr_origin.json` before mutations.
292
+ 2. Run page-level text and table-cell OCR correction with `textCorrection`.
293
+ 3. Run the Review Assistance page gate with `pageGate` and write `review_assistance_page_gate.json`.
294
+ 4. Run structural Review Assistance work items with task-specific models.
295
+ 5. Run table-specific correction work items for each detected table.
296
+ 6. Write `review_assistance_checkpoint.json` during execution and `review_assistance.json` at the end.
263
297
 
264
- - Extracts OCR text elements and table cells from each page
265
- - Uses `pdftotext` reference text as a quality anchor
266
- - VLM returns substitution-based corrections (find -> replace)
267
- - Failed page corrections are gracefully skipped, preserving original OCR text
298
+ Text correction applies to every page with text or table content. The page gate only controls structural Review Assistance noise; it does not disable OCR text correction.
268
299
 
269
- ### Strategy Options
300
+ ### Local Model Execution
270
301
 
271
- ```typescript
272
- const tokenUsageReport = await pdfParser.parse(
273
- 'file:///path/to/input.pdf',
274
- 'report-001',
275
- async (outputPath) => console.log(outputPath),
276
- false,
277
- {
278
- // Enable OCR strategy sampling (provide a Vision LLM model)
279
- strategySamplerModel: openai('gpt-5.1'),
302
+ The correction pipeline is optimized for local VLMs: small contexts, many calls, deterministic validation, retry loops, bounded concurrency, and resumable checkpoints. For local models, start with `concurrency.pages: 1`, `concurrency.tables: 1`, `localModelConcurrency: 1`, `temperature: 0`, and a generous `workItemTimeoutMs`. Increase concurrency only after the model is stable.
280
303
 
281
- // VLM model for text correction (required when Korean reports are detected)
282
- vlmProcessorModel: openai('gpt-5.1'),
304
+ ### Rollout Smoke Test
283
305
 
284
- // Concurrency for VLM page processing (default: 1)
285
- vlmConcurrency: 3,
306
+ Repository contributors can run the correction rollout smoke test against two local demo archaeological report artifacts:
286
307
 
287
- // Skip sampling and force a specific OCR method
288
- forcedMethod: 'ocrmac', // or 'vlm'
289
- },
290
- );
308
+ ```bash
309
+ pnpm --filter @heripo/pdf-parser smoke:correction
291
310
  ```
292
311
 
312
+ The smoke test copies existing demo artifacts into `/private/tmp/heripo-pdf-parser-correction-smoke`, runs correction with a deterministic local fake model, verifies `review_assistance.json`, table work-item traces, validation status, and checkpoint resume behavior. It exercises pipeline mechanics; semantic table quality still depends on the configured real local VLM.
313
+
293
314
  ## Review Assistance
294
315
 
295
- `reviewAssistance` enables an optional page-level VLM review after Docling
296
- conversion. It inspects rendered page images, text, tables, captions, pictures,
297
- reading order, roles, bounding boxes, and domain patterns, then writes an audit
298
- report while applying only high-confidence fixes.
316
+ Review Assistance always runs after text correction, but it does not process every page with the same intensity. The page gate marks covers, chapter covers, barcode/ISBN pages, and decorative pages as low-value for structural review. Skipped pages remain observable in `review_assistance.json` with an info issue and skip reason.
317
+
318
+ Eligible pages are split into small work items for text OCR/Hanja review, text integrity, text role/footnote review, tables, pictures/captions, layout/bbox/order, and table-specific correction. Each call records timing, model id, attempts, target refs, and deterministic validation status in `review_assistance.json`.
319
+
320
+ ### Table Correction Strategy
321
+
322
+ Tables are treated as first-class correction targets. The scheduler creates one table-specific work item per table so pages with multiple tables keep independent bbox, crop, context, and validation state. The table validator checks target identity, same-page table mixing, cells, spans, headers, units, footnotes, empty-cell expansion, and adjacent-page continuation refs before any command can become an auto-apply or proposal decision.
299
323
 
300
324
  ```typescript
301
325
  const tokenUsageReport = await pdfParser.parse(
@@ -304,13 +328,21 @@ const tokenUsageReport = await pdfParser.parse(
304
328
  async (outputPath) => console.log(outputPath),
305
329
  false,
306
330
  {
307
- strategySamplerModel: openai('gpt-5.1'),
308
- vlmProcessorModel: openai('gpt-5.1'),
309
- reviewAssistance: {
310
- enabled: true,
331
+ correction: {
332
+ models: {
333
+ textCorrection: openai('gpt-5.1'),
334
+ pageGate: openai('gpt-5.1-mini'),
335
+ reviewAssistance: openai('gpt-5.1'),
336
+ tableCorrection: openai('gpt-5.1'),
337
+ },
311
338
  autoApplyThreshold: 0.85,
312
339
  proposalThreshold: 0.5,
313
- maxRetries: 3,
340
+ maxRetries: {
341
+ textCorrection: 3,
342
+ pageGate: 3,
343
+ reviewAssistance: 3,
344
+ tableCorrection: 3,
345
+ },
314
346
  temperature: 0,
315
347
  outputLanguage: 'en-US',
316
348
  },
@@ -321,11 +353,7 @@ const tokenUsageReport = await pdfParser.parse(
321
353
  );
322
354
  ```
323
355
 
324
- Review Assistance requires a local `file://` PDF and either
325
- `vlmProcessorModel` or `strategySamplerModel`. It updates `result.json` with
326
- auto-applied fixes, keeps snapshots in `result_review_origin.json` and
327
- `result_ocr_origin.json`, and writes `review_assistance.json` containing
328
- per-page decisions, issues, proposals, and summary counts.
356
+ Review Assistance requires a local `file://` PDF for page image and text-layer references. It updates `result.json` with auto-applied fixes, keeps snapshots in `result_review_origin.json` and `result_ocr_origin.json`, writes `review_assistance_page_gate.json`, and writes `review_assistance.json` containing per-page decisions, issues, proposals, call traces, validation status, and summary counts.
329
357
 
330
358
  ## Document Type Validation
331
359
 
@@ -341,6 +369,13 @@ try {
341
369
  async (outputPath) => console.log(outputPath),
342
370
  false,
343
371
  {
372
+ correction: {
373
+ models: {
374
+ textCorrection: openai('gpt-5.1'),
375
+ pageGate: openai('gpt-5.1'),
376
+ reviewAssistance: openai('gpt-5.1'),
377
+ },
378
+ },
344
379
  documentValidationModel: openai('gpt-5.1'),
345
380
  },
346
381
  );
@@ -362,6 +397,13 @@ const tokenUsageReport = await pdfParser.parse(
362
397
  async (outputPath) => console.log(outputPath),
363
398
  false,
364
399
  {
400
+ correction: {
401
+ models: {
402
+ textCorrection: openai('gpt-5.1'),
403
+ pageGate: openai('gpt-5.1'),
404
+ reviewAssistance: openai('gpt-5.1'),
405
+ },
406
+ },
365
407
  chunkedConversion: true,
366
408
  chunkSize: 50, // Pages per chunk (default: configured in constants)
367
409
  chunkMaxRetries: 3, // Max retry attempts per failed chunk (default: configured in constants)
@@ -396,6 +438,13 @@ const tokenUsageReport = await pdfParser.parse(
396
438
  async (outputPath) => console.log(outputPath),
397
439
  false,
398
440
  {
441
+ correction: {
442
+ models: {
443
+ textCorrection: openai('gpt-5.1'),
444
+ pageGate: openai('gpt-5.1'),
445
+ reviewAssistance: openai('gpt-5.1'),
446
+ },
447
+ },
399
448
  forceImagePdf: true, // Always convert to image PDF first
400
449
  },
401
450
  );
@@ -419,7 +468,15 @@ try {
419
468
  'report-001',
420
469
  async (outputPath) => console.log(outputPath),
421
470
  false,
422
- {},
471
+ {
472
+ correction: {
473
+ models: {
474
+ textCorrection: openai('gpt-5.1'),
475
+ pageGate: openai('gpt-5.1'),
476
+ reviewAssistance: openai('gpt-5.1'),
477
+ },
478
+ },
479
+ },
423
480
  controller.signal, // AbortSignal
424
481
  );
425
482
  } catch (error) {
@@ -561,12 +618,8 @@ await pdfParser.dispose();
561
618
 
562
619
  ```typescript
563
620
  type PDFConvertOptions = {
564
- // OCR strategy options
565
- strategySamplerModel?: LanguageModel; // Vision LLM for OCR strategy sampling
566
- vlmProcessorModel?: LanguageModel; // Vision LLM for text correction
567
- vlmConcurrency?: number; // Parallel page processing (default: 1)
568
- skipSampling?: boolean; // Skip strategy sampling
569
- forcedMethod?: 'ocrmac' | 'vlm'; // Force specific OCR method
621
+ // Mandatory post-Docling correction
622
+ correction: PDFCorrectionOptions;
570
623
 
571
624
  // Image PDF options
572
625
  forceImagePdf?: boolean; // Force pre-conversion to image-based PDF
@@ -579,8 +632,7 @@ type PDFConvertOptions = {
579
632
  document_timeout?: number; // Document processing timeout in seconds
580
633
  documentValidationModel?: LanguageModel; // LLM for document type validation
581
634
 
582
- // Review Assistance
583
- reviewAssistance?: boolean | ReviewAssistanceOptions; // Optional page-level review
635
+ // Correction progress
584
636
  onReviewAssistanceProgress?: (event: ReviewAssistanceProgressEvent) => void; // Progress callback
585
637
 
586
638
  // Chunked conversion (large PDFs)
@@ -595,27 +647,47 @@ type PDFConvertOptions = {
595
647
  };
596
648
  ```
597
649
 
598
- ### ReviewAssistanceOptions
650
+ ### PDFCorrectionOptions
599
651
 
600
652
  ```typescript
601
- interface ReviewAssistanceOptions {
602
- enabled?: boolean; // Enable page-level review assistance
653
+ interface PDFCorrectionOptions {
654
+ models: {
655
+ textCorrection: LanguageModel; // Required: page text and table-cell OCR correction
656
+ pageGate: LanguageModel; // Required: structural Review Assistance page gate
657
+ reviewAssistance: LanguageModel; // Required: default structural review model
658
+ tableCorrection?: LanguageModel; // Optional: table-specific correction override
659
+ reviewAssistanceTasks?: Partial<
660
+ Record<
661
+ | 'text_ocr_hanja'
662
+ | 'text_integrity'
663
+ | 'text_role_footnote'
664
+ | 'tables'
665
+ | 'pictures_captions'
666
+ | 'layout_bbox_order',
667
+ LanguageModel
668
+ >
669
+ >;
670
+ };
671
+ concurrency?: {
672
+ pages?: number; // Page-level text correction and page gate concurrency
673
+ reviewTasks?: number; // Structural Review Assistance work-item concurrency
674
+ tables?: number; // Table-specific correction concurrency
675
+ };
676
+ maxRetries?: {
677
+ textCorrection?: number;
678
+ pageGate?: number;
679
+ reviewAssistance?: number;
680
+ tableCorrection?: number;
681
+ };
682
+ localModelConcurrency?: number; // Bounded local model request concurrency
683
+ workItemTimeoutMs?: number; // Per-work-item timeout
684
+ outputLanguage?: string; // Human-readable review reason language (default: en-US)
685
+ pageGate?: {
686
+ structuralNoiseThreshold?: number;
687
+ };
603
688
  autoApplyThreshold?: number; // Minimum confidence for direct mutation (default: 0.85)
604
689
  proposalThreshold?: number; // Minimum confidence for sidecar proposal (default: 0.5)
605
- maxRetries?: number; // Max retries per page-level VLM call (default: 3)
606
690
  temperature?: number; // VLM generation temperature (default: 0)
607
- outputLanguage?: string; // Human-readable review reason language (default: en-US)
608
- }
609
- ```
610
-
611
- ### ConvertWithStrategyResult
612
-
613
- ```typescript
614
- interface ConvertWithStrategyResult {
615
- /** The OCR strategy that was determined */
616
- strategy: OcrStrategy;
617
- /** Token usage report from sampling and/or VLM processing (null when no LLM usage occurs) */
618
- tokenUsageReport: TokenUsageReport | null;
619
691
  }
620
692
  ```
621
693