Metadata-Version: 2.5
Name: presidio-malaysia
Version: 1.0.0
Summary: Malaysian PII recognizers (NRIC, phone, postcode, SSM) for Microsoft Presidio.
Project-URL: Repository, https://github.com/cheneeheng/presidio-compliance-stack
Project-URL: Issues, https://github.com/cheneeheng/presidio-compliance-stack/issues
License: Apache-2.0
License-File: LICENSE
Keywords: compliance,malaysia,mykad,nric,pdpa,pii,presidio
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Natural Language :: English
Classifier: Natural Language :: Malay
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: presidio-analyzer>=2.2.363
Provides-Extra: audit
Requires-Dist: presidio-audit>=1.0.0; extra == 'audit'
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest-randomly; extra == 'dev'
Requires-Dist: pytest-repeat; extra == 'dev'
Requires-Dist: ruff==0.16.1; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# presidio-malaysia

Malaysian PII recognizers for Microsoft Presidio: `MY_NRIC`, `MY_SSM`, `MY_PHONE`,
`MY_POSTCODE`. The Malaysia counterpart to the Singapore and Russia recognizer
packs, with structural validation rather than bare regexes.

```bash
pip install presidio-malaysia            # recognizers only
pip install "presidio-malaysia[audit]"   # plus the audit/evidence layer
```

## Use

```python
from presidio_analyzer import AnalyzerEngine
from presidio_malaysia import all_recognizers

analyzer = AnalyzerEngine()
for recognizer in all_recognizers():
    analyzer.registry.add_recognizer(recognizer)
```

Or, if you hold a registry rather than an engine:

```python
from presidio_malaysia import load_malaysia_recognizers

load_malaysia_recognizers(registry)
```

## What each recognizer actually checks

| Entity | Validation |
| --- | --- |
| `MY_NRIC` | Hyphenated and compact forms. The birth date must be a real calendar date and the place-of-birth pair an assigned code. **MyKad numbers carry no check digit** — anything claiming "checksum validation" for an NRIC is describing structural validation. |
| `MY_SSM` | New 12-digit format validated on registration year and entity-type code. Old six-to-eight-digit formats (`NNNNNN-X` to `NNNNNNNN-X`) have no internal structure and rely entirely on context words. |
| `MY_PHONE` | Mobile prefixes and landline area codes, with `+60`/`60` and separators tolerated. Scoped to Malaysian numbers on purpose — Presidio's built-in `PhoneRecognizer` covers international ones; this exists so detections carry a MY-specific label. |
| `MY_POSTCODE` | Range-checked, but context-dependent by design: five bare digits carry almost no signal, so the base score is near zero and address context is what surfaces it. |

Every recognizer is tagged `country_code = "MY"`, so `recognizer.country_code()` and
`registry.get_country_codes()` report the pack the way they report Presidio's own
country-specific recognizers. The tag is metadata, not a switch: the `countries=` filter
belongs to `load_predefined_recognizers()`, and this pack registers through
`add_recognizer()`, so the tag never decides whether a `MY_*` recognizer runs.

Context words fire on both English and Malay phrasing (`nric`, `mykad`, `kad
pengenalan`, `poskod`, `syarikat`, `telefon`, …). The recognizers are regex plus
validation and do **not** depend on spaCy NER, which is English-only — so Malay
text is not a second-class case.

## Scope: MY entities only

`load_malaysia_recognizers()` registers `MY_*` entities and nothing else. Note that
since Presidio 2.2.359 most country-specific predefined recognizers — including
`SgFinRecognizer`, the `In*` family and `EsNifRecognizer` — ship **disabled by
default**, so they will not fire out of the box either.

This matters in practice: Malaysian customer records routinely contain Singaporean
identifiers. Enable them deliberately if you want them:

```python
from presidio_analyzer.predefined_recognizers import SgFinRecognizer

registry.add_recognizer(SgFinRecognizer())
```

This package will not do that for you. Widening detection scope is your decision,
and entities you never opted into would otherwise appear in your audit report.

### One collision to know about: UK_NHS

Presidio's built-in `UK_NHS` recognizer is enabled by default and matches Malaysian
mobile numbers — `012-3456789` scores 1.0 as a UK NHS number, because it passes the
NHS checksum. When both fire at 1.0, Presidio's overlap resolution can pick
`UK_NHS`, and your audit report will then say `UK_NHS` for a Malaysian phone number,
with whatever operator you configured for that entity.

Two ways to deal with it, both yours to choose:

```python
# Scope the analysis to the entities you care about
analyzer.analyze(text=text, language="en", entities=["MY_NRIC", "MY_PHONE"])

# Or drop the recognizer if you have no UK data at all
analyzer.registry.remove_recognizer("NhsRecognizer")
```

We do not remove it for you, for the same reason we do not enable Singapore's
recognizers for you.

## Precision

These recognizers report what they match. They do not guarantee they match
everything — Presidio disclaims detection completeness and so does this package.
`MY_POSTCODE` in particular is explicitly context-dependent and will stay quiet
without address cues. Its near-zero base score means the context enhancer's floor of
**0.4 is the highest score it ever reaches** — filter results at 0.6 and you will
never see a postcode.

## Attribution

The NRIC place-of-birth code table follows the National Registration Department's own
published tables — [state codes](https://www.jpn.gov.my/en/information/state-code/) and
[country codes](https://www.jpn.gov.my/en/information/country-code/), checked
2026-08-20. It is vendored as a static frozenset rather than taken as a dependency.

Release history: [CHANGELOG.md](https://github.com/cheneeheng/presidio-compliance-stack/blob/main/packages/presidio_malaysia/CHANGELOG.md).

Apache-2.0 licensed.
