TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1/**2 * PDF text extraction (SPEC §1): auction houses that publish prices-realised lists as PDFs.3 * pdfjs-dist is loaded lazily so connectors that never touch PDFs pay nothing.4 */5export interface PdfPage {6 page: number;7 /** lines reconstructed from text items grouped by y coordinate */8 lines: string[];9}1011export async function extractPdfPages(data: Uint8Array, opts: { maxPages?: number } = {}): Promise<PdfPage[]> {12 const pdfjs = (await import('pdfjs-dist/legacy/build/pdf.mjs')) as typeof import('pdfjs-dist/legacy/build/pdf.mjs');13 const doc = await pdfjs.getDocument({ data, useSystemFonts: true, disableFontFace: true, isEvalSupported: false }).promise;14 const pages: PdfPage[] = [];15 const n = Math.min(doc.numPages, opts.maxPages ?? doc.numPages);16 for (let i = 1; i <= n; i++) {17 const page = await doc.getPage(i);18 const content = await page.getTextContent();19 const rows = new Map<number, Array<{ x: number; s: string }>>();20 for (const it of content.items as Array<{ str?: string; transform?: number[] }>) {21 if (!it.str || !it.transform) continue;22 const y = Math.round(it.transform[5]! / 2) * 2; // 2pt buckets23 const x = it.transform[4]!;24 const row = rows.get(y) ?? [];25 row.push({ x, s: it.str });26 rows.set(y, row);27 }28 const lines = [...rows.entries()]29 .sort((a, b) => b[0] - a[0])30 .map(([, cells]) => cells.sort((a, b) => a.x - b.x).map((c) => c.s).join(' ').replace(/\s+/g, ' ').trim())31 .filter(Boolean);32 pages.push({ page: i, lines });33 }34 await doc.destroy();35 return pages;36}3738export async function extractPdfText(data: Uint8Array, opts: { maxPages?: number } = {}): Promise<string> {39 const pages = await extractPdfPages(data, opts);40 return pages.map((p) => p.lines.join('\n')).join('\n\f\n');41}4243/**44 * Parse "lot number … price" rows from prices-realised text. Returns null-safe rows; the connector45 * still decides currency and date. Handles "123 1,250", "Lot 123: $1,250", "123 ........ 1.250,00".46 */47export function parsePricesRealised(lines: string[]): Array<{ lot: string; priceText: string; line: string }> {48 const out: Array<{ lot: string; priceText: string; line: string }> = [];49 const re = /^(?:lot\s*)?(\d{1,6}[A-Za-z]?)\b[\s.:\-–]*.*?([$€£¥]?\s?\d{1,3}(?:[.,\s]\d{3})*(?:[.,]\d{2})?)\s*$/i;50 for (const line of lines) {51 const m = line.match(re);52 if (!m) continue;53 if (m[1] === m[2]) continue;54 out.push({ lot: m[1]!, priceText: m[2]!.trim(), line });55 }56 return out;57}58