Skip to content

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.


What it is ​

Three layers, each usable on its own or stacked:

  1. 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.
  2. Dictionary (L2) — a built-in Turkish gazetteer (provinces, first names, surnames, titles, organisation suffixes) that raises person / organization / location candidates a regex has no shape to anchor on. Zero dependencies, async-only.
  3. 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 needs onnxruntime-node / onnxruntime to 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 / password are 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 ​

About ​

MIT licensed. Source lives at github.com/Cognipeer/pii.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub