syncova-policies/scripts/enrich-legal-basis.js
Weapie 1323406e7a DSGVO-Rechtsgrundlage zu jeder Erklärung ergänzen
Art. 13 Abs. 1 lit. c DSGVO verlangt die Rechtsgrundlage der Verarbeitung.
Die Liste nannte durchgehend nur die Fachnorm ("§ 64 LBO"), also die
Aufgabe, nicht die datenschutzrechtliche Erlaubnis. Ergänzt wird je nach
Art der Verarbeitung:

  91x hoheitlich       Art. 6 Abs. 1 lit. e DSGVO i. V. m. § 3 LDSG SH
   9x Beschaeftigung   Art. 6 Abs. 1 lit. b DSGVO i. V. m. § 15 LDSG SH
   6x Vertrag          Art. 6 Abs. 1 lit. b DSGVO
   2x Einwilligung     Art. 6 Abs. 1 lit. a DSGVO, mit Hinweis auf Art. 7 Abs. 3

Der vom Amt angegebene Text bleibt unveraendert darunter stehen. Die 13
Erklaerungen ganz ohne Angabe wurden nicht angefasst: Eine fehlende
Grundlage zu erfinden ist etwas anderes, als eine vorhandene zu
vervollstaendigen.

Wiederholbar, und mit `npm run import-csv` wieder auf die Rohwerte der
Liste zurueckzusetzen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:16:53 +02:00

169 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Ergänzt die Rechtsgrundlage jeder Erklärung um die DSGVO-Grundlage.
*
* node scripts/enrich-legal-basis.js --dry-run
* node scripts/enrich-legal-basis.js
*
* Art. 13 Abs. 1 lit. c DSGVO verlangt „die Rechtsgrundlagen der Verarbeitung".
* Gemeint ist die Erlaubnisnorm der DSGVO selbst – die Fachnorm allein (etwa
* „§ 64 LBO") benennt nur die Aufgabe, nicht die datenschutzrechtliche
* Grundlage. Für Behörden in Schleswig-Holstein ist das:
*
* hoheitliches Handeln Art. 6 Abs. 1 lit. e DSGVO i. V. m. § 3 LDSG SH
* Beschäftigtendaten Art. 6 Abs. 1 lit. b DSGVO i. V. m. § 15 LDSG SH
* Einwilligung Art. 6 Abs. 1 lit. a DSGVO
* Vertrag Art. 6 Abs. 1 lit. b DSGVO
*
* Der vom Amt angegebene Text bleibt unverändert erhalten und wird nur
* darunter gesetzt. Erklärungen ohne jede Angabe werden NICHT ergänzt – dort
* eine Grundlage zu erfinden wäre etwas anderes als eine vorhandene zu
* vervollständigen. Sie stehen im Bericht.
*
* Der Lauf ist wiederholbar: Bereits ergänzte Texte werden übersprungen.
* Rückgängig machen lässt er sich mit `npm run import-csv`, das die
* Rohwerte der Liste wiederherstellt.
*/
const fs = require("fs")
const path = require("path")
const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
const JSON_DIR = path.join(process.cwd(), "data", "policies")
const GRUNDLAGEN = {
hoheitlich: {
kopf: "**Art. 6 Abs. 1 lit. e DSGVO** i. V. m. **§ 3 LDSG SH** – Verarbeitung zur Wahrnehmung einer Aufgabe, die im öffentlichen Interesse liegt oder in Ausübung öffentlicher Gewalt erfolgt.",
label: "hoheitliche Aufgabe",
},
beschaeftigung: {
kopf: "**Art. 6 Abs. 1 lit. b DSGVO** i. V. m. **§ 15 LDSG SH** – Verarbeitung für Zwecke des Beschäftigungsverhältnisses.",
label: "Beschäftigungsverhältnis",
},
einwilligung: {
kopf: "**Art. 6 Abs. 1 lit. a DSGVO** – Einwilligung der betroffenen Person. Die Einwilligung kann jederzeit mit Wirkung für die Zukunft widerrufen werden (Art. 7 Abs. 3 DSGVO).",
label: "Einwilligung",
},
vertrag: {
kopf: "**Art. 6 Abs. 1 lit. b DSGVO** – Erfüllung eines Vertrags oder Durchführung vorvertraglicher Maßnahmen.",
label: "Vertrag",
},
}
/** Liest Fachbereich und Fachdienst aus der Bezeichnung der Tätigkeit. */
function organisation(beschreibung) {
const lies = (feld) => {
const treffer = new RegExp(`^${feld}: (.+)$`, "m").exec(beschreibung || "")
return treffer ? treffer[1].trim() : ""
}
return { bereich: lies("Fachbereich"), dienst: lies("Fachdienst") }
}
/** Ordnet einer Erklärung die zutreffende DSGVO-Grundlage zu. */
function einordnen(policy) {
const norm = (policy.legalBasis || "").toLowerCase()
const { bereich, dienst } = organisation(policy.processingDescription)
// Personalangelegenheiten zuerst: Sie nennen oft BGB/TVöD und würden sonst
// als reine Vertragsverhältnisse durchgehen.
if (dienst === "Personalamt" || bereich === "LVB") return "beschaeftigung"
if (norm.includes("einwilligung")) return "einwilligung"
// Privatrechtliches Handeln der Gemeinde
if (/(^|[^a-z])(miet|pacht|kauf)vertrag/.test(norm)) return "vertrag"
if (norm.includes("§ 311b bgb")) return "vertrag"
if (norm.includes("vereinbarung der eigentümer")) return "vertrag"
return "hoheitlich"
}
async function main() {
const dryRun = process.argv.includes("--dry-run")
const policies = await prisma.privacyPolicy.findMany({ orderBy: { title: "asc" } })
const geplant = []
const ohneAngabe = []
const schonErgaenzt = []
for (const policy of policies) {
const bestand = (policy.legalBasis || "").trim()
if (!bestand) {
ohneAngabe.push(policy)
continue
}
if (bestand.includes("DSGVO")) {
schonErgaenzt.push(policy)
continue
}
const art = einordnen(policy)
const neu = `${GRUNDLAGEN[art].kopf}\n\nKonkrete Rechtsgrundlage: ${bestand}`
geplant.push({ policy, art, alt: bestand, neu })
}
const nachArt = {}
geplant.forEach((g) => {
nachArt[g.art] = nachArt[g.art] || []
nachArt[g.art].push(g)
})
console.log(`Erklärungen gesamt: ${policies.length}`)
console.log(`Zu ergänzen: ${geplant.length}`)
console.log(`Bereits ergänzt: ${schonErgaenzt.length}`)
console.log(`Ohne jede Angabe: ${ohneAngabe.length} (werden nicht angefasst)`)
console.log("")
for (const [art, liste] of Object.entries(nachArt)) {
console.log(`=== ${GRUNDLAGEN[art].label} (${liste.length}) ===`)
console.log(` ${GRUNDLAGEN[art].kopf.replace(/\*\*/g, "")}`)
if (art !== "hoheitlich") {
liste.forEach((g) => console.log(` ${g.policy.title} – „${g.alt}"`))
}
console.log("")
}
console.log(`=== Ohne Rechtsgrundlage – bleiben offen (${ohneAngabe.length}) ===`)
ohneAngabe.forEach((p) => console.log(` ${p.title}`))
console.log("")
if (dryRun) {
console.log("--dry-run: nichts geschrieben.")
if (geplant.length) {
console.log("")
console.log("Beispiel:")
console.log(" vorher : " + geplant[0].alt)
console.log(" nachher: " + geplant[0].neu.replace(/\n/g, "\n "))
}
return
}
for (const { policy, neu } of geplant) {
await prisma.privacyPolicy.update({
where: { id: policy.id },
data: { legalBasis: neu },
})
// JSON-Ablage nachziehen, soweit vorhanden und beschreibbar
const datei = path.join(JSON_DIR, `${policy.slug}.json`)
try {
if (fs.existsSync(datei)) {
const inhalt = JSON.parse(fs.readFileSync(datei, "utf8"))
inhalt.legalBasis = neu
fs.writeFileSync(datei, JSON.stringify(inhalt, null, 2), "utf8")
}
} catch {
// Die Datenbank ist maßgeblich; die Ablage ist nur Nachweis.
}
}
console.log(`Ergänzt: ${geplant.length}`)
}
main()
.catch((e) => {
console.error(e)
process.exitCode = 1
})
.finally(() => prisma.$disconnect())