Python pipeline · German emails

AI Privacy Filter

I built a Python pipeline that replaces personal data in German emails with placeholders, then restores the values in the reply.

  • Python
  • Presidio
  • FastAPI
  • n8n

All examples are synthetic. No real customer emails appear on this page.

Measured, with limits

Measured once on held-out test data. No value here was tuned on the test split.

Detection can miss values. Read the results with their limits.

See results and limits

What I built

Detection with Presidio and spaCy, typed placeholders, a second-pass guard, interchangeable AI backends and a human review step in n8n.

Try the filter

Paste a German email and replace personal data with placeholders. This simplified filter runs in your browser tab; it is not the server pipeline.

Email, German phone, IBAN with checksum, postcode + city, street + number, URL, German first names and salutation cues.

What the AI would see
The result appears here after you press the button.

This box makes no network requests. It runs only the checks listed on the left.

An email, end to end

One synthetic quote request, shown end to end. Highlighted text is personal data; the middle column is all the model receives.

The filter is built for German text; the interface is available in English and German.

What it caught and missed

Numbers from the test runs behind this project. The datasets and the limits follow below.

Measured once on held-out test data. No value here was tuned on the test split.

Results and limits

One test split, measured once. Misses are reported as they are.

Detection

A labelled span counts as caught only when every character is covered by a compatible prediction.

Dataset Recall Missed spans

GermEval 2014 is used only to compute detection rates; no sentences are shown.

Reply drafting

Every backend answered the same enquiry set.

Backend Valid JSON Category Urgency Median reply Calls

What this does not catch

Known limits, shown next to the numbers.

Limit What happens Mitigation

Not legal advice

This page describes a technical filter. It is not legal advice and does not replace a review of your processing under data-protection law.

Pseudonymisation keeps the link

The filter replaces personal data with placeholders. The original values still exist in the local map and can be restored, so the result stays linked to the person.

Interested in using this in your organisation? Get in touch.

The pipeline

Personal data is replaced before the request crosses to the AI backend. The placeholder map never leaves the local process.

Five-step flow: detect, replace, guard, AI draft, restore 1 2 3 4 5 Detect Replace Guard AI draft Restore names, emails, IBANs with [TYPE_n] re-scan before sending on pseudonymised text real values back Five-step flow: detect, replace, guard, AI draft, restore 1 2 3 4 5 Detect Replace Guard AI draft Restore names, emails, IBANs with [TYPE_n] re-scan before sending on pseudonymised text real values back
On your machine Network call

Step 4 is the only one that talks to an AI backend. The guard re-scans the pseudonymised text and blocks the call if a mapped value is still there.

A person approves every reply

In n8n, an enquiry is pseudonymised, checked by the guard and drafted. A reviewer sees the pseudonymised text, the category, the urgency and the restored draft, then approves, edits or rejects it.

Workflow canvas after one demo run: enquiry in, pseudonymise, guard, draft, review, write the result. Both branches finish green.

Implementation details

Architecture, detection stack, guard, backends and the tests behind the privacy claims.

Architecture

A FastAPI service receives an enquiry, runs detection, replaces values with typed placeholders, re-scans the result, calls one AI backend, and restores the placeholders in the returned draft. n8n triggers the flow; the placeholder map stays in memory and is never written to the page data.

Detection stack

Microsoft Presidio and spaCy handle names and organisations. Regular expressions with checksums handle emails, German phone numbers, IBANs, postcodes with city, street with number, URLs and VAT IDs. The browser demo adds a Faker de_DE first-name list and salutation cues.

The guard

Before any AI call, a second pass compares the pseudonymised text with the values found in the original. It checks normalised originals and digit-only forms for phone numbers and IBANs. On a leak the call is blocked and the verdict names the leaked types, never the values.

AI backends

Four backends behind one interface: Claude and Codex through their CLIs, a BYOK chat-completions endpoint, and a local Ollama model. The results table records every call of the reply-writing test.

No-network test for the demo

The try-it box was checked with browser network logging: typing text and pressing the button made no requests. The claim covers that box only; the rest of this page loads its two JSON files once.

Test data and reporting

All enquiries on this page are synthetic, built from hand-written templates and Faker de_DE values. GermEval 2014 is downloaded by a script, git-ignored, never shown and never sent to a model; only detection rates are published. Labelled misses are reported as they are.

Reproducibility

The page data carries the run's version, commit, timestamp and SHA-256. A small assertion script checks that every KPI and every charted value equals the value in kpis.json and charts/*.json.

Sources and credits

Open tools and one research dataset, with the licence and the limit of each.

GermEval 2014 German Wikipedia and news text. Used only to compute detection rates; no sentences shown. CC BY 4.0
Microsoft Presidio PII detection for names, organisations and more. MIT
spaCy German NLP with the de_core_news_lg model behind Presidio. MIT
Faker Synthetic German names, addresses and contact data; the de_DE first-name export powers the browser demo. MIT
n8n Workflow glue and the human review step. Sustainable Use License
Ollama Local model runtime, used only for the optional local run. MIT
Service and client FastAPI (MIT), pydantic (MIT), httpx (BSD-3-Clause), uvicorn (BSD-3-Clause). MIT / BSD-3-Clause
Runtime libraries Starlette (BSD-3-Clause), AnyIO (MIT), phonenumbers (Apache-2.0), tldextract (BSD-3-Clause), requests (Apache-2.0), numpy (BSD-3-Clause and other open licences), PyYAML (MIT). MIT / BSD / Apache-2.0
Development tools pytest, pytest-cov and pytest-socket (MIT), hypothesis (MPL-2.0), ruff and mypy (MIT), uv and uv_build (MIT OR Apache-2.0), gitleaks (MIT). MIT / MPL-2.0 / Apache-2.0
Ported adapter pattern SaberOps worker adapters, the origin of the backend layer; attribution headers are retained. Apache-2.0
Local model Qwen2.5 1.5B Instruct, downloaded at run time by Ollama; not redistributed here. Apache-2.0
Icons and illustrations Inline SVG adapted from the portfolio source, including its Feather GitHub icon. No external images or icon script. Feather: MIT · Other SVG: this page
IBM Plex Sans + Inter Plex Sans for the page; Inter for the portfolio author mark. Both typefaces are self-hosted. SIL OFL 1.1

n8n workflow canvas

Scroll or pinch to zoom; drag to move. Double tap to fit or enlarge. Use + and − to zoom, 0 to fit, and Escape to close. Tap outside the image to close.