diff --git a/BuyerSheetParser.ts b/BuyerSheetParser.ts index 944baa1..fc5b548 100644 --- a/BuyerSheetParser.ts +++ b/BuyerSheetParser.ts @@ -2,6 +2,7 @@ import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js'; import * as fs from 'fs'; export interface BuyerSheetData { + file_name: string; // NEU is_buyer_sheet: boolean; name_company: string | null; prospective_buyer: string | null; @@ -21,8 +22,8 @@ export interface BuyerSheetData { date_of_introduction: string | null; _checkbox_pending: boolean; _parser: string; - _info_page: number; - _ca_page: number; + _info_page: number | null; + _ca_page: number | null; } const LABELS = [ @@ -34,16 +35,69 @@ const LABELS = [ export class DeterministicParser { - public async parsePdf(filePath: string): Promise { + public async parsePdf(filePath: string, fileName: string): Promise { const dataBuffer = fs.readFileSync(filePath); const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(dataBuffer) }); const pdfDocument = await loadingTask.promise; - const page = await pdfDocument.getPage(1); - const textContent = await page.getTextContent(); + // Regel: Alles über 10 Seiten wird radikal ignoriert + if (pdfDocument.numPages > 10) { + return null; + } - // 1. Textfragmente mit X, Y und Breite (Width) auslesen - const mappedItems = textContent.items + let infoPageNum: number | null = null; + let caPageNum: number | null = null; + let infoPageObj: any = null; + let infoPageTextContent: any = null; + + // ========================================== + // 1. Dynamische Seitensuche (Visuell sortiert & kugelsicher) + // ========================================== + for (let i = 1; i <= pdfDocument.numPages; i++) { + const page = await pdfDocument.getPage(i); + const textContent = await page.getTextContent(); + + // Elemente mit Koordinaten versehen und wie ein Mensch lesen (von oben nach unten, links nach rechts) + const sortedItems = textContent.items + .map((item: any) => ({ + text: item.str, + x: item.transform[4], + y: item.transform[5] + })) + .sort((a: any, b: any) => { + // Y-Toleranz für Buchstaben auf derselben Zeile + if (Math.abs(b.y - a.y) > 5) { + return b.y - a.y; + } + return a.x - b.x; + }); + + // Wir werfen ALLE Leerzeichen, Striche, Punkte und unsichtbare Artefakte weg. + // Übrig bleibt eine reine, unverwüstliche Buchstabenkette. + const textRaw = sortedItems.map((i: any) => i.text).join('').toUpperCase().replace(/[^A-Z]/g, ''); + + // Anker-Suche in der sauberen Zeichenkette + if (textRaw.includes('BUYERINFORMATIONSHEET')) { + infoPageNum = i; + infoPageObj = page; + infoPageTextContent = textContent; + } + if (textRaw.includes('PROSPECTIVEBUYERAGREESTOKEEPANDHOLDCONFIDENTIAL')) { + caPageNum = i; + } + } + + // ========================================== + // 2. Fallback für Bild/Scan PDFs + // ========================================== + if (!infoPageNum || !infoPageObj || !infoPageTextContent) { + return this.createEmptyFallback(fileName, filePath, infoPageNum, caPageNum); + } + + // ========================================== + // 3. Werte-Extraktion (nur auf der Info-Seite!) + // ========================================== + const mappedItems = infoPageTextContent.items .filter((item: any) => item.str.trim() !== '') .map((item: any) => ({ text: item.str, @@ -51,9 +105,8 @@ export class DeterministicParser { y: item.transform[5], width: item.width })) - .sort((a, b) => b.y - a.y || a.x - b.x); // Y absteigend (oben nach unten) + .sort((a: any, b: any) => b.y - a.y || a.x - b.x); - // 2. Zeilen bilden (Y-Toleranz) const linesGrouped: { y: number, items: any[] }[] = []; let currentY: number | null = null; let currentItems = []; @@ -72,7 +125,6 @@ export class DeterministicParser { linesGrouped.push({ y: currentY, items: currentItems }); } - // 3. Zerrissene Wörter reparieren (Kerning) const lines: { y: number, text: string }[] = []; for (const group of linesGrouped) { group.items.sort((a, b) => a.x - b.x); @@ -82,10 +134,7 @@ export class DeterministicParser { for (const item of group.items) { if (prevEnd !== -1) { const gap = item.x - prevEnd; - // Wenn die Lücke größer als ~4 Pixel ist, ist es ein echtes Leerzeichen - if (gap > 4) { - lineStr += " "; - } + if (gap > 4) lineStr += " "; } lineStr += item.text; prevEnd = item.x + item.width; @@ -93,18 +142,13 @@ export class DeterministicParser { lines.push({ y: group.y, text: lineStr }); } - // 4. Y-Intervall Parsing (Die "Nutzer-Idee") const rawResults: Record = {}; LABELS.forEach(lbl => rawResults[lbl] = []); - let currentLabel: string | null = null; - // Längste Labels zuerst suchen, damit "EMAIL ADDRESS" vor "ADDRESS" gefunden wird const sortedLabels = [...LABELS].sort((a, b) => b.length - a.length); for (const line of lines) { const textUpper = line.text.toUpperCase().replace(/\s+/g, ''); - - // Boilerplate ignorieren, der Labels enthält ("VERIFICATION OF DOWN PAYMENT") if (textUpper.includes("SELLERMAYREQUIREVERIFICATION")) continue; let foundLabel: string | null = null; @@ -118,75 +162,87 @@ export class DeterministicParser { if (foundLabel) { currentLabel = foundLabel; - - // Falls der Wert direkt auf derselben Zeile steht (z.B. "NAME/COMPANY: Gunnar Schultz") if (line.text.includes(':')) { const parts = line.text.split(':'); - const val = parts.slice(1).join(':').replace(/_+/g, '').trim(); // Unterstriche entfernen - if (val.length > 0) { - rawResults[currentLabel].push(val); - } + const val = parts.slice(1).join(':').replace(/_+/g, '').trim(); + if (val.length > 0) rawResults[currentLabel].push(val); } } else if (currentLabel) { - // Diese Zeile ist kein Label, gehört also zum aktuellen Bereich! const cleanVal = line.text.replace(/_+/g, '').trim(); - - // Footer und Artefakte ignorieren if (cleanVal.length > 0 && !cleanVal.includes("Doc ID") && !cleanVal.includes("Bizmatch")) { rawResults[currentLabel].push(cleanVal); } } } - // --- NEU: Visuelle Checkbox Erkennung --- - const visualCheckboxResult = await this.detectGraphicCheckbox(page, mappedItems); - return this.mapToTargetStructure(rawResults, filePath, visualCheckboxResult); + + const visualCheckboxResult = await this.detectGraphicCheckbox(infoPageObj, mappedItems); + return this.mapToTargetStructure(rawResults, filePath, fileName, visualCheckboxResult, infoPageNum, caPageNum); + } + + private createEmptyFallback(fileName: string, filePath: string, infoPage: number | null, caPage: number | null): BuyerSheetData { + let dateOfIntro = null; + const dateMatch = filePath.match(/(\d{2})(\d{2})(\d{2})\.pdf$/); + if (dateMatch) dateOfIntro = `20${dateMatch[3]}-${dateMatch[1]}-${dateMatch[2]}`; + + return { + file_name: fileName, + is_buyer_sheet: false, + name_company: null, + prospective_buyer: null, + company: null, + phone: null, + cell: null, + email: null, + address: null, + state: null, + how_did_you_hear: null, + interested_in_updates: null, + types_of_business_raw: null, + background_experience: null, + total_purchase_price: null, + down_payment: null, + down_payment_raw: null, + date_of_introduction: dateOfIntro, + _checkbox_pending: true, + _parser: "deterministic", // Signal für den AI-Pass + _info_page: infoPage, + _ca_page: caPage + }; } private mapToTargetStructure( raw: Record, filePath: string, - visualCheckboxResult: boolean | null // <-- Neuer Parameter + fileName: string, + visualCheckboxResult: boolean | null, + infoPageNum: number | null, + caPageNum: number | null ): BuyerSheetData { const getVal = (label: string) => raw[label] && raw[label].length > 0 ? raw[label].join(' ') : null; - const nameCompany = getVal("NAME / COMPANY"); - // ========================================== - // 1. Telefon & Handy (Cell) separieren - // ========================================== let phoneRaw = getVal("PHONE"); - let phoneClean: string | null = null; - let cellClean: string | null = null; + let phoneClean = null; + let cellClean = null; if (phoneRaw) { - // Alles vor "FAX:" oder "CELL:" ist die Telefonnummer const phoneMatch = phoneRaw.split(/FAX:|CELL:/)[0]; phoneClean = phoneMatch ? phoneMatch.trim() : null; - - // Alles nach "CELL:" ist die Handynummer const cellMatch = phoneRaw.match(/CELL:\s*(.*)/); cellClean = cellMatch && cellMatch[1] ? cellMatch[1].trim() : null; } - // ========================================== - // 2. Adresse bereinigen - // ========================================== let addressClean = getVal("ADDRESS"); if (addressClean) { - // Den statischen Formulart-Subtext entfernen addressClean = addressClean.replace(/PO BOX \/ STREET\s+CITY \/ STATE \/ ZIP/g, '').trim(); if (addressClean === '') addressClean = null; } - // ========================================== - // 3. Checkbox Auswertung (Hybrid) - // ========================================== const interestedRaw = getVal("ARE YOU INTERESTED"); let interestedInUpdates: boolean | null = null; let checkboxPending = true; if (interestedRaw) { - // 1. Zuerst Text-Prüfung versuchen (wie vorher) if (/(✔|☑|X|✓)\s*YES/i.test(interestedRaw) || /YES\s*(✔|☑|X|✓)/i.test(interestedRaw)) { interestedInUpdates = true; checkboxPending = false; @@ -195,30 +251,21 @@ export class DeterministicParser { checkboxPending = false; } } - - // 2. Wenn Text-Prüfung versagt hat, nutzen wir unser visuelles X-Koordinaten Ergebnis! if (checkboxPending && visualCheckboxResult !== null) { interestedInUpdates = visualCheckboxResult; checkboxPending = false; } - // ========================================== - // 4. Down Payment & Datum - // ========================================== const downPaymentRaw = getVal("DOWN PAYMENT")?.replace(/[^0-9,]/g, '') || null; const downPaymentClean = downPaymentRaw ? downPaymentRaw.replace(/,/g, '') : null; let dateOfIntro = null; const dateMatch = filePath.match(/(\d{2})(\d{2})(\d{2})\.pdf$/); - if (dateMatch) { - dateOfIntro = `20${dateMatch[3]}-${dateMatch[1]}-${dateMatch[2]}`; - } + if (dateMatch) dateOfIntro = `20${dateMatch[3]}-${dateMatch[1]}-${dateMatch[2]}`; - // ========================================== - // 5. JSON Return mit dynamischen Flags - // ========================================== return { - is_buyer_sheet: true, // Wird in der Praxis dynamisch gesetzt + file_name: fileName, + is_buyer_sheet: true, name_company: nameCompany, prospective_buyer: nameCompany ? nameCompany.split('/')[0].trim() : null, company: null, @@ -237,31 +284,25 @@ export class DeterministicParser { date_of_introduction: dateOfIntro, _checkbox_pending: checkboxPending, _parser: "deterministic", - _info_page: 1, // Wird in der Praxis über eine Schleife ermittelt - _ca_page: 2 // Wird in der Praxis über eine Schleife ermittelt + _info_page: infoPageNum, + _ca_page: caPageNum }; } + private async detectGraphicCheckbox(page: any, textItems: any[]): Promise { let yesX = null, noX = null, targetY = null; - // 1. Koordinaten von YES und NO suchen for (const item of textItems) { const textUpper = item.text.toUpperCase().trim(); if (textUpper.includes("YES")) { yesX = item.x; targetY = item.y; } } for (const item of textItems) { const textUpper = item.text.toUpperCase().trim(); - // Wir suchen NO auf derselben Höhe (Toleranz 5px) - if (textUpper.includes("NO") && targetY !== null && Math.abs(item.y - targetY) < 5) { - noX = item.x; - } + if (textUpper.includes("NO") && targetY !== null && Math.abs(item.y - targetY) < 5) noX = item.x; } if (yesX === null || noX === null || targetY === null) return null; - // ========================================== - // BILD-ERKENNUNG (Der 16x16 Stempel-Trick) - // ========================================== const opList = await page.getOperatorList(); let currentTransform = [1, 0, 0, 1, 0, 0]; @@ -269,10 +310,7 @@ export class DeterministicParser { const fn = opList.fnArray[i]; const args = opList.argsArray[i]; - if (fn === pdfjsLib.OPS.transform) { - currentTransform = args; - } - // Wenn ein Bild auf die Seite gezeichnet wird + if (fn === pdfjsLib.OPS.transform) currentTransform = args; else if ( fn === pdfjsLib.OPS.paintImageXObject || fn === pdfjsLib.OPS.paintInlineImageXObject || @@ -283,25 +321,13 @@ export class DeterministicParser { const imgX = currentTransform[4]; const imgY = currentTransform[5]; - // Wir filtern nach "Stempeln" (kleine Bilder unter 40x40 Pixeln) - if (width < 40 && height < 40) { - // Befindet sich der Stempel auf unserer Ziel-Zeile? - // (Wir erlauben 50px Toleranz, da Y=378 vs Y=417) - if (Math.abs(imgY - targetY) < 50) { - const distToYes = Math.abs(imgX - yesX); - const distToNo = Math.abs(imgX - noX); - - // Wenn das Bildchen näher an YES ist - if (distToYes < distToNo) { - return true; - } else { - return false; - } - } + if (width < 40 && height < 40 && Math.abs(imgY - targetY) < 50) { + const distToYes = Math.abs(imgX - yesX); + const distToNo = Math.abs(imgX - noX); + return distToYes < distToNo; } } } - return null; } -} +} \ No newline at end of file diff --git a/batch_runner.ts b/batch_runner.ts new file mode 100644 index 0000000..61921e1 --- /dev/null +++ b/batch_runner.ts @@ -0,0 +1,101 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { DeterministicParser } from './BuyerSheetParser'; + +async function main() { + // 1. Argument-Check + const args = process.argv.slice(2); + + // Verzeichnis Parameter + const dirArgIndex = args.indexOf('--dir'); + if (dirArgIndex === -1 || !args[dirArgIndex + 1]) { + console.error("Fehler: Bitte ein Verzeichnis angeben!"); + console.error("Nutzung: npx tsx batch_runner.ts --dir \"/Pfad/zum/Ordner\" [--limit 10]"); + process.exit(1); + } + const dirPath = args[dirArgIndex + 1]; + + // Limit Parameter + const limitArgIndex = args.indexOf('--limit'); + let limit = -1; // -1 bedeutet: Kein Limit, alle verarbeiten + if (limitArgIndex !== -1 && args[limitArgIndex + 1]) { + limit = parseInt(args[limitArgIndex + 1], 10); + } + + if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) { + console.error(`Fehler: Der Pfad "${dirPath}" existiert nicht oder ist kein Verzeichnis.`); + process.exit(1); + } + + // 2. PDFs finden und Metadaten (für die Sortierung) auslesen + const filesWithStats = fs.readdirSync(dirPath) + .filter(f => f.toLowerCase().endsWith('.pdf')) + .map(file => { + const fullPath = path.join(dirPath, file); + return { + file, + fullPath, + // Änderungsdatum der Datei auslesen (in Millisekunden) + mtime: fs.statSync(fullPath).mtimeMs + }; + }); + + if (filesWithStats.length === 0) { + console.log("Keine PDFs in diesem Verzeichnis gefunden."); + process.exit(0); + } + + // 3. Absteigend sortieren (Neueste zuerst) + filesWithStats.sort((a, b) => b.mtime - a.mtime); + + // 4. Limit anwenden (falls gesetzt) + const filesToProcess = limit > 0 ? filesWithStats.slice(0, limit) : filesWithStats; + + console.log(`Starte Verarbeitung: ${filesToProcess.length} von ${filesWithStats.length} PDFs ausgewählt (Sortierung: Neueste zuerst)...\n`); + + const parser = new DeterministicParser(); + const finalResults = []; + let skippedCounter = 0; + + // 5. PDFs iterieren + for (const fileObj of filesToProcess) { + const { file, fullPath } = fileObj; + process.stdout.write(`-> Verarbeite: ${file} ... `); + + try { + const data = await parser.parsePdf(fullPath, file); + + if (data === null) { + console.log("ÜBERSPRUNGEN (> 10 Seiten)"); + skippedCounter++; + } else if (data.is_buyer_sheet === false) { + console.log("IMAGE SCAN (Zuweisung an Vision AI)"); + finalResults.push(data); + } else { + console.log("ERFOLGREICH"); + finalResults.push(data); + } + } catch (error) { + console.log("FEHLER BEIM PARSEN"); + console.error(error); + } + } + + // 6. JSON Export unter ./out/buyers.json + const outDir = path.join(process.cwd(), 'out'); + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir); + } + + const outPath = path.join(outDir, 'buyers.json'); + fs.writeFileSync(outPath, JSON.stringify(finalResults, null, 2), 'utf-8'); + + console.log(`\n=================================================`); + console.log(`Zusammenfassung:`); + console.log(` Verarbeitet: ${finalResults.length}`); + console.log(` Verworfen (>10 Seiten): ${skippedCounter}`); + console.log(` Export gespeichert in: ${outPath}`); + console.log(`=================================================`); +} + +main(); \ No newline at end of file diff --git a/bayarea-ai-server/Dockerfile.cuda b/bayarea-ai-server/Dockerfile.cuda new file mode 100644 index 0000000..30eeb46 --- /dev/null +++ b/bayarea-ai-server/Dockerfile.cuda @@ -0,0 +1,35 @@ +# llama-server für Qwen3.6 Vision — NVIDIA RTX PRO 6000 Blackwell (sm_120) +# +# WICHTIG: +# - Muss aus aktuellem Source gebaut werden: Qwen3.6 nutzt ein neues +# Rope-Encoding (rope.dimension_sections 3 statt 4), alte Images/Builds +# brechen mit "wrong array length". +# - Blackwell braucht CUDA >= 12.8. KEIN CUDA 13.2 verwenden — erzeugt +# mit Qwen3.6 Gibberish (bekannter NVIDIA-Bug). + +FROM nvidia/cuda:12.8.1-devel-ubuntu24.04 AS build + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git cmake build-essential libcurl4-openssl-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp /src + +# 120 = Blackwell (RTX PRO 6000). Für andere Karten anpassen. +RUN cmake /src -B /build \ + -DGGML_CUDA=ON \ + -DCMAKE_CUDA_ARCHITECTURES=120 \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLAMA_CURL=ON \ + && cmake --build /build --config Release -j --target llama-server + +FROM nvidia/cuda:12.8.1-runtime-ubuntu24.04 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libcurl4 libgomp1 curl \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /build/bin/llama-server /usr/local/bin/llama-server + +EXPOSE 8000 +ENTRYPOINT ["llama-server"] \ No newline at end of file diff --git a/bayarea-ai-server/Dockerfile.vulkan b/bayarea-ai-server/Dockerfile.vulkan new file mode 100644 index 0000000..499cf98 --- /dev/null +++ b/bayarea-ai-server/Dockerfile.vulkan @@ -0,0 +1,32 @@ +# llama-server für Qwen3.6 Vision — AMD Radeon AI Pro R9700, Vulkan-Backend +# +# Muss aus aktuellem Source gebaut werden (Qwen3.6-Rope-Änderung, s. Dockerfile.cuda). +# Vulkan statt ROCm — hat sich bei Vision als stabiler erwiesen. + +FROM ubuntu:24.04 AS build + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git cmake build-essential libcurl4-openssl-dev \ + libvulkan-dev glslc \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp /src + +RUN cmake /src -B /build \ + -DGGML_VULKAN=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLAMA_CURL=ON \ + && cmake --build /build --config Release -j --target llama-server + +FROM ubuntu:24.04 + +# mesa-vulkan-drivers = RADV-Treiber im Container (GPU via /dev/dri durchgereicht) +RUN apt-get update && apt-get install -y --no-install-recommends \ + libvulkan1 mesa-vulkan-drivers vulkan-tools \ + libcurl4 libgomp1 curl \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /build/bin/llama-server /usr/local/bin/llama-server + +EXPOSE 8000 +ENTRYPOINT ["llama-server"] \ No newline at end of file diff --git a/bayarea-ai-server/docker-compose.yml b/bayarea-ai-server/docker-compose.yml index 4c53f5a..c2c91e1 100644 --- a/bayarea-ai-server/docker-compose.yml +++ b/bayarea-ai-server/docker-compose.yml @@ -1,47 +1,140 @@ -# llama.cpp + Gemma-4-12B (Vision) auf CUDA (AWS G7e / RTX PRO 6000). +# llama-server für Qwen3.6 Vision — zwei Profile: +# docker compose --profile cuda up -d --build (EC2, RTX PRO 6000 Blackwell) +# docker compose --profile vulkan up -d --build (inhouse, Radeon AI Pro R9700) # -# Bewusst SCHLANK gehalten: keine ROCm/Vulkan-Workarounds. Wir testen, ob -# CUDA die Instabilitaeten von vornherein vermeidet. Nur die inhaltlich noetigen -# Flags (--reasoning off gegen leeres content) bleiben. +# Modelle nach ./models legen (Haupt-GGUF + zugehöriger mmproj aus demselben Repo!): +# CUDA: unsloth/Qwen3.6-27B-GGUF → Qwen3.6-27B-UD-Q4_K_XL.gguf + mmproj-BF16.gguf +# Vulkan: unsloth/Qwen3.6-35B-A3B-GGUF → Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf + mmproj-BF16.gguf # -# Voraussetzung: DLAMI mit NVIDIA Container Toolkit (docker --gpus all funktioniert). -# -# Start: docker compose -f docker-compose-cuda.yml up -d -# Logs: docker compose -f docker-compose-cuda.yml logs -f +# Bewusst KEIN MTP: --mmproj + MTP ist laut Unsloth nicht unterstützt und hat +# offene OOM-/Hänger-Bugs (llama.cpp #23371, #23430). Für Batch-Extraktion +# irrelevant, da die Zeit im Image-Encoding steckt, nicht in der Generierung. services: - llamacpp-gemma12b: - image: ghcr.io/ggml-org/llama.cpp:server-cuda - container_name: llamacpp-gemma12b - restart: unless-stopped - init: true - # GPU-Zugriff ueber das NVIDIA Container Toolkit + llama-cuda: + profiles: ["cuda"] + build: + context: . + dockerfile: Dockerfile.cuda + ports: + - "8000:8000" + volumes: + - ./models:/models deploy: resources: reservations: devices: - driver: nvidia - count: 1 + count: all capabilities: [gpu] - ports: - - "8000:8080" - volumes: - - ~/.cache/llama.cpp:/root/.cache/llama.cpp command: - - -hf - - unsloth/gemma-4-12b-it-GGUF:UD-Q4_K_XL + - -m + - /models/Qwen3.6-27B-UD-Q4_K_XL.gguf + - --mmproj + - /models/mmproj-BF16.gguf - --host - 0.0.0.0 - --port - - "8080" + - "8000" + - --alias + - qwen3.6 - -ngl - "99" - - --ctx-size - - "16384" + - -fa + - "on" + - -c + - "32768" - --parallel - "1" - --jinja - - --reasoning - - "off" + # Erlernte Stabilitäts-Settings (Vision + Checkpoints = OOM-Bug): + - --ctx-checkpoints + - "0" + # Qwen-VL braucht min. 1024 Image-Tokens für korrektes Grounding: + - --image-min-tokens + - "1024" + - --image-max-tokens + - "4096" + # Extraktion: niedrige Temperatur, kein Repeat-Penalty + - --temp + - "0.1" + - --top-p + - "0.95" + - --top-k + - "20" + - --repeat-penalty + - "1.0" + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8000/health"] + interval: 15s + timeout: 5s + retries: 40 + start_period: 60s + restart: unless-stopped + + llama-vulkan: + profiles: ["vulkan"] + build: + context: . + dockerfile: Dockerfile.vulkan + ports: + - "8000:8000" + volumes: + - ./models:/models + devices: + - /dev/dri:/dev/dri + - /dev/kfd:/dev/kfd + group_add: + - video + - render + security_opt: + - seccomp:unconfined + command: + - -m + - /models/Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf + - --mmproj + - /models/mmproj-BF16.gguf + - --host + - 0.0.0.0 + - --port + - "8000" - --alias - - gemma-4-12b \ No newline at end of file + - qwen3.6 + - -ngl + - "99" + - -fa + - "on" + - -c + - "32768" + - --parallel + - "1" + # KV-Cache quantisieren — 32GB VRAM, 35B-A3B + mmproj + Bildkontext: + - -ctk + - q8_0 + - -ctv + - q8_0 + - --jinja + - --ctx-checkpoints + - "0" + - --image-min-tokens + - "1024" + - --image-max-tokens + - "4096" + - --temp + - "0.1" + - --top-p + - "0.95" + - --top-k + - "20" + - --repeat-penalty + - "1.0" + # NOTFALL-Fallback bei Vision-Hängern/OOM unter RADV — mmproj auf CPU, + # Bildverarbeitung wird DEUTLICH langsamer. Nur aktivieren wenn nötig: + # - --no-mmproj-offload + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8000/health"] + interval: 15s + timeout: 5s + retries: 40 + start_period: 120s + restart: unless-stopped \ No newline at end of file diff --git a/debug_text.ts b/debug_text.ts new file mode 100644 index 0000000..23e6d83 --- /dev/null +++ b/debug_text.ts @@ -0,0 +1,48 @@ +import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js'; +import * as fs from 'fs'; + +async function debugText(filePath: string) { + console.log(`\nLese PDF für Text-Debugging: ${filePath}`); + + const dataBuffer = fs.readFileSync(filePath); + const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(dataBuffer) }); + const pdfDocument = await loadingTask.promise; + + console.log(`Anzahl Seiten: ${pdfDocument.numPages}`); + + // Wir nehmen uns Seite 1 vor + const page = await pdfDocument.getPage(1); + const textContent = await page.getTextContent(); + + console.log(`\n--- RAW TEXT ITEMS (Die ersten 40 Fragmente) ---`); + for (let i = 0; i < Math.min(40, textContent.items.length); i++) { + const item = textContent.items[i] as any; + console.log(`Y: ${item.transform[5].toFixed(1).padStart(6)} | X: ${item.transform[4].toFixed(1).padStart(6)} | Text: "${item.str}"`); + } + + // Unsere Sortier- und Bereinigungslogik anwenden + const sortedItems = textContent.items + .map((item: any) => ({ + text: item.str, + x: item.transform[4], + y: item.transform[5] + })) + .sort((a: any, b: any) => { + if (Math.abs(b.y - a.y) > 5) return b.y - a.y; + return a.x - b.x; + }); + + const textRaw = sortedItems.map((i: any) => i.text).join('').toUpperCase().replace(/[^A-Z]/g, ''); + + console.log(`\n--- BEREINIGTER SUCH-STRING (Die ersten 200 Zeichen) ---`); + console.log(textRaw.substring(0, 200)); + console.log(`\nEnthält 'BUYERINFORMATIONSHEET'? -> ${textRaw.includes('BUYERINFORMATIONSHEET')}`); +} + +const args = process.argv.slice(2); +const pdfArgIndex = args.indexOf('--pdf'); +if (pdfArgIndex !== -1 && args[pdfArgIndex + 1]) { + debugText(args[pdfArgIndex + 1]).catch(console.error); +} else { + console.error("Bitte --pdf Parameter angeben."); +} \ No newline at end of file diff --git a/out/buyers.json b/out/buyers.json new file mode 100644 index 0000000..3757a57 --- /dev/null +++ b/out/buyers.json @@ -0,0 +1,2354 @@ +[ + { + "file_name": "Sanderson, Daniel 070926.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-07-09", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sudduth, Henry Notes 070826.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-07-08", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sudduth, Henry 070826.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-07-08", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Spahn, Jeffrey 070726.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-07-07", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shah, Shail 070726.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-07-07", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Saraceni, Dario 063026.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-30", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shah, Zohaib 062926.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-29", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shaju, Alen 062926.pdf", + "is_buyer_sheet": true, + "name_company": "TerraCaelum Capital", + "prospective_buyer": "TerraCaelum Capital", + "company": null, + "phone": "630-935-9530", + "cell": "630-935-9530", + "email": "alen.pshaju@gmail.com", + "address": "9307 Eckert Rd, Rosharon TX 77583", + "state": null, + "how_did_you_hear": "BizBuySell", + "interested_in_updates": true, + "types_of_business_raw": "IT MSP, Health Care, Home Serivces", + "background_experience": "Cloud/Infra Engineer, MBA in Finance, Strategy & Operations, Investment Management", + "total_purchase_price": "$2.4m", + "down_payment": "240", + "down_payment_raw": "240", + "date_of_introduction": "2026-06-29", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Stodola, Philip Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Squires, Brian Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Smith, Rachel Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stephens, Jared Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Santa, Daniel Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sinacola, Jon Paul Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sakievich, Samuel Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stephens, David Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shapiro, Hilary Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stovsand, Hunter Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sato, Hirotoshi Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Solis, Polo Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Smith, Leslie Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stilwel, Mike Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sidhu, Randy Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Saporito, Chris Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shea, Neal Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sarraf, Nick Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Smith, Jordan Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stephens, Jim Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shah, Dhaval Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sperber, Aaron Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sagani, Nizarali Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stefanovits, Peter Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Somo, Salem Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Stevenson, Brett Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Spaulding, Evan Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shillig, Bonnie Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Smith, Carson Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sterling, Tyler Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sturgill, Garrett Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sykes, Kevin Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sandoval, Austen 062526.pdf", + "is_buyer_sheet": true, + "name_company": "Grove Street Holdings LLC", + "prospective_buyer": "Grove Street Holdings LLC", + "company": null, + "phone": "916-710-0211", + "cell": "916-710-0211", + "email": null, + "address": "345 Banyan Blvd West Palm Beach, FL 33401 asandoval@grovestreetholdings.com", + "state": null, + "how_did_you_hear": "Biz Buy Sell", + "interested_in_updates": null, + "types_of_business_raw": "B2B Service Busines", + "background_experience": "Entrepreneurship, Private Equity, Operational Consulting", + "total_purchase_price": "dependent on business", + "down_payment": "3000000", + "down_payment_raw": "3,000,000", + "date_of_introduction": "2026-06-25", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Smith, Ryan Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Spangle, Jack Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sahota, Tejpal Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Syed, Moin Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Snyder, Austin Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Sedas, Carlos Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Seals, Michael Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Shoaf, Loucas Notes 062626.pdf", + "is_buyer_sheet": false, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": null, + "cell": null, + "email": null, + "address": null, + "state": null, + "how_did_you_hear": null, + "interested_in_updates": null, + "types_of_business_raw": null, + "background_experience": null, + "total_purchase_price": null, + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-26", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": null, + "_ca_page": null + }, + { + "file_name": "Smith, Colby 062526.pdf", + "is_buyer_sheet": true, + "name_company": "Colby Smith; American Transition Partners", + "prospective_buyer": "Colby Smith; American Transition Partners", + "company": null, + "phone": "936 524 2514", + "cell": "936 524 2514", + "email": null, + "address": "1 Stage Stop Cir., Houston TX 77024 colby@americantransitionpartners.com", + "state": null, + "how_did_you_hear": "Axial", + "interested_in_updates": null, + "types_of_business_raw": "IN: industrial services home services B2B facility services open to others", + "background_experience": "EXPERIENCE: US Army special forces Private Equity Associate at DVG EVP Finance and Operations at Home Run Dugout Founder at American Transition Partners n/a", + "total_purchase_price": "$300k personal cash plus SBA 7(a) and investor equity as needed", + "down_payment": "150", + "down_payment_raw": "150", + "date_of_introduction": "2026-06-25", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Schmidt, Olivia 062326.pdf", + "is_buyer_sheet": true, + "name_company": "Unity Partners, LP", + "prospective_buyer": "Unity Partners, LP", + "company": null, + "phone": "8329980990", + "cell": "NA", + "email": null, + "address": "1333 Oak Lawn Avenue, Suite 1000, Dallas, TX 75207 oschmidt@unitypartnerslp.com", + "state": null, + "how_did_you_hear": "Axial", + "interested_in_updates": null, + "types_of_business_raw": "IN: Field and office based services businesses NA NA NA", + "background_experience": "EXPERIENCE: PE firm founded in 2022 spun out of HGGC NA NA NA 0", + "total_purchase_price": "0", + "down_payment": "0", + "down_payment_raw": "0", + "date_of_introduction": "2026-06-23", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Sachse, Michael 062326.pdf", + "is_buyer_sheet": true, + "name_company": "Baseload Holdings LLC", + "prospective_buyer": "Baseload Holdings LLC", + "company": null, + "phone": "6462650556", + "cell": "same", + "email": null, + "address": "2627 Connecticut Ave NW #300, Washington, DC 20008 michael@baseloadholdings.com", + "state": null, + "how_did_you_hear": "internet", + "interested_in_updates": null, + "types_of_business_raw": "IN: Cathodic protection, corrosion prevention surveying, GIS, environmental consulting, utility locating ... ...", + "background_experience": "EXPERIENCE: ... Co-founders are both multi-time CEOs of infrastructure businesses ... ... TBD", + "total_purchase_price": "25,000,000", + "down_payment": "750", + "down_payment_raw": "750", + "date_of_introduction": "2026-06-23", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stoltz, Mark 062326.pdf", + "is_buyer_sheet": true, + "name_company": "Barnabas Ventures", + "prospective_buyer": "Barnabas Ventures", + "company": null, + "phone": "2816381724", + "cell": "2816381724", + "email": "mark@barnabasventures.com", + "address": "320 W 15th St., Houston, TX 77008", + "state": null, + "how_did_you_hear": "Listing on BusinessBroker.net", + "interested_in_updates": true, + "types_of_business_raw": "manufacturing, distribution, services", + "background_experience": "M&A", + "total_purchase_price": "6,000,000", + "down_payment": "3000000", + "down_payment_raw": "3,000,000", + "date_of_introduction": "2026-06-23", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Stephens, Neema 062226.pdf", + "is_buyer_sheet": true, + "name_company": "Neema Stephens", + "prospective_buyer": "Neema Stephens", + "company": null, + "phone": "(210) 483-0773", + "cell": "(210) 483-0773", + "email": "neemastephens77@gmail.com", + "address": "8190 Barker Cypress Rd. Suite 1900-526 Cypress, TX 77433", + "state": null, + "how_did_you_hear": "Internet", + "interested_in_updates": true, + "types_of_business_raw": "B2B services, home health companies, non emergency medical transportation", + "background_experience": "Physician with clinical experience and 11 years experience as a corporate medical director at a major health insurance company and pharmaceutical company. My husband and business partner has an MBA and is an executive in data and analytics.", + "total_purchase_price": "1,500,000", + "down_payment": "300000", + "down_payment_raw": "300,000", + "date_of_introduction": "2026-06-22", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Satani, Akash 062126.pdf", + "is_buyer_sheet": true, + "name_company": "akash satani", + "prospective_buyer": "akash satani", + "company": null, + "phone": "5514442318", + "cell": "0000", + "email": "akashsatani33@gmail.com", + "address": "42 river palace 2 mota varachha ,surat,gj,india-394101", + "state": null, + "how_did_you_hear": "web", + "interested_in_updates": true, + "types_of_business_raw": "cnc based parts manufacturing unit", + "background_experience": "we currently in jewelry manufacturing business", + "total_purchase_price": "2000000", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-21", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Skulski, Brian 061926.pdf", + "is_buyer_sheet": true, + "name_company": "Brian Skulski / Bronco Ventures Management LLC", + "prospective_buyer": "Brian Skulski", + "company": null, + "phone": "7136794865", + "cell": "7136794865", + "email": "brian@bronco.ventures", + "address": "2904 First St, Bryan, TX 77801", + "state": null, + "how_did_you_hear": "N/A", + "interested_in_updates": true, + "types_of_business_raw": "Pest Control, Septic, Pool Service, Janitorial, Waste Hauling/Removal, HVAC, Plumbing, Electrical, Paving/Striping", + "background_experience": "Started waste removal company, grew it to over 25,000 customers and 100 employees, sold to strategic buyer. Since then have purchased and operated pest control, septic, pool service, and janitorial businesses. Recently exited septic business, looking to deploy capital within next 6-12 mos.", + "total_purchase_price": "?", + "down_payment": "12000000", + "down_payment_raw": "12000000", + "date_of_introduction": "2026-06-19", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Stern, Adam 061826.pdf", + "is_buyer_sheet": true, + "name_company": "Hillstar Capital", + "prospective_buyer": "Hillstar Capital", + "company": null, + "phone": "469-371-3435", + "cell": "469-371-3435", + "email": null, + "address": "17304 Preston Rd, Ste 800 astern@hillstarcapital.com", + "state": null, + "how_did_you_hear": "axial", + "interested_in_updates": null, + "types_of_business_raw": "IN: industrial distribution, environmental services, road and paving, steel, trucking and transportation various various", + "background_experience": "EXPERIENCE: 25+ years na na na various", + "total_purchase_price": "yes", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-18", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Scanlan, Connor 061926.pdf", + "is_buyer_sheet": true, + "name_company": "Greenrow Capital LLC", + "prospective_buyer": "Greenrow Capital LLC", + "company": null, + "phone": "2172995354", + "cell": "2172995354", + "email": null, + "address": "1801 E Camelback Rd Phoenix, AZ 85016 connor@greenrowcapital.com", + "state": null, + "how_did_you_hear": "Axial", + "interested_in_updates": null, + "types_of_business_raw": "IN: Non discretionary services Non discretionary services Non discretionary services Non discretionary services", + "background_experience": "EXPERIENCE: We've owned and operated multiple restaurants, hotels and industr We've owned and operated multiple restaurants, hotels and industrial businesses. We've owned and operated multiple restaurants, hotels and industrial businesses. We've owned and operated multiple restaurants, hotels and industrial businesses. 10000000", + "total_purchase_price": "2000000", + "down_payment": "400000", + "down_payment_raw": "400000", + "date_of_introduction": "2026-06-19", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Sunesara, Rainish 061726.pdf", + "is_buyer_sheet": true, + "name_company": "Rainish Sunesara", + "prospective_buyer": "Rainish Sunesara", + "company": null, + "phone": "8329989931", + "cell": "8329989931", + "email": "sunesara.r@gmail.com", + "address": "18222 Cairnbrogie Court richmond tx 77407", + "state": null, + "how_did_you_hear": "Onlinen", + "interested_in_updates": true, + "types_of_business_raw": "Retail and manufacturing", + "background_experience": "Buisness owner for last 10 years. Experienced in retual, software an dsmall scale manufacturing", + "total_purchase_price": "3000000", + "down_payment": "600000", + "down_payment_raw": "600000", + "date_of_introduction": "2026-06-17", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Sazegar, Kouros 061626.pdf", + "is_buyer_sheet": true, + "name_company": null, + "prospective_buyer": null, + "company": null, + "phone": "8322537033", + "cell": "8322537033", + "email": "KOUROS@ALKOROCAPITAL.COM", + "address": "3305 RIDGEWAY VALLEY LN HOUSTON TX 77055", + "state": null, + "how_did_you_hear": "AXIAL", + "interested_in_updates": null, + "types_of_business_raw": "IN: INDUSTRIALS MANUFACTURING SERVICES SERVICES", + "background_experience": "PETROLEUM ENGINEER EXPERIENCE: OPERATIONS CONSULTING FOR FORTUNE 500 COMPANIES EV CHARGING STATION START UP OIL AND GAS 5000000", + "total_purchase_price": "1500000", + "down_payment": "200000", + "down_payment_raw": "200000", + "date_of_introduction": "2026-06-16", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Sandoval, Austen 061526.pdf", + "is_buyer_sheet": true, + "name_company": "Grove Street Holdings", + "prospective_buyer": "Grove Street Holdings", + "company": null, + "phone": "916-801-9919", + "cell": "916-801-9919", + "email": null, + "address": "345 Banyan Blvd West Palm Beach, FL, 33401 asandoval@grovestreetholdings.com", + "state": null, + "how_did_you_hear": "online", + "interested_in_updates": true, + "types_of_business_raw": "Service, B2B", + "background_experience": "Private equity, Entrepreneurship, Operational Consulting", + "total_purchase_price": "1-4 million", + "down_payment": "3000000", + "down_payment_raw": "3,000,000", + "date_of_introduction": "2026-06-15", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Sutherland, William Dub 061626.pdf", + "is_buyer_sheet": true, + "name_company": "Sweet 16 LLC", + "prospective_buyer": "Sweet 16 LLC", + "company": null, + "phone": "210-259-3885", + "cell": "210-259-3885", + "email": "dsutherland@kslawllp.com", + "address": "1305 E. Houston Street, Suite 1400, San Antonio, TX 78205", + "state": null, + "how_did_you_hear": "buy biz sell", + "interested_in_updates": false, + "types_of_business_raw": "Various", + "background_experience": null, + "total_purchase_price": "-", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-16", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Sambrani, Naishadh 061626.pdf", + "is_buyer_sheet": true, + "name_company": "Emerald Water Capital, LLC", + "prospective_buyer": "Emerald Water Capital, LLC", + "company": null, + "phone": "9178218332", + "cell": "9178218332", + "email": "naishadh@emeraldwatercapital.com", + "address": "306 W 48th St, New York, NY 10036", + "state": null, + "how_did_you_hear": "Axial", + "interested_in_updates": null, + "types_of_business_raw": "B2B Business Services IN: NA NA NA", + "background_experience": "Prior Investor at Alpine Investors EXPERIENCE: 3+ CEO Partners 1+ Investor Partner NA >20 Million", + "total_purchase_price": "Depending on Opportunity", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-16", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stanger, Thao 061526.pdf", + "is_buyer_sheet": true, + "name_company": "Thao Stanger", + "prospective_buyer": "Thao Stanger", + "company": null, + "phone": "2063712598", + "cell": "2063712598", + "email": "simplypropholdings@gmail.com", + "address": "22212 Locust Way Brier WA 98036", + "state": null, + "how_did_you_hear": "Google search", + "interested_in_updates": true, + "types_of_business_raw": "Buy Laundromat business and property", + "background_experience": "Rental property", + "total_purchase_price": "800k", + "down_payment": "150", + "down_payment_raw": "150", + "date_of_introduction": "2026-06-15", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Souza, Bobby 061226.pdf", + "is_buyer_sheet": true, + "name_company": "Bobby Souza/Overland Collective LLC", + "prospective_buyer": "Bobby Souza", + "company": null, + "phone": "954-821-5636", + "cell": "954-821-5636", + "email": null, + "address": "30 N Gould St Ste. N Sheridan, WY 82801 bobby@overlandparkscollective.com", + "state": null, + "how_did_you_hear": "Internet Listings", + "interested_in_updates": true, + "types_of_business_raw": "RV Parks, Campgrounds, Mobile Home Parks, Tiny Home Villages/Communities, Workforce Housing", + "background_experience": "development. We currently own Inn Town Campground, a 67 site campground and RV park in Northern California, and are closing on a 76-site park in goldsmith, TX in June 2026. Our team brings over 75 years of combined experience in commercial development and ground-up construction, alongside several years operating in the hospitality and vacation rental space, including building and selling a short-term rental management company that managed over $7 million in assets, delivered 800+ guest stays, and maintained a 4.95-start average rating across the Orlando/Disney and South Florida markets.", + "total_purchase_price": "$15,000,000+", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-12", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Shannon, Niall 061026.pdf", + "is_buyer_sheet": true, + "name_company": "Niall Shannon", + "prospective_buyer": "Niall Shannon", + "company": null, + "phone": "8324162747", + "cell": "8324162747", + "email": "n.r.shannon.26@gmail.com", + "address": "19418 Fannin County Lane, cypress, texas, 77433", + "state": null, + "how_did_you_hear": "Google", + "interested_in_updates": true, + "types_of_business_raw": "Property and home services", + "background_experience": "Business management consulting", + "total_purchase_price": "TBD", + "down_payment": "350000", + "down_payment_raw": "350000", + "date_of_introduction": "2026-06-10", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Sietsema, Tom 061026.pdf", + "is_buyer_sheet": true, + "name_company": "Evergate Holdings, Inc.", + "prospective_buyer": "Evergate Holdings, Inc.", + "company": null, + "phone": "9103305891", + "cell": "NA", + "email": "tom@evergateholdings.com", + "address": "108 Wild Basin South, Suite 250, Austin, TX 78746", + "state": null, + "how_did_you_hear": "Online listing", + "interested_in_updates": true, + "types_of_business_raw": "Essential services, physical infrastructure (roads, bridges, DOT, etc.) service companies, and industrial distributors.", + "background_experience": "10+ years in M&A, investing and small business operations.", + "total_purchase_price": "up to $10,000,000", + "down_payment": "10000000", + "down_payment_raw": "10,000,000", + "date_of_introduction": "2026-06-10", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Sheffield, Brad 060926.pdf", + "is_buyer_sheet": true, + "name_company": "Brad Sheffield", + "prospective_buyer": "Brad Sheffield", + "company": null, + "phone": "8178089976", + "cell": "8178089976", + "email": "braddotaggie@hotmail.com", + "address": "4121 Mapleridge Dr., Grapevine, TX 76051", + "state": null, + "how_did_you_hear": "Bizbuysell.com", + "interested_in_updates": null, + "types_of_business_raw": "Manufacturing, distribution", + "background_experience": "working/operating small businesses for 30 years, engineering degree", + "total_purchase_price": "$2MM-20MM", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-09", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Saraceni, Dario 060926.pdf", + "is_buyer_sheet": true, + "name_company": "ICOSAGON LLC", + "prospective_buyer": "ICOSAGON LLC", + "company": null, + "phone": "2819049479", + "cell": "2819049479", + "email": "dsaraceni@icosagonllc.com", + "address": "2913 GRAN HAWTHORNE RD - CONROE - TEXAS - 77385", + "state": null, + "how_did_you_hear": "Google Biz Buy Sell", + "interested_in_updates": true, + "types_of_business_raw": "HVAC - ENERGY - HEALT CARE", + "background_experience": "Private investment group with secured backing focused on acquiring essential-service businesses in Texas. Our team brings over 30 years of combined experience in oil & gas operations.", + "total_purchase_price": "5000000", + "down_payment": "500000", + "down_payment_raw": "500000", + "date_of_introduction": "2026-06-09", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Sriperumbudur, Sri 060826.pdf", + "is_buyer_sheet": true, + "name_company": "Sri Sriperumbudur", + "prospective_buyer": "Sri Sriperumbudur", + "company": null, + "phone": "NA", + "cell": "6308640669", + "email": "sri.smb2026@gmail.com", + "address": "2917 Lyons Rd, Austin, TX 78702", + "state": null, + "how_did_you_hear": "BizBuySell", + "interested_in_updates": null, + "types_of_business_raw": "Already provided in the previous NDA", + "background_experience": "M&A Consulting for Big 4, owns and operates an STR is Austin area. Looking for SDE/EBITDA $250K to $1M and have no issues putting down the equity.", + "total_purchase_price": "$4.9M", + "down_payment": "10", + "down_payment_raw": "10", + "date_of_introduction": "2026-06-08", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Stewart, Cody 060926.pdf", + "is_buyer_sheet": true, + "name_company": "Stewart Legacy Group", + "prospective_buyer": "Stewart Legacy Group", + "company": null, + "phone": "361-229-0857", + "cell": "Same", + "email": null, + "address": "Po box 490 fulton tx 78358 cstewart@stewartlegacyllc.com", + "state": null, + "how_did_you_hear": "Local networking", + "interested_in_updates": true, + "types_of_business_raw": "Real Estate Included, Private Equity ready", + "background_experience": "20yr Private Equity and Real Estate Professionals", + "total_purchase_price": "Depends on opportunity", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-09", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Souza, Bobby 060426.pdf", + "is_buyer_sheet": true, + "name_company": "Bobby Souza / Overland Collective LLC", + "prospective_buyer": "Bobby Souza", + "company": null, + "phone": "954-821-5636", + "cell": "954-821-5636", + "email": "stonefoundationpropertiesllc@gmail.com", + "address": "30 N Gould St Ste N, Sheridan, WY 82801", + "state": null, + "how_did_you_hear": "-", + "interested_in_updates": true, + "types_of_business_raw": "RV Parks, Campgrounds, Mobile Home Parks, Tiny Home Villages/Communities, Workforce Housing", + "background_experience": "development. We currently own Inn Town Campground, a 67-site campground and RV park in Northern California, and are closing on a 76-site park in Goldsmith, TX in June 2026. Our team brings over 75 years of combined experience in commercial development and ground-up construction, alongside several years operating in the hospitality and vacation rental space, including building and selling a short-term rental management company that managed over $7 million in assets, delivered 800+ guest stays, and maintained a 4.95-star average rating across the Orlando/Disney and South Florida markets.", + "total_purchase_price": "$15,000,000+", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-06-04", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Spellman, Patrick 060426.pdf", + "is_buyer_sheet": true, + "name_company": "Patrick Spellman", + "prospective_buyer": "Patrick Spellman", + "company": null, + "phone": "816-807-4085", + "cell": "816-807-4085", + "email": "patrickspellman@gmail.com", + "address": "14871 N Oak Grove School Rd., Harrisburg, MO 65256", + "state": null, + "how_did_you_hear": "I cant remember exactly", + "interested_in_updates": true, + "types_of_business_raw": "Trades, home services, manufacturing, various others", + "background_experience": "15 years building, scaling, and growing a car wash company with 3,000+ employees indirect reporting to me over 250 locations as the Vice President of Operations", + "total_purchase_price": "Up to $3m or so", + "down_payment": "500000", + "down_payment_raw": "500,000", + "date_of_introduction": "2026-06-04", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Souffrant, James 060426.pdf", + "is_buyer_sheet": true, + "name_company": "SBC Legacy Partners", + "prospective_buyer": "SBC Legacy Partners", + "company": null, + "phone": "925-414-1241", + "cell": "925-414-1241", + "email": null, + "address": "2001 Clayton Rd. Concord, CA 94520 jsouffrant@sbclegacypartners.com", + "state": null, + "how_did_you_hear": "Biz Buy Sell", + "interested_in_updates": true, + "types_of_business_raw": "landscaping, janitorial, Facilities Maintenance,", + "background_experience": "20 years of corporate experience, people manager Marketer and Operations", + "total_purchase_price": "<$10M", + "down_payment": "1500000", + "down_payment_raw": "1,500,000", + "date_of_introduction": "2026-06-04", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Singh, Kulbir 060426.pdf", + "is_buyer_sheet": true, + "name_company": "Prince Metal Works of America Inc", + "prospective_buyer": "Prince Metal Works of America Inc", + "company": null, + "phone": "9098559374", + "cell": "9098559374", + "email": "kulbir@usapmw.com", + "address": "201 Poppy Ave, Patterson, CA 95363", + "state": null, + "how_did_you_hear": "Bizbuysell", + "interested_in_updates": true, + "types_of_business_raw": "Manufacturing", + "background_experience": "24 years metal Stampings manufacturing company", + "total_purchase_price": "2000000", + "down_payment": "500000", + "down_payment_raw": "500000", + "date_of_introduction": "2026-06-04", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": null + }, + { + "file_name": "Stoltz, Mark 060226.pdf", + "is_buyer_sheet": true, + "name_company": "Barnabas Ventures", + "prospective_buyer": "Barnabas Ventures", + "company": null, + "phone": "2816381724", + "cell": "2816381724", + "email": "mark@barnabasventures.com", + "address": "320 W 15th St., Houston, TX 77008", + "state": null, + "how_did_you_hear": "Website", + "interested_in_updates": true, + "types_of_business_raw": "Manufacturing, distribution, services", + "background_experience": "M&A experience partnered with industry experts", + "total_purchase_price": "5,000,000", + "down_payment": "3000000", + "down_payment_raw": "3,000,000", + "date_of_introduction": "2026-06-02", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Somo, Fedwon 052626.pdf", + "is_buyer_sheet": true, + "name_company": "Fedwon Somo", + "prospective_buyer": "Fedwon Somo", + "company": null, + "phone": "6194952123", + "cell": "6194952123", + "email": "feddwonsomo@yahoo.com", + "address": "3704 cerino ln. Round rock Texas 78665", + "state": null, + "how_did_you_hear": "Online", + "interested_in_updates": true, + "types_of_business_raw": "Laundromat Self serve car wash", + "background_experience": null, + "total_purchase_price": "700000", + "down_payment": "70000", + "down_payment_raw": "70000", + "date_of_introduction": "2026-05-26", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Steger, Erica 051226.pdf", + "is_buyer_sheet": true, + "name_company": "Pointer Acquisition Partners, LLC", + "prospective_buyer": "Pointer Acquisition Partners, LLC", + "company": null, + "phone": "4154189066", + "cell": "4154189066", + "email": "erica@pointeracquisitionpartners.com", + "address": "11638 Renaissance Dr., Montgomery, TX 77356", + "state": null, + "how_did_you_hear": "BizBuySell", + "interested_in_updates": true, + "types_of_business_raw": "Real estate services, professional services, fabrication/light manufacturing", + "background_experience": "6 years in debt capital markets, MBA from Harvard Business School, 8 years in private equity investing roles, 2 years full P&L responsibility for a $45M EBITDA services business owned by private equity.", + "total_purchase_price": "3,500,000", + "down_payment": "600000", + "down_payment_raw": "600,000", + "date_of_introduction": "2026-05-12", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Spahn, Jeffrey 052026.pdf", + "is_buyer_sheet": true, + "name_company": "Jeffrey Spahn", + "prospective_buyer": "Jeffrey Spahn", + "company": null, + "phone": "563-412-2691", + "cell": "563-412-2691", + "email": "team@amsulllc.com", + "address": "1595 Geraldine Drive, Dubuque, IA 52003", + "state": null, + "how_did_you_hear": "Web", + "interested_in_updates": true, + "types_of_business_raw": "Service and Manufacturing", + "background_experience": "Jeffrey Spahn is an accomplished operations executive with extensive experience in manufacturing, retail, and construction. His career began in the aerospace industry as a Plant Operations Manager before transitioning to the family-owned chain of 28 retail lumberyards. As District Supervisor, he oversaw hiring, operational management, sales, and financial performance for multiple locations, consistently earning high-performance bonuses. Following leadership changes in the family business, Jeffrey pivoted to independent business ownership in 2006. He successfully acquired and expanded a pallet manufacturing company, increasing revenue from $300K to $1.3M. Later, he managed a factory's daily operations before acquiring his current business—a commercial and residential excavating and asphalt construction co mpany—growing revenue from $1.5M to $2.4M in five years.", + "total_purchase_price": "Depends", + "down_payment": "1350000", + "down_payment_raw": "1,350,000", + "date_of_introduction": "2026-05-20", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Sekhri, Karn 052026.pdf", + "is_buyer_sheet": true, + "name_company": "Karn Sekhri", + "prospective_buyer": "Karn Sekhri", + "company": null, + "phone": "9178255013", + "cell": "9178255013", + "email": "karn108@yahoo.com", + "address": "110 River Dr S, Jersey City, NJ 07310", + "state": null, + "how_did_you_hear": "SMB", + "interested_in_updates": true, + "types_of_business_raw": "Medical, Service, Manufacturing", + "background_experience": "Consulting, Operations, Management", + "total_purchase_price": "10000000", + "down_payment": "1000000", + "down_payment_raw": "1000000", + "date_of_introduction": "2026-05-20", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stahler, Kevin 052026.pdf", + "is_buyer_sheet": true, + "name_company": "Stahler Capital", + "prospective_buyer": "Stahler Capital", + "company": null, + "phone": "4696698456", + "cell": "N/A", + "email": "ks@stahlercapital.com", + "address": "3311 Willow Ridge Circle, Carrollton, TX, 75007", + "state": null, + "how_did_you_hear": "BizBuySell", + "interested_in_updates": true, + "types_of_business_raw": "Service and Manufacturing, 800k to 2M EBITDA", + "background_experience": "stahlercapital.com", + "total_purchase_price": "10000000", + "down_payment": "600000", + "down_payment_raw": "600000", + "date_of_introduction": "2026-05-20", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Scanlon, Daniel 051326.pdf", + "is_buyer_sheet": true, + "name_company": "Daniel Scanlon", + "prospective_buyer": "Daniel Scanlon", + "company": null, + "phone": "7088701173", + "cell": "7088701173", + "email": "daniel.scanlon.e@gmail.com", + "address": "513 E Monroe St, Austin, TX 78704", + "state": null, + "how_did_you_hear": "Listing website", + "interested_in_updates": true, + "types_of_business_raw": "residential and commercial services, B2B services and sales, automotive, blue collar", + "background_experience": "sales and marketing experience. Currently manage several reps to assist in growing a territory. Excellent customer service and significant contributor to team culture", + "total_purchase_price": "pre qualified for $6.5M", + "down_payment": "650000", + "down_payment_raw": "650000", + "date_of_introduction": "2026-05-13", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Silva, Emilie 051226.pdf", + "is_buyer_sheet": true, + "name_company": "Emilie Silva", + "prospective_buyer": "Emilie Silva", + "company": null, + "phone": "5107893227", + "cell": "0", + "email": "silvaacquisitions08@gmail.com", + "address": "436 Dunmore Dr Haslet TX 76052", + "state": null, + "how_did_you_hear": "BizBuy", + "interested_in_updates": true, + "types_of_business_raw": "industry agnostic", + "background_experience": "Engineering Project Manager", + "total_purchase_price": "TBD", + "down_payment": "1000000", + "down_payment_raw": "1000000", + "date_of_introduction": "2026-05-12", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Saraceni, Dario 051126.pdf", + "is_buyer_sheet": true, + "name_company": "ICOSAGON LLC", + "prospective_buyer": "ICOSAGON LLC", + "company": null, + "phone": "2816099479", + "cell": "2816099479", + "email": "dsaraceni@icosagonllc.com", + "address": "2913 Grand Hawthorne RD 1 Conroe/ Texas / 77385", + "state": null, + "how_did_you_hear": "Google", + "interested_in_updates": true, + "types_of_business_raw": "HVAC / Mechanical Services - Senior Care / Healthcare Services - Oilfield Services", + "background_experience": "Icosagon LLC is a Houston-based private investment group with secured backing, focused on acquiring essential-service businesses in Texas. We are actively deploying capital in 2026 across three verticals and looking to build relationships with advisors who run quality processes in any of them", + "total_purchase_price": "12 M", + "down_payment": "250000", + "down_payment_raw": "250000", + "date_of_introduction": "2026-05-11", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Spangle, Jack 051226.pdf", + "is_buyer_sheet": true, + "name_company": "Jack Spangle", + "prospective_buyer": "Jack Spangle", + "company": null, + "phone": "6613400688", + "cell": "6613400688", + "email": "jack@jlazysranch.com", + "address": "PO Box 942 Sanger, CA. 93657", + "state": null, + "how_did_you_hear": "SMB Market", + "interested_in_updates": true, + "types_of_business_raw": "HVAC, Electrical, Manufacturing", + "background_experience": "HVAC, Electrical, Industrial Refrigeration, Management", + "total_purchase_price": "Up to $5M", + "down_payment": "500", + "down_payment_raw": "500", + "date_of_introduction": "2026-05-12", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Shrestha, Abhi 051126.pdf", + "is_buyer_sheet": true, + "name_company": "Abhi Shrestha", + "prospective_buyer": "Abhi Shrestha", + "company": null, + "phone": "2148140562", + "cell": "2148140562", + "email": "shadowstone46@gmail.com", + "address": "1503 Mount evans trl, ARlington tx 76005", + "state": null, + "how_did_you_hear": "bizbuysell", + "interested_in_updates": true, + "types_of_business_raw": "home service, manufacturing, glass work, pet grooming", + "background_experience": "IT manager, Vacation rental owner operator, convenience store owner operator.", + "total_purchase_price": "0", + "down_payment": "250000", + "down_payment_raw": "250000", + "date_of_introduction": "2026-05-11", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stefanovits, Peter 051126.pdf", + "is_buyer_sheet": true, + "name_company": "ALPINE CAPITAL INTERNATIONAL LLC", + "prospective_buyer": "ALPINE CAPITAL INTERNATIONAL LLC", + "company": null, + "phone": "404-213-7792", + "cell": "0", + "email": "peters@alpinecapitalinternational.com", + "address": "5805 State Bridge Rd. STE 305G, Duluth, GA 30097", + "state": null, + "how_did_you_hear": "Research", + "interested_in_updates": true, + "types_of_business_raw": "Industrial Manufacturing", + "background_experience": null, + "total_purchase_price": "up tp $ 50 M", + "down_payment": "50", + "down_payment_raw": "50", + "date_of_introduction": "2026-05-11", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stephens, Neema 051126.pdf", + "is_buyer_sheet": true, + "name_company": "Neema Stephens", + "prospective_buyer": "Neema Stephens", + "company": null, + "phone": "(210) 483-0773", + "cell": "(210) 483-0773", + "email": "neemastephens77@gmail.com", + "address": "8190 Barker Cypress Rd. Suite 1900-526 Cypress, TX 77433", + "state": null, + "how_did_you_hear": "Internet search", + "interested_in_updates": true, + "types_of_business_raw": "B2B or B2C business with EBIDTA $400-800K, 5+ years of operation, with established team and manager in the Greater Houston area. Open to industry types, including medical billing, revenue cycle management, commercial cleaning, property management, franchise preschools, pest control, HVAC, commercial kitchen vent hood cleaning, and others.", + "background_experience": "Physician with experience in clinical care and corporate medical director roles in the health insurance and pharmaceutical industry. I previously had an academic appointment and was responsible for building educational curriculum and teaching medical trainees. My husband and business partner has an MBA and is an IT/data and analytics executive at a major insurance company. He leads a team of 100 employees.", + "total_purchase_price": "Flexible, prefer under $5 million", + "down_payment": "300600", + "down_payment_raw": "300600", + "date_of_introduction": "2026-05-11", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stoltz, Mark 050726.pdf", + "is_buyer_sheet": true, + "name_company": "Barnabas Ventures", + "prospective_buyer": "Barnabas Ventures", + "company": null, + "phone": "2816381724", + "cell": "2816381724", + "email": "mark@barnabasventures.com", + "address": "320 W 15th St, Houston, TX 77008", + "state": null, + "how_did_you_hear": "Listing", + "interested_in_updates": true, + "types_of_business_raw": "Manufacturing, distribution, or services in Houston", + "background_experience": "M&A", + "total_purchase_price": "$6,000,000", + "down_payment": "3000000", + "down_payment_raw": "3,000,000", + "date_of_introduction": "2026-05-07", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stovesand, Hunter 050726.pdf", + "is_buyer_sheet": true, + "name_company": "Hunter Stovesand", + "prospective_buyer": "Hunter Stovesand", + "company": null, + "phone": "949-315-0204", + "cell": "949-315-0204", + "email": "hunter.stovesand@gmail.com", + "address": "2828 Woodside St, Dallas, TX 75204", + "state": null, + "how_did_you_hear": "SMBmarket", + "interested_in_updates": null, + "types_of_business_raw": "All", + "background_experience": "N/A", + "total_purchase_price": "4000000", + "down_payment": "400000", + "down_payment_raw": "400000", + "date_of_introduction": "2026-05-07", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Schwartz, Jason 050626.pdf", + "is_buyer_sheet": true, + "name_company": "Jason Schwartz", + "prospective_buyer": "Jason Schwartz", + "company": null, + "phone": "3053356991", + "cell": "3053356991", + "email": "jason@5starservicepartners.com", + "address": "2074 PRairie Avenue, Miami Beach, FL 33139", + "state": null, + "how_did_you_hear": "Loopnet", + "interested_in_updates": null, + "types_of_business_raw": "business services", + "background_experience": "1 exit 10 years 2 platforms current", + "total_purchase_price": "10000000", + "down_payment": "10000000", + "down_payment_raw": "10000000", + "date_of_introduction": "2026-05-06", + "_checkbox_pending": true, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Strickland, Bryan 050126.pdf", + "is_buyer_sheet": true, + "name_company": "Bryan Strickland", + "prospective_buyer": "Bryan Strickland", + "company": null, + "phone": "830.237.4952", + "cell": "na", + "email": "bstrickland@gvtc.com", + "address": "8206 Barlovento St., Corpus Christi, TX 78414", + "state": null, + "how_did_you_hear": "internet search", + "interested_in_updates": true, + "types_of_business_raw": "small businesses with good cash flow and possible seller financing", + "background_experience": "none, i'm in technology sales looking to branch out", + "total_purchase_price": "500,000", + "down_payment": "10", + "down_payment_raw": "10", + "date_of_introduction": "2026-05-01", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Smith, Leslie 042926.pdf", + "is_buyer_sheet": true, + "name_company": "Leslie Smith", + "prospective_buyer": "Leslie Smith", + "company": null, + "phone": "512-925-1225", + "cell": "512-925-1225", + "email": "leslie.smola@yahoo.com", + "address": "7025 FM 1331, Taylor, TX 76574", + "state": null, + "how_did_you_hear": "Web search", + "interested_in_updates": true, + "types_of_business_raw": "healthcare services (optometry; dental; senior care facilities) B2B consulting services (accounting, bookkeeping, IT/MSP, specialty consulting) Manufacturing", + "background_experience": "(financial services and consumer goods), focused on operations, data systems, and stakeholder management. I'd be a non-operator buyer relying on existing management and current employees' involvement for industry-specific expertise.", + "total_purchase_price": "$2.5M", + "down_payment": "150000", + "down_payment_raw": "150,000", + "date_of_introduction": "2026-04-29", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stevenson, Brett 042926.pdf", + "is_buyer_sheet": true, + "name_company": "Brett Stevenson", + "prospective_buyer": "Brett Stevenson", + "company": null, + "phone": "4148820134", + "cell": "4148820134", + "email": "stevensonbrett@yahoo.com", + "address": "3955 Vantech Dr, Ste 10, Memphis, TN 38115", + "state": null, + "how_did_you_hear": "Bizbuysell.com", + "interested_in_updates": true, + "types_of_business_raw": "Metal or plastic manufacturing, i.e. cnc shop, metal fabrication shop", + "background_experience": "manufacturing executive operator and owner", + "total_purchase_price": "5,000,000", + "down_payment": "925000", + "down_payment_raw": "925,000", + "date_of_introduction": "2026-04-29", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stooksberry, Dylan 042926.pdf", + "is_buyer_sheet": true, + "name_company": "Alamo Legacy Capital LLC", + "prospective_buyer": "Alamo Legacy Capital LLC", + "company": null, + "phone": "214-335-3022", + "cell": "214-335-3022", + "email": "dylan@alamo-legacy.com", + "address": "7311 Danwood Dr. Austin, TX 78759", + "state": null, + "how_did_you_hear": "Google", + "interested_in_updates": true, + "types_of_business_raw": "Greater Austin / San Antonio Metro Area", + "background_experience": "I served for seven years as an Army Special Operations officer. Since then, I’ve built operating experience in lower middle market businesses, including serving as Chief of Staff in a testing, inspection, and calibration company with $1M of EBITDA, where I worked on technician recruiting, pricing, and operational initiatives. I’ve also worked with a business doing approximately $2.5M–$3M of EBITDA across B2B services and retail, with a particular focus on retail site selection, strategic growth planning, and operating in a high-turnover labor environment.", + "total_purchase_price": "$12M", + "down_payment": "20015", + "down_payment_raw": "20015", + "date_of_introduction": "2026-04-29", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Sakievich, Samuel 042826.pdf", + "is_buyer_sheet": true, + "name_company": "Samuel Sakievich", + "prospective_buyer": "Samuel Sakievich", + "company": null, + "phone": "218-419-2100", + "cell": "218-419-2100", + "email": "sam@sakievichgroup.com", + "address": "13918 Woodridge Path Savage, MN 55378", + "state": null, + "how_did_you_hear": "Website", + "interested_in_updates": false, + "types_of_business_raw": "Ecommerce or Online Business", + "background_experience": "United States Marine Corps (USMC) – 8 Years", + "total_purchase_price": "3,000,000", + "down_payment": "375000", + "down_payment_raw": "375,000", + "date_of_introduction": "2026-04-28", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stephens, Neema 042926.pdf", + "is_buyer_sheet": true, + "name_company": "Neema Stephens PurposePath Solutions LLC", + "prospective_buyer": "Neema Stephens PurposePath Solutions LLC", + "company": null, + "phone": "(210) 483-0773", + "cell": "(210) 483-0773", + "email": "neemastephens77@gmail.com", + "address": "8190 Barker Cypress Rd. Ste 1900-526 Cypress, TX 77433", + "state": null, + "how_did_you_hear": "Online search", + "interested_in_updates": true, + "types_of_business_raw": "B2B Commercial businesses such as commercial cleaning, pest control, revenue cycle management for medical practices, medical billing, medical staffing, assisted living facilities, home hospice", + "background_experience": "We are a professional duo seeking to acquire a thriving business and invest in its continued growth. I am a physician with experience in patient care and healthcare leadership. My husband is a corporate executive with a strong foundation in data, analytics, IT, and operations management.", + "total_purchase_price": "2,000,000", + "down_payment": "200000", + "down_payment_raw": "200,000", + "date_of_introduction": "2026-04-29", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + }, + { + "file_name": "Stephens, Jim 042926.pdf", + "is_buyer_sheet": true, + "name_company": "Jim Stephens", + "prospective_buyer": "Jim Stephens", + "company": null, + "phone": "7045179907", + "cell": "7045179907", + "email": "jim.e.stephens@gmail.com", + "address": "890 North Bend Rd Weatherford TX 76085", + "state": null, + "how_did_you_hear": "Online", + "interested_in_updates": true, + "types_of_business_raw": "Aerospace machining, OSP, castings", + "background_experience": "20 years aerospace industry. Parker Hannifin Corp", + "total_purchase_price": "To be discussed", + "down_payment": null, + "down_payment_raw": null, + "date_of_introduction": "2026-04-29", + "_checkbox_pending": false, + "_parser": "deterministic", + "_info_page": 1, + "_ca_page": 2 + } +] \ No newline at end of file diff --git a/anonymize_buyers.py b/python/anonymize_buyers.py similarity index 100% rename from anonymize_buyers.py rename to python/anonymize_buyers.py diff --git a/classify_pdfs.py b/python/classify_pdfs.py similarity index 100% rename from classify_pdfs.py rename to python/classify_pdfs.py diff --git a/debug_one_pdf.py b/python/debug_one_pdf.py similarity index 100% rename from debug_one_pdf.py rename to python/debug_one_pdf.py diff --git a/dump_pdfplumber.py b/python/dump_pdfplumber.py similarity index 100% rename from dump_pdfplumber.py rename to python/dump_pdfplumber.py diff --git a/dump_text.py b/python/dump_text.py similarity index 100% rename from dump_text.py rename to python/dump_text.py diff --git a/dump_values.py b/python/dump_values.py similarity index 100% rename from dump_values.py rename to python/dump_values.py diff --git a/extract_buyers_llamacpp.py b/python/extract_buyers_llamacpp.py similarity index 100% rename from extract_buyers_llamacpp.py rename to python/extract_buyers_llamacpp.py diff --git a/extract_buyers_poc.py b/python/extract_buyers_poc.py similarity index 100% rename from extract_buyers_poc.py rename to python/extract_buyers_poc.py diff --git a/inspect_poc.py b/python/inspect_poc.py similarity index 100% rename from inspect_poc.py rename to python/inspect_poc.py diff --git a/merge_buyers.py b/python/merge_buyers.py similarity index 100% rename from merge_buyers.py rename to python/merge_buyers.py diff --git a/parse_text_pdf.py b/python/parse_text_pdf.py similarity index 100% rename from parse_text_pdf.py rename to python/parse_text_pdf.py diff --git a/probe_checkbox.py b/python/probe_checkbox.py similarity index 100% rename from probe_checkbox.py rename to python/probe_checkbox.py diff --git a/probe_coords.py b/python/probe_coords.py similarity index 100% rename from probe_coords.py rename to python/probe_coords.py diff --git a/test_connection.py b/python/test_connection.py similarity index 100% rename from test_connection.py rename to python/test_connection.py diff --git a/test_connection_llamacpp.py b/python/test_connection_llamacpp.py similarity index 100% rename from test_connection_llamacpp.py rename to python/test_connection_llamacpp.py diff --git a/vision_runner.ts b/vision_runner.ts new file mode 100644 index 0000000..5d50bf5 --- /dev/null +++ b/vision_runner.ts @@ -0,0 +1,516 @@ +#!/usr/bin/env npx tsx +/** + * vision_runner.ts — Gleis B: Vision-LLM-Extraktion für Buyer Information Sheets + * + * Liest out/buyers.json (Ergebnis von Gleis A / batch_runner.ts), nimmt alle + * Einträge mit is_buyer_sheet === false, rendert die PDF-Seiten via pdftoppm + * (poppler-utils) zu PNGs und schickt sie an einen llama-server + * (OpenAI-kompatibel, Qwen3.6 + mmproj). + * + * Prinzip: Das VLM TRANSKRIBIERT nur (Rohwerte, verbatim). Sämtliche + * Normalisierung (Leer-Marker, State, down_payment, Datum) passiert + * deterministisch hier in TypeScript — identische Regeln wie Gleis A. + * + * Aufruf (AMD/Vulkan, inhouse): + * npx tsx vision_runner.ts \ + * --input out/buyers.json \ + * --pdf-root "/mnt/bizmatch-nas/AA Buyers NDA's/Buyers NDA's A-Z" \ + * --api http://localhost:8000/v1 --limit 5 + * + * Aufruf (NVIDIA/CUDA, EC2): + * npx tsx vision_runner.ts --input out/buyers.json \ + * --pdf-root ~/data --api http://localhost:8000/v1 --limit 5 + * + * Voraussetzungen: Node >= 18 (fetch), poppler-utils (pdftoppm) installiert. + * + * Outputs: + * out/buyers_vision.json — nur die Vision-Ergebnisse (Resume-Datei) + * out/buyers_merged.json — Gleis A + Gleis B zusammengeführt + */ + +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import * as fsp from "node:fs/promises"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; + +const execFileP = promisify(execFile); + +// --------------------------------------------------------------------------- +// CLI +// --------------------------------------------------------------------------- + +interface Args { + input: string; + pdfRoot: string; + api: string; + limit: number; + only: string | null; + dpi: number; + maxPages: number; + force: boolean; + outDir: string; + timeoutMs: number; +} + +function parseArgs(): Args { + const a = process.argv.slice(2); + const get = (flag: string, def: string | null = null): string | null => { + const i = a.indexOf(flag); + return i >= 0 && a[i + 1] !== undefined ? a[i + 1] : def; + }; + const input = get("--input", "out/buyers.json")!; + const pdfRoot = get("--pdf-root"); + const api = (get("--api", "http://localhost:8000/v1") || "").replace(/\/+$/, ""); + if (!pdfRoot) { + console.error("Fehler: --pdf-root ist erforderlich."); + process.exit(1); + } + return { + input, + pdfRoot: pdfRoot.replace(/^~(?=\/)/, os.homedir()), + api, + limit: parseInt(get("--limit", "0")!, 10) || 0, + only: get("--only"), + dpi: parseInt(get("--dpi", "150")!, 10) || 150, + maxPages: parseInt(get("--max-pages", "8")!, 10) || 8, + force: a.includes("--force"), + outDir: get("--out-dir", path.dirname(input))!, + timeoutMs: (parseInt(get("--timeout", "180")!, 10) || 180) * 1000, + }; +} + +// --------------------------------------------------------------------------- +// Typen +// --------------------------------------------------------------------------- + +interface BuyerRecord { + file_name: string; + is_buyer_sheet: boolean; + [k: string]: unknown; +} + +/** Rohantwort des VLM — alles verbatim, Normalisierung erfolgt in TS. */ +interface VisionRaw { + is_buyer_sheet: boolean; + info_page: number | null; + ca_page: number | null; + name_company: string | null; + prospective_buyer: string | null; + company: string | null; + phone: string | null; + cell: string | null; + email: string | null; + address: string | null; + state: string | null; + how_did_you_hear: string | null; + interested_in_updates: string | null; + types_of_business_raw: string | null; + background_experience: string | null; + total_purchase_price: string | null; + down_payment_raw: string | null; + date_of_introduction_raw: string | null; +} + +// --------------------------------------------------------------------------- +// Normalisierung — identisch zu Gleis A halten! +// (Falls BuyerSheetParser.ts diese Funktionen exportiert, stattdessen +// importieren, damit beide Gleise garantiert dieselbe Logik nutzen.) +// --------------------------------------------------------------------------- + +const NULL_MARKERS = /^(n|na|n\/a|none|nil|x|-+|\.+)$/i; + +function cleanStr(v: string | null | undefined): string | null { + if (v == null) return null; + const t = String(v).replace(/\s+/g, " ").trim(); + if (!t || NULL_MARKERS.test(t)) return null; + return t; +} + +const STATE_MAP: Record = { + alabama: "AL", alaska: "AK", arizona: "AZ", arkansas: "AR", california: "CA", + colorado: "CO", connecticut: "CT", delaware: "DE", florida: "FL", georgia: "GA", + hawaii: "HI", idaho: "ID", illinois: "IL", indiana: "IN", iowa: "IA", + kansas: "KS", kentucky: "KY", louisiana: "LA", maine: "ME", maryland: "MD", + massachusetts: "MA", michigan: "MI", minnesota: "MN", mississippi: "MS", + missouri: "MO", montana: "MT", nebraska: "NE", nevada: "NV", + "new hampshire": "NH", "new jersey": "NJ", "new mexico": "NM", + "new york": "NY", "north carolina": "NC", "north dakota": "ND", ohio: "OH", + oklahoma: "OK", oregon: "OR", pennsylvania: "PA", "rhode island": "RI", + "south carolina": "SC", "south dakota": "SD", tennessee: "TN", texas: "TX", + utah: "UT", vermont: "VT", virginia: "VA", washington: "WA", + "west virginia": "WV", wisconsin: "WI", wyoming: "WY", + "district of columbia": "DC", "washington dc": "DC", +}; +const STATE_CODES = new Set(Object.values(STATE_MAP)); + +function normState(v: string | null): string | null { + const c = cleanStr(v); + if (!c) return null; + const up = c.toUpperCase().replace(/\./g, ""); + if (up.length === 2 && STATE_CODES.has(up)) return up; + const full = STATE_MAP[c.toLowerCase().replace(/\./g, "")]; + return full ?? c; // unbekannt: bereinigt durchreichen, nicht raten +} + +/** "$350,000" → "350000"; "1.5M" → "1500000"; sonst null + raw. */ +function normDownPayment(raw: string | null): { value: string | null; raw: string | null } { + const c = cleanStr(raw); + if (!c) return { value: null, raw: null }; + const m = c.match(/^\$?\s*(\d[\d,]*(?:\.\d+)?)\s*([kKmM])?\s*$/); + if (!m) return { value: null, raw: c }; + let num = parseFloat(m[1].replace(/,/g, "")); + if (m[2]) num *= /k/i.test(m[2]) ? 1_000 : 1_000_000; + if (!Number.isFinite(num) || num <= 0) return { value: null, raw: c }; + return { value: String(Math.round(num)), raw: c }; +} + +const MONTHS: Record = { + jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6, + jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12, +}; + +function isoDate(y: number, mo: number, d: number): string | null { + if (y < 100) y += 2000; + if (y < 1990 || y > 2100 || mo < 1 || mo > 12 || d < 1 || d > 31) return null; + return `${y}-${String(mo).padStart(2, "0")}-${String(d).padStart(2, "0")}`; +} + +/** US-Formate: 6/25/26, 06-25-2026, "June 25, 2026" → YYYY-MM-DD. */ +function normDate(raw: string | null): string | null { + const c = cleanStr(raw); + if (!c) return null; + let m = c.match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2}|\d{4})$/); + if (m) return isoDate(parseInt(m[3], 10), parseInt(m[1], 10), parseInt(m[2], 10)); + m = c.match(/^([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:st|nd|rd|th)?,?\s+(\d{2}|\d{4})$/); + if (m) { + const mo = MONTHS[m[1].slice(0, 3).toLowerCase()]; + if (mo) return isoDate(parseInt(m[3], 10), mo, parseInt(m[2], 10)); + } + m = c.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (m) return isoDate(parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)); + return null; +} + +// --------------------------------------------------------------------------- +// PDF-Index (rekursiv, Basename → voller Pfad) + Rendering +// --------------------------------------------------------------------------- + +async function buildPdfIndex(root: string): Promise> { + const index = new Map(); + const stack = [root]; + while (stack.length) { + const dir = stack.pop()!; + let entries: fs.Dirent[]; + try { + entries = await fsp.readdir(dir, { withFileTypes: true }); + } catch { + continue; + } + for (const e of entries) { + const p = path.join(dir, e.name); + if (e.isDirectory()) stack.push(p); + else if (e.isFile() && e.name.toLowerCase().endsWith(".pdf")) { + if (!index.has(e.name)) index.set(e.name, p); + } + } + } + return index; +} + +async function renderPdf(pdfPath: string, dpi: number, maxPages: number, tmpDir: string): Promise { + const prefix = path.join(tmpDir, "page"); + await execFileP("pdftoppm", ["-png", "-r", String(dpi), "-l", String(maxPages), pdfPath, prefix], { + timeout: 120_000, + }); + const pageNum = (f: string) => parseInt(f.match(/-(\d+)\.png$/)?.[1] ?? "0", 10); + const files = (await fsp.readdir(tmpDir)) + .filter((f) => f.endsWith(".png")) + .sort((a, b) => pageNum(a) - pageNum(b)) + .map((f) => path.join(tmpDir, f)); + if (files.length === 0) throw new Error("pdftoppm hat keine Seiten erzeugt"); + return files; +} + +// --------------------------------------------------------------------------- +// VLM-Aufruf (llama-server, OpenAI-kompatibel, JSON-Schema) +// --------------------------------------------------------------------------- + +const nullableString = { type: ["string", "null"] }; +const nullableInt = { type: ["integer", "null"] }; + +const RESPONSE_SCHEMA = { + type: "object", + additionalProperties: false, + required: [ + "is_buyer_sheet", "info_page", "ca_page", "name_company", "prospective_buyer", + "company", "phone", "cell", "email", "address", "state", "how_did_you_hear", + "interested_in_updates", "types_of_business_raw", "background_experience", + "total_purchase_price", "down_payment_raw", "date_of_introduction_raw", + ], + properties: { + is_buyer_sheet: { type: "boolean" }, + info_page: nullableInt, + ca_page: nullableInt, + name_company: nullableString, + prospective_buyer: nullableString, + company: nullableString, + phone: nullableString, + cell: nullableString, + email: nullableString, + address: nullableString, + state: nullableString, + how_did_you_hear: nullableString, + interested_in_updates: nullableString, + types_of_business_raw: nullableString, + background_experience: nullableString, + total_purchase_price: nullableString, + down_payment_raw: nullableString, + date_of_introduction_raw: nullableString, + }, +} as const; + +const SYSTEM_PROMPT = + "You are a precise document transcription engine for scanned business forms. " + + "You transcribe handwritten and typed form fields EXACTLY as written (verbatim), " + + "including typos. You never guess, infer, or invent values. If a field is blank " + + "or unreadable, you return null."; + +const USER_PROMPT = `You see all pages of one PDF, in order (image 1 = page 1). + +Task: Decide whether this document contains a business brokerage "BUYER INFORMATION SHEET" form, and if so, transcribe its fields. + +1. is_buyer_sheet: true only if a page with the heading "BUYER INFORMATION SHEET" exists. If the document is something else (notes, listing, letter, other form), return is_buyer_sheet=false and null for every field. +2. info_page: page number (1-based) of the BUYER INFORMATION SHEET page, else null. +3. ca_page: page number of the confidentiality agreement page containing text like "PROSPECTIVE BUYER AGREES TO KEEP AND HOLD CONFIDENTIAL", else null. +4. From the info page, transcribe VERBATIM (exactly as written, do not normalize, do not expand abbreviations): + - name_company: value of the "Name/Company" line + - prospective_buyer: value of the "Prospective Buyer" line + - company: value of a separate "Company" line if present + - phone, cell, email, address, state + - how_did_you_hear: "How did you hear about us" + - interested_in_updates: answer/checkbox for receiving updates (transcribe what is marked, e.g. "Yes" or "No"), else null + - types_of_business_raw: "Type(s) of business interested in" (may span multiple lines — join with a space) + - background_experience: "Background/Experience" (may span multiple lines) + - total_purchase_price: "Total Purchase Price" as written + - down_payment_raw: "Down Payment" as written (e.g. "$350,000", "1.5M", "TBD") +5. date_of_introduction_raw: the date written next to the buyer's signature on the confidentiality agreement page, verbatim (e.g. "6/25/26"), else null. + +A blank field, "N/A", "n", or an empty line = transcribe it as written; if truly empty, use null. Return only the JSON object.`; + +async function fetchModelId(api: string): Promise { + try { + const r = await fetch(`${api}/models`); + const j = (await r.json()) as { data?: Array<{ id?: string }> }; + return j?.data?.[0]?.id ?? "unknown"; + } catch { + return "unknown"; + } +} + +async function callVision(api: string, images: string[], timeoutMs: number): Promise { + const content: Array> = [{ type: "text", text: USER_PROMPT }]; + for (const img of images) { + const b64 = await fsp.readFile(img, { encoding: "base64" }); + content.push({ type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } }); + } + const body = { + model: "qwen3.6", + temperature: 0.1, + top_p: 0.95, + max_tokens: 1500, + chat_template_kwargs: { enable_thinking: false }, + response_format: { + type: "json_schema", + json_schema: { name: "buyer_sheet", strict: true, schema: RESPONSE_SCHEMA }, + }, + messages: [ + { role: "system", content: SYSTEM_PROMPT }, + { role: "user", content }, + ], + }; + const ctl = new AbortController(); + const timer = setTimeout(() => ctl.abort(), timeoutMs); + try { + const r = await fetch(`${api}/chat/completions`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + signal: ctl.signal, + }); + if (!r.ok) throw new Error(`HTTP ${r.status}: ${(await r.text()).slice(0, 300)}`); + const j = (await r.json()) as { choices?: Array<{ message?: { content?: string } }> }; + const text = j?.choices?.[0]?.message?.content; + if (!text) throw new Error("Leere Antwort vom Server"); + return JSON.parse(text) as VisionRaw; + } finally { + clearTimeout(timer); + } +} + +// --------------------------------------------------------------------------- +// Merge: deterministischer Datensatz + Vision-Rohwerte → Zielstruktur +// --------------------------------------------------------------------------- + +function mergeRecord(det: BuyerRecord, vis: VisionRaw, model: string): BuyerRecord { + const base: BuyerRecord = { + ...det, + _parser: "vision", + _vision_model: model, + _vision_error: undefined, + }; + delete (base as Record)["_vision_error"]; + + if (!vis.is_buyer_sheet) { + return { ...base, is_buyer_sheet: false, _info_page: null, _ca_page: null }; + } + + const dp = normDownPayment(vis.down_payment_raw); + return { + ...base, + is_buyer_sheet: true, + name_company: cleanStr(vis.name_company), + prospective_buyer: cleanStr(vis.prospective_buyer), + company: cleanStr(vis.company), + phone: cleanStr(vis.phone), + cell: cleanStr(vis.cell), + email: cleanStr(vis.email), + address: cleanStr(vis.address), + state: normState(vis.state), + how_did_you_hear: cleanStr(vis.how_did_you_hear), + interested_in_updates: cleanStr(vis.interested_in_updates), + types_of_business_raw: cleanStr(vis.types_of_business_raw), + background_experience: cleanStr(vis.background_experience), + total_purchase_price: cleanStr(vis.total_purchase_price), + down_payment: dp.value, + down_payment_raw: dp.raw, + // Vision-Datum bevorzugt; Fallback: deterministischer Wert (z.B. aus Dateinamen) + date_of_introduction: normDate(vis.date_of_introduction_raw) ?? (det.date_of_introduction as string | null) ?? null, + _info_page: vis.info_page, + _ca_page: vis.ca_page, + }; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +const RETRIES = 3; + +function progress(line: string): void { + const cols = process.stderr.columns ?? 120; + process.stderr.write("\r" + line.slice(0, cols - 1).padEnd(cols - 1)); +} + +async function main(): Promise { + const args = parseArgs(); + + // pdftoppm vorhanden? + try { + await execFileP("pdftoppm", ["-v"]); + } catch { + console.error("Fehler: pdftoppm nicht gefunden. Installieren: sudo apt install poppler-utils"); + process.exit(1); + } + + const all: BuyerRecord[] = JSON.parse(await fsp.readFile(args.input, "utf8")); + let targets = all.filter((r) => r.is_buyer_sheet === false); + if (args.only) targets = targets.filter((r) => r.file_name === args.only); + if (args.limit > 0) targets = targets.slice(0, args.limit); + + const visionPath = path.join(args.outDir, "buyers_vision.json"); + const mergedPath = path.join(args.outDir, "buyers_merged.json"); + const done = new Map(); + try { + const prev: BuyerRecord[] = JSON.parse(await fsp.readFile(visionPath, "utf8")); + for (const r of prev) done.set(r.file_name, r); + } catch { + /* kein Resume-Stand */ + } + + console.error(`Indexiere PDFs unter ${args.pdfRoot} ...`); + const pdfIndex = await buildPdfIndex(args.pdfRoot); + console.error(`${pdfIndex.size} PDFs gefunden. ${targets.length} Einträge zu verarbeiten.`); + + const model = await fetchModelId(args.api); + console.error(`Modell: ${model} @ ${args.api}`); + + let ok = 0, notSheet = 0, errors = 0, skipped = 0; + + for (let i = 0; i < targets.length; i++) { + const det = targets[i]; + const tag = `[${i + 1}/${targets.length}] ${det.file_name}`; + + const prev = done.get(det.file_name); + if (prev && !prev["_vision_error"] && !args.force) { + skipped++; + progress(`${tag} … übersprungen (bereits verarbeitet)`); + continue; + } + + const pdfPath = pdfIndex.get(det.file_name); + if (!pdfPath) { + done.set(det.file_name, { ...det, _vision_error: "PDF nicht gefunden" }); + errors++; + progress(`${tag} … FEHLER: PDF nicht gefunden`); + continue; + } + + const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "bvs-")); + try { + progress(`${tag} … rendere`); + const images = await renderPdf(pdfPath, args.dpi, args.maxPages, tmpDir); + + let vis: VisionRaw | null = null; + let lastErr = ""; + for (let attempt = 1; attempt <= RETRIES; attempt++) { + try { + progress(`${tag} … VLM (${images.length} Seiten, Versuch ${attempt})`); + vis = await callVision(args.api, images, args.timeoutMs); + break; + } catch (e) { + lastErr = e instanceof Error ? e.message : String(e); + if (attempt < RETRIES) await new Promise((res) => setTimeout(res, 5000 * attempt)); + } + } + + if (!vis) { + done.set(det.file_name, { ...det, _vision_error: lastErr }); + errors++; + progress(`${tag} … FEHLER: ${lastErr}`); + } else { + const merged = mergeRecord(det, vis, model); + done.set(det.file_name, merged); + if (merged.is_buyer_sheet) { ok++; progress(`${tag} … OK`); } + else { notSheet++; progress(`${tag} … kein Buyer Sheet`); } + } + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + done.set(det.file_name, { ...det, _vision_error: msg }); + errors++; + progress(`${tag} … FEHLER: ${msg}`); + } finally { + await fsp.rm(tmpDir, { recursive: true, force: true }); + } + + // Inkrementell sichern (Resume-fähig) + await fsp.mkdir(args.outDir, { recursive: true }); + await fsp.writeFile(visionPath, JSON.stringify([...done.values()], null, 2)); + } + + // Merge: Gleis A + Gleis B + const merged = all.map((r) => done.get(r.file_name) ?? r); + await fsp.writeFile(mergedPath, JSON.stringify(merged, null, 2)); + + process.stderr.write("\n"); + console.error( + `Fertig. OK: ${ok}, kein Buyer Sheet: ${notSheet}, Fehler: ${errors}, übersprungen: ${skipped}` + ); + console.error(`→ ${visionPath}\n→ ${mergedPath}`); +} + +main().catch((e) => { + console.error("\nAbbruch:", e instanceof Error ? e.message : e); + process.exit(1); +}); \ No newline at end of file