Spot sensitive details.
Detect names, organisations, case numbers and other identifying facts.
01 · NLP · Privacy engineering
I built a tool to find and transform sensitive details in legal documents, then tested what it missed and whether the remaining details could single out a case within the dataset.
The project in 60 seconds
Sending a legal file to an AI service can expose private details. This tool finds sensitive information and helps a reviewer decide what to remove before sharing the text.
Sarah Jones, the only surgeon in a small town, filed a complaint.
[PERSON_A], the only surgeon in a small town, filed a complaint.
The name is gone. The remaining clues could still reveal who it is.
Detect names, organisations, case numbers and other identifying facts.
Remove details, make them less specific, or replace names with consistent labels.
Flag distinctive details and let the reader inspect what changed. Keep the name-to-label map locally.
Better name detection helps.
It does not guarantee privacy.
I built: the detection models, privacy checks, text transformations and review interface. Status: evaluated prototype; human review is still needed.
Data
The project uses the Text Anonymization Benchmark: 1,268 annotated European Court of Human Rights judgments. Its annotations distinguish entity types such as people, organisations and case codes, and whether an item is a direct identifier, quasi-identifier or does not require masking.
The held-out evaluation contains 555 annotator-document rows covering 127 unique judgments. That distinction matters: presenting those 555 rows as 555 independent judgments would overstate the breadth of the test data.
Approach
I normalised every detector to the same contract: text in, typed character spans out. This made spaCy, Presidio, three fine-tunes and their voting rules comparable through one evaluator. The baseline exposed a missing category: spaCy had 0% recall on case-file codes. A custom recogniser helped that label, but noisy organisation predictions kept Presidio behind the fine-tunes. Detection feeds three product modes:
Replace names, organisations, dates, IDs and case codes when direct disclosure is the immediate risk.
Try broader ages, locations and dates, then report the remaining corpus uniqueness. If only deleting every fact reaches the target, report failure to converge; an empty fingerprint is not evidence of safety.
Map entities to stable tokens so a model can follow who did what, then restore names locally if authorised.
Redaction removes visible identifiers. Anonymisation tries to reduce the chance that remaining information can single someone out. Pseudonymisation is reversible. Keeping its mapping local limits direct disclosure, but the transformed text can still contain identifying context.
System architecture
A simplified example of reversible name replacement.
Detect sensitive details, choose what to change and review the text.
Send the reviewed text
The AI receives the transformed text. It does not receive the local name-to-label map.
Return the answer
Use the saved map to restore names in the answer, then review it.
Ready for human review
Saved in step 1. Used again in step 3. Never included in the AI request.
Evaluation method
Detection: entity spans are scored with partial-match and exact-match precision, recall and F1 on the held-out annotations. Cluster-bootstrap confidence intervals resample the 127 unique judgments, not the 555 annotation rows. Repeated annotations of the same text are not independent observations. Partial matching gives credit when the right entity is found with an imperfect boundary; exact matching exposes those boundary errors.
Mosaic risk: after masking direct identifiers, the analysis selects increasingly many quasi-identifiers and asks when a record becomes unique within this corpus. This is a controlled corpus-uniqueness study, not a real-world re-identification probability.
Results
Detection results below use 555 annotation rows covering 127 judgments. The ensemble votes over saved predictions from RoBERTa, LegalBERT and Longformer; the hosted Studio uses RoBERTa alone at 512/64, while the ensemble rows describe separate offline experiments.
| Configuration | Precision | Recall | F1 |
|---|---|---|---|
| RoBERTa · 512-token window | 0.8454 | 0.8668 | 0.8559 |
| LegalBERT · 512-token window | 0.8456 | 0.8621 | 0.8538 |
| Longformer · 4,096-token window | 0.8373 | 0.8631 | 0.8500 |
| Union · at least one vote | 0.8421 | 0.8835 | 0.8623 |
| Majority · at least two votes | 0.8898 | 0.8653 | 0.8774 |
Majority voting improves precision while recall is 0.0015 below RoBERTa. Union catches more mentions and has lower F1: the preferred rule depends on the cost of a missed identifier. Partial overlap can earn detection credit while leaving part of a name visible, so F1 alone cannot validate release quality.
Evidence: scores and cluster confidence intervals · ensemble evaluation.
In a separate union-vote simulation, 94.6% of PERSON and 63.2% of ORG mentions marked for masking were fully removed. Only 58.7% of the 555 annotation rows had no such mentions left. When the requirement expands to every person and organisation, including TAB’s NO_MASK annotations, that clean-row rate falls to 0%. The training target deliberately excludes many public institutions; a broader customer requirement needs a different target.
Inspect the coverage simulation. These are annotation-row results, not 555 independent documents or measured real-world release outcomes.
Adaptations & future work
Finding names, preparing useful text and avoiding missed identifiers are different jobs. These experiments show how the design could change when the priority changes.
Test scope: 555 annotation rows across 127 unique legal judgments. The small model was run on those judgments; the other policies replay saved predictions. These are exploratory comparisons on the existing test set, not fresh external validation or deployed features.
Route 01 · Just find names
If the task is to extract people and organisations for indexing or review, keep the entity detector and omit the privacy-risk scorer, generalisation loop and identity vault. This is named-entity recognition (NER), with no claim that its output is anonymous.
Test run: the small, non-transformer en_core_web_sm model versus the existing fine-tuned RoBERTa, both restricted to PERSON and ORG. Here the target is every annotated name, including public organisations the privacy benchmark permits leaving visible.
| Detector | Precision | Recall | F1 |
|---|---|---|---|
| Small spaCy model | 52.0% | 73.3% | 0.608 |
| Fine-tuned RoBERTa | 90.5% | 47.9% | 0.627 |
The small model finds more of this broader target, but produces more false positives. RoBERTa was trained for the masking policy, which excludes many public institutions; it needs retraining for an “extract every name” brief. On just the 6,084 names marked for masking, the ranking is different: RoBERTa reaches 0.822 F1 versus 0.386 for the small model.
Possible adaptation: use the small model for a first-pass extraction assistant, with correction by a reader. Distil or retrain a compact model on the actual extraction target before treating it as a replacement. Latency and memory savings have not been benchmarked here.
Route 02 · Prioritise fewer misses
If a missed name costs more than an unnecessary redaction, change the policy. Start by covering every span proposed by any of the three fine-tunes. Then try a broader rule: remove each sentence-like passage containing a detection.
A high F1 score—even 0.90—does not mean every document is clean. This simulation instead checks whether every character of a target name is removed, and how much text is lost in doing so.
Headline scope: the 6,084 PERSON/ORG mentions marked DIRECT or QUASI, across 555 annotation rows. Full removal is stricter than partial-match detection recall.
| Redaction policy | Names fully removed | Rows with no target names left | Text removed |
|---|---|---|---|
| One fine-tuned detector | 79.3% | 49.9% | 9.5% |
| Three detectors · majority vote | 83.3% | 56.0% | 9.6% |
| Three detectors · cover every predicted span | 85.3% | 62.7% | 10.1% |
| Remove whole flagged passages | 96.2% | 87.4% | 63.9% |
| Remove the entire document | 100.0% | 100.0% | 100.0% |
The passage policy still misses part or all of 3.8% of target names. Some passages have no detection to trigger removal. Suppressing the entire document is the only tested policy reaching 100%—and it leaves nothing to summarise. Neither result measures residual re-identification risk or unannotated identifiers.
Keeping the policies fixed and adding NO_MASK mentions changes the task. The passage policy fully removes 88.0% of names, but only 23.6% of rows have none left. Changing the requirement exposes a larger gap.
| Redaction policy | Names fully removed | Rows with no target names left | Text removed |
|---|---|---|---|
| One fine-tuned detector | 44.5% | 0.0% | 9.5% |
| Three detectors · majority vote | 47.4% | 0.0% | 9.6% |
| Three detectors · cover every predicted span | 49.3% | 0.0% | 10.1% |
| Remove whole flagged passages | 88.0% | 23.6% | 63.9% |
| Remove the entire document | 100.0% | 100.0% | 100.0% |
All five policies are scored against the same rows. Redaction can use predictions of any entity type; a name is counted as fully removed only when the union of removed character positions covers its entire annotated span. “Text removed” counts all characters, including whitespace, and is a proxy for lost utility—not a measured loss in answer quality.
The broad policy uses punctuation and newlines to delimit passages; abbreviations can split them. It removes any segment touched by a prediction. This tests broadening the removal area, not lowering a confidence threshold: the saved cache does not contain confidence scores.
The raw union preserves coverage from overlapping spans. It differs from the earlier Results-tab union, whose longest-span voting can discard fragments: 85.3% versus 84.6% full removal. The majority and single-model rows reproduce the earlier coverage results.
The three fine-tunes use their saved windows: 512 tokens for RoBERTa and LegalBERT, 4,096 for Longformer. Repeated annotations are scored as rows; these are descriptive point estimates without new confidence intervals. A second corpus is needed before claiming general performance.
Download policy results (CSV) · Metrics, counts and provenance (JSON) · Reproduction script
Future work · proposed, not measured
Include public organisations in the labels, train or distil a smaller detector, and compare recall, memory and latency on a new corpus.
Cache confidence scores, choose thresholds on validation data and test word- or sentence-level expansion. Measure both missed names and time spent correcting excess redaction.
Compare summary accuracy and retained facts across the privacy modes. Add a separate contextual-risk assessment and external-domain evaluation.
Demo & source
Anonymiser Studio lets you compare Source, Redact, Anonymise and Pseudonymise, inspect each detected phrase, and restore names using the demo identity map. It runs the RoBERTa fine-tune on CPU with a 512/64 inference window; the three-model ensemble results above are a separate comparison. Use the supplied synthetic example or your own synthetic text. Input and identity mappings are processed on Hugging Face infrastructure, not on your computer. Do not paste confidential information into this public demo.
Compare Source, Redact, Anonymise and Pseudonymise, inspect detected phrases, and try restoring names. The hosted app loads when you open this panel; if it is waking up, allow a moment or open it in a new tab.