Library
pii
Offline PII detection for Turkish and English, CPU-only, no network calls. Regex and checksum validation, a built-in Turkish name/place gazetteer, and an optional bundled 1.45 MB char-CNN that acts as a context judge rather than another vote. Ships as two independent, parity-tested ports: an npm package for Node and a PyPI package for Python.
- Packages
@cognipeer/pii·cognipeer-pii - Languages Node 18+ · Python 3.9+
- Runtime CPU, zero required dependencies
- Licence MIT
pii exists because commercial and open-source PII detectors are built English-first, and most treat Turkish — when they support it at all — as one more spaCy language pack. This package validates real Turkish checksums (TC Kimlik, VKN), ships a small auditable gazetteer (81 provinces, 189 first names, 236 surnames, common organisation suffixes), and trains its own compact neural tagger from scratch instead of shrinking a large one, so the whole stack — pattern tier, gazetteer, and model — stays CPU-only and sub-millisecond to a few milliseconds per call.
Getting started
Install and run your first detect/redact/mask call in JavaScript and Python.
Detection modes
Pattern, dictionary, NER, and the CNN-as-judge mode — what each adds, and the fusion math behind the judge.
Categories
All 31 built-in categories — languages, default on/off, validators, and why.
Model & benchmarks
Architecture, training data, contamination discipline, and every measured number, good and bad.
What it is
Three layers, each usable on its own or stacked:
- Pattern (L1) — regex plus real checksum/format validators: Luhn for cards, the official two-step algorithm for TC Kimlik, IBAN length, calendar-plausible dates, MAC address structure, GPS coordinate ranges. Zero dependencies, synchronous, well under a millisecond per call.
- Dictionary (L2) — a built-in Turkish gazetteer (provinces, first names, surnames, titles, organisation suffixes) that raises
person/organization/locationcandidates a regex has no shape to anchor on. Zero dependencies, async-only. - NER (L3) — a bundled 1.45 MB int8 char-CNN (753,703 parameters, 15 BIO labels) that either adds candidates the gazetteer never saw (
pattern+dictionary+ner) or re-scores every pattern/dictionary candidate up or down by how much it agrees with that exact span (pattern+ner-verified, the judge mode). The model file ships inside both packages — nothing is downloaded at install or at runtime — and only needsonnxruntime-node/onnxruntimeto execute.
See Detection modes for the full picture, including the fusion math behind the judge.
When to use it vs. Console guardrails
Cognipeer Console ships its own hosted pii guardrail — a request-pipeline check with 15 categories (email, phone, credit card, IBAN, TC Kimlik, API keys/JWTs, and more) that runs on input/output/tool hooks, with block/redact/warn/flag actions and evaluation logging. See Guardrails.
Reach for this package instead when:
- You need detection outside a Console request path at all — batch-redacting a document store, scrubbing logs before they leave a service, or an on-device/air-gapped pipeline with zero network calls, including to Console itself.
- You need Turkish-specific coverage beyond a generic PII preset: checksummed TC Kimlik and VKN, Turkish IBAN/phone/plate/passport formats, an address pattern for
Mahalle/No:records, and a gazetteer plus neural tagger tuned on Turkish name morphology (case suffixes, homonyms, ALL-CAPS records). - You want the CNN-as-judge behaviour (
pattern+ner-verified): a way to pull a regex hit's confidence down when context disagrees, not just up. - You want reversible tokenization (
tokenizeText/detokenizeText) with your own vault, rather than a guardrail's block/redact/warn/flag actions.
Reach for Console guardrails when the traffic already flows through Console (or the AI App Gateway) and you want centralized policy management, per-tenant configuration, and evaluation logs without embedding a library in your own service.
Headline numbers, with the caveats
All numbers below are from the shipped model (v8, 2026-09-13); the full tables, methodology, and every regression are in Model & benchmarks.
- Turkish, against a 441 MB BERT NER model: on the three entity types BERT can emit (person/org/location), the bundled 1.45 MB model reaches 0.858 recall on a clean third-party CC0 Turkish PII benchmark against BERT's 0.871 — within 1.3 points, at roughly 1/38th the resident memory (~25 MB vs. 944 MB) and about 20x the speed (~0.9 ms/call vs. ~18–21 ms).
pattern+ner-verified(the judge mode) is the recommended mode for Turkish production use: identifier-probe F1 0.922–0.934 depending on checkpoint, against 0.795 for the pattern tier alone, at ~0.83 ms p50 per call.- The Turkish homonym challenge (
Can/can,Deniz/deniz— the same word as a name in one sentence and an ordinary noun in its twin): strict F1 0.793 in the head-to-head re-run, edging out BERT's 0.773. Read this one carefully — see the caveat below. - English is the honest weak spot. On the Text Anonymization Benchmark (real, human-annotated court judgments), the shipped model reaches 0.669 token recall / 0.631 precision with every category enabled, against a fine-tuned RoBERTa baseline at 0.906 recall. A tool that leaves case numbers and most dates in place is not a general anonymiser, whatever it scores on names.
Caveats that matter, stated plainly:
- The homonym suite is not an unseen-word test — its words appear as person names in the model's own training data, so the score measures telling a known name from the same everyday word, not generalisation to a name never seen. BERT's pretraining has seen these names too, so the comparison is fair, but neither number is a clean out-of-vocabulary result.
data/eval/id_probes.json, the 350-row identifier-probe suite, was written by a separate Claude (Sonnet) agent inside this project — an independent check on the package's own generator, not third-party or human-annotated data the way the CC0 benchmark or TAB are.- The two genuinely external, never-trained-on checks are a third-party CC0 Turkish PII benchmark and the Text Anonymization Benchmark / ECHR court judgments — and this package scores worse on both than on its own evaluations, which is the point of reading them.
Known gaps
username/passwordare keyword-anchored and off by default: a value with no introducing keyword is never found, and a bare keyword in ordinary prose was roughly half of raw hits on a 3,000-row sample.birthDate, when enabled, matches every calendar-valid date, not only birth dates.tr_vkn's checksum has never been checked against an official test vector, so it ships off by default with a deliberately low base score.- CNN-only identifier spans (a phone/ID/financial span the CNN finds with no overlapping regex candidate) are deliberately down-weighted — the regex front stays in charge of whether a number-shaped entity exists at all.
- JavaScript strings are UTF-16 and Python strings are code points, so integer offsets differ by one per non-BMP character (an emoji).
redact()output text is byte-identical in both languages regardless.
Full detail, including four numbers that moved the wrong way during the 2026-09-13 model work and were recorded rather than hidden, is in Model & benchmarks.
Where to go next
- Getting Started — install and run your first
detect/redact/maskcall. - Detection modes — the four modes, the judge's fusion math, and latency per mode.
- Categories — every built-in category, with languages, defaults, and validators.
- Turkish specifics — checksums, suffix trimming, homonyms, case handling, spelled-out digits.
- Redaction & tokens —
redact/mask/tokenizeText/detokenizeText, mask strategies, and the finding shape. - Model & benchmarks — architecture, training data, and every measured number.
- JavaScript API and Python API — full function and option reference.
About
MIT licensed. Source lives at github.com/Cognipeer/pii.

