01 · The problem
Masking is not one operation: a doctor, a billing clerk and a researcher need different views of the same record. Built a role-by-purpose-by-consent policy matrix choosing full access, partial mask, full anonymise or deny per request.
02 · How it works
- 01
Matched structured identifiers by format rather than by model, including Aadhaar with Verhoeff checksum validation, PAN, MRN, cards, IBAN and IP, so detection works identically whichever spaCy pipeline is configured.
- 02
Resolved overlapping detections through a deterministic priority table so no strategy leaves part of an identifier visible, and read the caller's role from the JWT rather than the request body, with privileged self-registration refused.
- 03
Shipped three layers, a stateless engine on PyPI, a hardened FastAPI service and a Next.js app, with the spaCy model size configurable so an operator picks the accuracy and memory trade-off at deploy time.
- 04
Enforced authentication on every protected route with the role read from the JWT rather than the body, and refused privileged self-registration so admins are seeded out of band.
03 · What it cost, and what it returned
Required the encryption key in production and made the service fail fast when it is missing, rather than silently falling back to a throwaway key.
