150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
"""
|
||
Generiert synthetische deutsche Texte mit PII-Daten via OpenAI.
|
||
Speichert das Ergebnis als JSON.
|
||
|
||
Usage:
|
||
pip install openai python-dotenv
|
||
set OPENAI_API_KEY=sk-...
|
||
python generate_pii_texts.py
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import random
|
||
from pathlib import Path
|
||
from openai import OpenAI
|
||
from dotenv import load_dotenv
|
||
|
||
load_dotenv()
|
||
|
||
SYSTEM_PROMPT = """\
|
||
Du bist ein Testdaten-Generator. Du erzeugst realistische deutsche Texte \
|
||
die personenbezogene Daten (PII) enthalten. Alle Daten sind FIKTIV.
|
||
|
||
Wichtig:
|
||
- Sei KREATIV bei den PII-Typen. Nicht nur Name+Email, sondern auch:
|
||
Geburtsdaten, Steuernummern, Kennzeichen, Kundennummern, IP-Adressen,
|
||
Benutzernamen, Bankverbindungen, Sozialversicherungsnummern, Adressen,
|
||
Telefonnummern, Kreditkarten, Personalausweisnummern, Reisepassnummern,
|
||
Führerscheinnummern, Krankenkassennummern, URLs mit PII-Parametern,
|
||
Geburtsorte, Nationalitäten, Religionszugehörigkeit usw.
|
||
- Baue EDGE CASES ein:
|
||
* PII in Tabellenform, Aufzählungen, Signaturen
|
||
* Abgekürzte Namen (M. Müller), Spitznamen, Doppelnamen
|
||
* Telefonnummern in verschiedenen Formaten (+49, 0049, 089/, mit/ohne Leerzeichen)
|
||
* Adressen in verschiedenen Formaten (mit/ohne Bundesland, Postfach, c/o)
|
||
* Teilweise geschwärzte Daten (DE** **** **** 1234 56)
|
||
* PII eingebettet in Code-Snippets, Log-Dateien, SQL-Queries
|
||
* PII in E-Mail-Headern, Chat-Verläufen
|
||
* Mehrere Personen im selben Text
|
||
* PII die sich über Zeilenumbrüche erstreckt
|
||
* Tippfehler in PII-nahen Kontexten
|
||
* IBANs/Kartennummern mit und ohne Leerzeichen
|
||
- Variiere die Textarten: Emails, Chatverläufe, Briefe, Logs, Formulare,
|
||
Berichte, Notizen, Protokolle, Tickets, Datenbankeinträge
|
||
"""
|
||
|
||
SCENARIOS = [
|
||
"Kundenanfrage per Email mit Signatur",
|
||
"IT-Support-Ticket mit Log-Auszügen",
|
||
"Internes Chat-Protokoll zwischen Kollegen",
|
||
"Behördliches Schreiben / Bescheid",
|
||
"Arztbrief mit Patientendaten",
|
||
"Polizeibericht / Unfallprotokoll",
|
||
"Bewerbung mit Lebenslauf-Auszügen",
|
||
"Bankmitteilung / Kontoeröffnung",
|
||
"Versicherungsmeldung / Schadensfall",
|
||
"Datenbankexport / CSV-artige Daten mit Kopfzeile",
|
||
"Handschriftliche Notiz (transkribiert) mit PII",
|
||
"Server-Logfile mit User-Daten und IPs",
|
||
"SQL-Query oder Code-Snippet mit hardkodierten PII",
|
||
"Meeting-Protokoll mit Teilnehmerliste",
|
||
"Mietvertrag / Wohnungsübergabe-Protokoll",
|
||
]
|
||
|
||
|
||
def generate(count=5, scenario=None, model="gpt-4o"):
|
||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||
scenario = scenario or random.choice(SCENARIOS)
|
||
|
||
user_prompt = f"""\
|
||
Generiere {count} verschiedene deutsche Texte (je 50-250 Wörter).
|
||
Szenario: {scenario}
|
||
|
||
Jeder Text soll VERSCHIEDENE PII-Typen enthalten – nicht immer die gleichen.
|
||
Baue bewusst Edge Cases und ungewöhnliche Formate ein.
|
||
|
||
Antwort als JSON:
|
||
{{
|
||
"samples": [
|
||
{{
|
||
"text": "...",
|
||
"pii_spans": [
|
||
{{"type": "PERSON", "value": "Max Müller", "start": 10, "end": 20}},
|
||
{{"type": "PHONE", "value": "+49 170 1234567", "start": 55, "end": 70}}
|
||
],
|
||
"scenario": "Kundenanfrage"
|
||
}}
|
||
]
|
||
}}
|
||
|
||
Regeln für pii_spans:
|
||
- type: freier String, z.B. PERSON, EMAIL, PHONE, IBAN, ADDRESS, DATE_OF_BIRTH,
|
||
LICENSE_PLATE, IP_ADDRESS, CREDIT_CARD, TAX_ID, SSN, ID_CARD, PASSPORT, URL, USERNAME, ...
|
||
- start/end: Zeichenpositionen im text (0-basiert). MÜSSEN exakt dem value entsprechen.
|
||
- JEDE PII im Text muss als span annotiert sein.
|
||
"""
|
||
|
||
print(f" → Generiere {count}x '{scenario}' ...")
|
||
|
||
resp = client.chat.completions.create(
|
||
model=model,
|
||
messages=[
|
||
{"role": "system", "content": SYSTEM_PROMPT},
|
||
{"role": "user", "content": user_prompt},
|
||
],
|
||
temperature=1.0,
|
||
response_format={"type": "json_object"},
|
||
)
|
||
|
||
raw = json.loads(resp.choices[0].message.content or "{}")
|
||
samples = raw.get("samples", [])
|
||
|
||
# Fix offsets: LLMs liegen oft daneben bei start/end
|
||
for s in samples:
|
||
for span in s.get("pii_spans", []):
|
||
val = span.get("value", "")
|
||
idx = s["text"].find(val)
|
||
if idx != -1:
|
||
span["start"] = idx
|
||
span["end"] = idx + len(val)
|
||
|
||
return samples
|
||
|
||
|
||
def main():
|
||
total = 50
|
||
batch_size = 5
|
||
all_samples = []
|
||
|
||
print(f"Generiere {total} synthetische PII-Texte...\n")
|
||
|
||
for i in range(0, total, batch_size):
|
||
scenario = random.choice(SCENARIOS)
|
||
n = min(batch_size, total - i)
|
||
try:
|
||
batch = generate(count=n, scenario=scenario)
|
||
all_samples.extend(batch)
|
||
print(f" ✓ {len(all_samples)}/{total} fertig")
|
||
except Exception as e:
|
||
print(f" ✗ Fehler: {e}")
|
||
|
||
out = Path("output/synthetic_pii_data.json")
|
||
out.parent.mkdir(exist_ok=True)
|
||
out.write_text(json.dumps(all_samples, indent=2, ensure_ascii=False), encoding="utf-8")
|
||
print(f"\n✅ {len(all_samples)} Texte gespeichert → {out}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|