/** * Vergleicht die DSE-Gesamtliste (CSV) mit den bisherigen Merkblatt-PDFs. * Schreibt nichts – dient nur der Prüfung vor einem Import. * * node scripts/compare-csv-pdf.js Zusammenfassung * node scripts/compare-csv-pdf.js --detail jede Abweichung im Wortlaut */ const fs = require("fs") const path = require("path") const { readPolicyCsv } = require("./lib/csv-policy") const { parsePolicyPdf, reflow, joinTitle } = require("./lib/pdf-policy") const CSV = path.join("data", "source", "DSE_Gesamtliste_Amt_Leezen.CSV") const PDF_DIR = path.join("data", "import") const H = { activity: "Bezeichnung der Verarbeitungstätigkeit", purpose: "Ihre Daten werden zu folgendem Zweck erhoben", legal: "Ihre Daten wurden aufgrund folgender Rechtsgrundlage erhoben", obligation: "Ihre Pflicht zur Bereitstellung der Daten", consequences: "Folgen, wenn Sie die Daten nicht angeben", recipients: "Wir beabsichtigen, Ihre Daten an folgende Empfänger weiterzuleiten", storage: "Dauer der Speicherung der personenbezogenen Daten", source: "Wir haben Ihre Daten aus folgender Quelle erhalten", } const C = { title: "Formular-Bezeichnung", purpose: "Verarbeitungszweck", legal: "Rechtsgrundlage f.d. Datenerhebung", obligation: "Pflicht zur Bereitstellung der Daten", consequences: "Folgen, wenn Daten nicht angeben werden", recipients: "Empfänger f. d. beabsichtigte Weitergabe der Daten", source: "Datenquelle (bei Dritterhebung)", storage: "Dauer der Speicherung (Löschfristen)", } const FIELDS = [ ["title", C.title, H.activity], ["purpose", C.purpose, H.purpose], ["legal", C.legal, H.legal], ["obligation", C.obligation, H.obligation], ["consequences", C.consequences, H.consequences], ["recipients", C.recipients, H.recipients], ["source", C.source, H.source], ["storage", C.storage, H.storage], ] /** Vergleichsform: Groß-/Kleinschreibung, Leerraum und Satzzeichen egal. */ function norm(value) { return String(value ?? "") .replace(/\s+/g, " ") .replace(/[.,;:]+$/g, "") .trim() .toLowerCase() } const block = (lines) => (lines && lines.length ? reflow(lines).join("\n") : "") async function main() { const detail = process.argv.includes("--detail") const { records } = readPolicyCsv(CSV) const pdfFiles = fs .readdirSync(PDF_DIR) .filter((f) => f.toLowerCase().endsWith(".pdf")) const pdfByNumber = new Map(pdfFiles.map((f) => [f.replace(/\.pdf$/i, ""), f])) const stats = {} FIELDS.forEach(([key]) => { stats[key] = { gleich: 0, nurCsv: 0, nurPdf: 0, unterschiedlich: 0, beideLeer: 0 } }) const diffs = [] for (const record of records) { const nummer = record["Formularnummer"] const file = pdfByNumber.get(nummer) if (!file) continue const parsed = await parsePolicyPdf(path.join(PDF_DIR, file)) for (const [key, csvCol, pdfHeading] of FIELDS) { const csvValue = (record[csvCol] || "").trim() const pdfValue = key === "title" ? joinTitle(parsed.sections[pdfHeading] || []) : block(parsed.sections[pdfHeading]) const c = norm(csvValue) const p = norm(pdfValue) if (!c && !p) stats[key].beideLeer++ else if (c && !p) { stats[key].nurCsv++ diffs.push({ nummer, key, art: "nur CSV", csvValue, pdfValue }) } else if (!c && p) { stats[key].nurPdf++ diffs.push({ nummer, key, art: "nur PDF", csvValue, pdfValue }) } else if (c === p) stats[key].gleich++ else { stats[key].unterschiedlich++ diffs.push({ nummer, key, art: "unterschiedlich", csvValue, pdfValue }) } } } console.log("Abgeglichene Datensätze:", records.filter((r) => pdfByNumber.has(r["Formularnummer"])).length) console.log("") console.log("Feld gleich nur CSV nur PDF abweichend beide leer") console.log("─".repeat(68)) for (const [key] of FIELDS) { const s = stats[key] console.log( key.padEnd(15) + String(s.gleich).padStart(6) + String(s.nurCsv).padStart(9) + String(s.nurPdf).padStart(9) + String(s.unterschiedlich).padStart(12) + String(s.beideLeer).padStart(12) ) } console.log("") console.log("=== Inhaltliche Abweichungen (weder nur-CSV noch nur-PDF) ===") const echte = diffs.filter((d) => d.art === "unterschiedlich") console.log(`${echte.length} Fälle`) for (const d of echte) { console.log(`\n ${d.nummer} · ${d.key}`) console.log(` CSV: ${d.csvValue.replace(/\n/g, " ⏎ ").slice(0, 200)}`) console.log(` PDF: ${d.pdfValue.replace(/\n/g, " ⏎ ").slice(0, 200)}`) } console.log("") console.log("=== Nur im PDF vorhanden (ginge beim CSV-Import verloren) ===") const verlust = diffs.filter((d) => d.art === "nur PDF") console.log(`${verlust.length} Fälle`) const nachFeld = {} verlust.forEach((d) => { nachFeld[d.key] = nachFeld[d.key] || [] nachFeld[d.key].push(d) }) for (const [key, list] of Object.entries(nachFeld)) { console.log(`\n ${key} (${list.length}):`) const zeigen = detail ? list : list.slice(0, 5) zeigen.forEach((d) => console.log(` ${d.nummer}: ${d.pdfValue.replace(/\n/g, " ⏎ ").slice(0, 150)}`) ) if (!detail && list.length > 5) console.log(` … und ${list.length - 5} weitere`) } } main().catch((e) => { console.error(e) process.exitCode = 1 })