Inspiration
Lawyers, fiduciaries and compliance teams in Switzerland and Italy want to use AI assistants on client files, but data-protection law and professional secrecy make sending client data to a cloud service risky. Simple anonymisers catch IBANs and tax numbers but miss names in free text, and almost nothing catches indirect identifiers: details that point to one person without naming them, like "the only family doctor in Bosco Gurin". I wanted a tool that removes all of that locally, lets you use any AI on the safe text, and then puts the real values back.
What it does
- Paste a document in Italian, German or French (three sample letters are included).
- Riserbo finds the personal data: names, Swiss AHV numbers, IBANs, Italian tax codes, addresses, phone numbers, health details and indirect identifiers. Each hit gets a type and a short reason.
- Every hit becomes a placeholder such as
[PERSON_1],[IBAN_1]or[QUASI_1]. Only this redacted text is shared with an external AI. - Paste the AI's answer back: Riserbo restores the real values locally. The mapping stays in the browser page and is never written to disk.
How I built it
I planned the project with the Devpost Learn skill pack (scope, PRD, spec and build checklist are in devpost/ in the repository) and built it slice by slice with an AI coding agent.
- Rules with checksums find identifiers that have a strict format (AHV/AVS, IBAN, Swiss UID, codice fiscale, partita IVA, cards, e-mail, phone, dates of birth, addresses) plus names after titles, signatories and relatives. A number is only redacted when its checksum is valid, so invoice numbers and amounts stay readable.
- Apertus 1.5 8B, the open Swiss language model, runs locally (4-bit GGUF) behind llama.cpp. It makes two focused passes on each short chunk: one for people, organisations and addresses, one for health data and indirect identifiers. Its output is forced into a JSON schema, and every span must appear word for word in the document, so invented names are dropped.
- Merge and placeholders: checksum rules win, otherwise the longer span wins, and the same value always gets the same placeholder.
- Docker:
make runstarts the app and the model. The model container sits on an internal network with no internet access, so the whole tool works on-premise or air-gapped. - Evaluation: a seeded generator creates 60 synthetic documents (20 per language) with 594 labelled items and distractors that must stay visible. On this set, rules alone leave 55.0% of personal-data characters readable; Riserbo leaves 12.6% and is the only setup that finds indirect identifiers.
Challenges I ran into
- With a single extraction prompt, the 4-bit model sometimes returned an empty list on a chunk that clearly contained names. Splitting the work into two smaller passes and adding a "NONE" item to the JSON schema made the output stable across repeated runs.
- The model's choices shifted slightly between two inference backends, so a name in a signature or greeting line was sometimes missed. I moved those cases to deterministic rules, which raised person recall from 90.0% to 93.3%.
- On a CPU-only machine one page takes a few minutes; with a consumer GPU it takes about 25 seconds.
Accomplishments that I'm proud of
- It works end to end, fully offline, with one command, and I measured it instead of trusting a demo.
- It finds indirect identifiers, which neither the rules nor the small reference tagger I compared against could find.
- All test data is synthetic and the whole evaluation is reproducible with
make eval.
What I learned
- How to run an open-weights model fully locally in Docker and check that it really has no internet access.
- That evaluation should drive prompt design: the first prompt looked fine on one example and failed on others, and only a test set showed it.
- That planning first (scope → PRD → spec → checklist) keeps an AI coding agent on track: decisions were written down, so later fixes did not undo earlier ones.
- Leak rate, precision and recall as practical metrics for a privacy tool.
What's next for Riserbo
- Import PDF and Word files and export a truly redacted file.
- Test on real, consented documents with a partner law firm, and add Romansh and Swiss-German test sets.
- A second "verifier" pass in which the model re-reads the redacted text and flags anything still identifying.
Pre-existing work and AI use
- The repository skeleton comes from the Hack Apertus project template (Apache-2.0), and
.agents/skills/is the Devpost Learn skill pack. The same project is also entered in the Hack Apertus online hackathon (Track 2B). - Model: Apertus 1.5 8B by the Swiss AI Initiative (Apache-2.0), served with llama.cpp (MIT).
- The idea of reversible placeholders was inspired by rizzo-pii (MIT); no code was copied. Its small model is used only as a comparison point in the evaluation.
- The code was written with an AI coding agent. In the demo video, the narration is my own voice cloned with ElevenLabs, and the short landscape and office clips were generated with Higgsfield (Kling). The app footage and terminal output are real recordings.
- All names and documents in the repository and the video are synthetic.
Built With
- apertus
- css3
- docker
- fastapi
- html5
- javascript
- llama.cpp
- pytest
- python
Log in or sign up for Devpost to join the conversation.