feat: Seiten- und Wortzahl für den Duplikatvergleich

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bjoernpoettkerandClaude Opus 5.5 committed 2026-11-02 13:55:51 +01:00
1 parent 57a460ffa2
commit 8e0c609802
10 files changed
+273 -21

No files matched your search

@@ -0,0 +1,56 @@
import { PDFDocument } from 'pdf-lib';
import { PDFParse } from 'pdf-parse';
/** Kennzahlen eines Belegs; `null` = nicht ermittelbar. */
export interface Kennzahlen {
seiten: number | null;
woerter: number | null;
}
/** Kennzahlen des vorhandenen (Paperless) und des neuen Belegs. */
export interface VergleichKennzahlen {
vorhanden: Kennzahlen;
neu: Kennzahlen;
}
/**
* Zählt Wörter: Tokens zwischen Leerraum, die mindestens einen Buchstaben oder
* eine Ziffer enthalten. Seitenmarker von pdf-parse („-- 1 of 2 --“) zählen
* nicht. Kein Wort → `null` (z. B. Scan ohne Textebene).
*/
export function zaehleWoerter(text: string | null | undefined): number | null {
if (!text) return null;
const ohneMarker = text.replace(/-- \d+ of \d+ --/g, ' ');
const anzahl = ohneMarker
.split(/\s+/)
.filter((token) => /[\p{L}\p{N}]/u.test(token)).length;
return anzahl > 0 ? anzahl : null;
}
async function seitenzahl(pdf: Buffer): Promise<number | null> {
try {
const doc = await PDFDocument.load(pdf, { ignoreEncryption: true });
return doc.getPageCount();
} catch {
return null;
}
}
async function wortzahl(pdf: Buffer): Promise<number | null> {
// pdf.js übernimmt den Puffer, deshalb eine Kopie übergeben
const parser = new PDFParse({ data: new Uint8Array(pdf) });
try {
const { text } = await parser.getText();
return zaehleWoerter(text);
} catch {
return null;
} finally {
await parser.destroy().catch(() => undefined);
}
}
/** Seiten- und Wortzahl eines PDFs; Lesefehler ergeben `null`. */
export async function kennzahlenAusPdf(pdf: Buffer): Promise<Kennzahlen> {
const [seiten, woerter] = await Promise.all([seitenzahl(pdf), wortzahl(pdf)]);
return { seiten, woerter };
}