feat(analyzer): Add Healthcare identifiers recognizer - #2159
feat(analyzer): Add Healthcare identifiers recognizer#2159bhargavikalicheti wants to merge 8 commits into
Conversation
|
Hi @SharonHart @omri374 , No rush - gentle reminder on PR whenever you get a chance. Thank you! |
There was a problem hiding this comment.
Pull request overview
Adds a set of conservative, disabled-by-default US healthcare identifier recognizers to Presidio Analyzer, aiming to detect common healthcare administrative IDs only when appropriate workflow context is present (to reduce false positives in general alphanumeric/ID-like text).
Changes:
- Introduces new US healthcare admin ID recognizers (claim, prior auth, prescription, referral, provider tax ID) plus a health insurance member ID recognizer, all requiring nearby context.
- Wires the new recognizers into predefined recognizer exports and default registry YAML (disabled by default).
- Adds unit tests, supported-entities documentation entries, and changelog notes for the new entities/recognizers.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| presidio-analyzer/tests/test_us_healthcare_admin_recognizers.py | New tests for healthcare admin ID recognizers (positive/negative context + metadata). |
| presidio-analyzer/tests/test_us_health_insurance_member_id_recognizer.py | New tests for health insurance member ID recognizer detection behavior and metadata. |
| presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/us/us_healthcare_admin_recognizers.py | Adds context-required pattern recognizer base + concrete admin ID recognizers. |
| presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/us/us_health_insurance_member_id_recognizer.py | Adds context-required member/subscriber ID recognizer with negative-context pruning. |
| presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/us/init.py | Exports the new US healthcare recognizers from the US package. |
| presidio-analyzer/presidio_analyzer/predefined_recognizers/init.py | Exposes the new recognizers via the top-level predefined_recognizers import surface. |
| presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml | Registers the recognizers as predefined + disabled-by-default with country_code: us. |
| docs/supported_entities.md | Documents the new supported entity types and brief descriptions. |
| CHANGELOG.md | Notes new analyzer recognizers under Unreleased. |
omri374
left a comment
There was a problem hiding this comment.
Thanks! Please add references to be able to trace where the regex pattern is coming from, and see the comment around context management.
|
@SharonHart @omri374 Hi! Just a friendly follow up on my PR whenever you have a chance. I'd appreciate a review when your schedule allows. Please let me know if there are any changes you'd like me to make. Thanks! |
|
@omri374 @SharonHart - Hi! just checking if you are okay with this and ready to merge please? |
Added various disabled-by-default recognizers for US and South African IDs, including health insurance member IDs, claim numbers, and UUID detection. Introduced NoOpNlpEngine for standalone recognizers.
omri374
left a comment
There was a problem hiding this comment.
Thanks! Left a few comments, hopefully all are easy to change and clear.
| member numbers. The default regex is therefore a conservative heuristic and | ||
| can be replaced through the ``patterns`` constructor argument. | ||
|
|
||
| Reference: https://www.cms.gov/files/document/2020-c2c-how-use-health-coverage-slide-deck.pdf |
There was a problem hiding this comment.
I couldn't find any reference to the insurance number ID in this deck... Maybe I missed it?
| "Health insurance member ID (alphanumeric)", | ||
| r"\b(?=[A-Z0-9-]{6,20}\b)(?=[A-Z0-9-]*[A-Z])" | ||
| r"(?=[A-Z0-9-]*\d)[A-Z]{1,5}-?[A-Z0-9]{5,14}\b", | ||
| 0.3, |
There was a problem hiding this comment.
0.3 is high for how broad this is. global_regex_flags includes re.IGNORECASE by default, so despite the uppercase character classes this matches any 6 to 20 char token starting with letters and containing a digit:
covid19, sha256, iphone15pro, rfc2119, gpt4turbo, ICD10CM123, ABC-1234567
Package convention for this specificity is 0.05 to 0.1 with an explicit strength label (UsPassportRecognizer: "Passport (very weak)", 0.05; UsBankRecognizer: "Bank Account (weak)"). Suggest 0.1, renaming to "Health insurance member ID (weak)", and noting the case-insensitive runtime behaviour in the docstring.
| PATTERNS = [ | ||
| Pattern( | ||
| "Prescription number", | ||
| r"\bRX-?\d{6,12}\b", |
There was a problem hiding this comment.
Requiring a literal RX inside the identifier means near-zero recall on real documents. Real prescription numbers are bare digits next to an Rx# label; the same applies to CLM (Medicare ICNs are 13 to 15 digits) and PA.
Anchoring on the label instead of the prefix improves precision and recall together. Matching uses the regex module, so variable-width lookbehind works and keeps the label out of the span:
r"(?<=\b(?:rx|prescription)\s*(?:#|no\.?|number)?\s*:?\s*)\d{6,12}\b"Verified: matches Rx #1234567, Prescription number: 7654321, prescription 4455667; does not match The claim 1234567 was paid.
| PATTERNS = [ | ||
| Pattern( | ||
| "Provider tax ID", | ||
| r"\b\d{2}-\d{7}\b", |
There was a problem hiding this comment.
A bare EIN shape with one generic context word is too loose:
'Provider phone extension 12-3456789' -> [('12-3456789', 0.7)]
'provider 00-0000000 listed' -> [('00-0000000', 0.7)]
The IRS publishes the valid campus prefix set for the first two digits; restricting to those is cheap and removes a large share of false positives. Also worth adding tax id, tin, ein, billing to CONTEXT.
| results = sorted(results, key=lambda result: result.start) | ||
| assert len(results) == len(expected_positions) | ||
| for result, (start, end) in zip(results, expected_positions): | ||
| assert_result(result, entity, start, end, 0.6499999999999999) |
There was a problem hiding this comment.
Please use pytest.approx(0.65) rather than asserting the exact float.
| "Claim number BCBSM1234567 was denied", | ||
| ], | ||
| ) | ||
| def test_when_member_id_lacks_insurance_context_then_below_threshold( |
There was a problem hiding this comment.
Other gaps worth covering: lowercase and mixed case inputs; multiple IDs in one text; trailing punctuation; negative pattern cases for the admin recognizers (PA-12345 too short, PA-1234567890123 too long)
| return ["en"] | ||
|
|
||
|
|
||
| class ContextAwareNlpEngineMock(NlpEngineMock): |
There was a problem hiding this comment.
This diverges from the repo convention: test_context_support.py uses the spacy_nlp_engine fixture for context tests. The mock tokenizes with \b[\w-]+\b and uses lowercased tokens as pseudo-lemmas, neither of which matches spaCy, so green tests here do not demonstrate production behaviour.
Prefer the existing fixture. If the mock stays, document the limitation in its docstring and hold off on exporting it from tests/mocks/__init__.py until more than one module needs it.
| |US_DRIVER_LICENSE|A US driver license according to <https://ntsi.com/drivers-license-format/>|Pattern match and context| | ||
| |US_ITIN | US Individual Taxpayer Identification Number (ITIN). Nine digits that start with a "9" and contain a "7" or "8" as the 4 digit.|Pattern match and context| | ||
| |US_CLAIM_NUMBER|A US healthcare claim identifier used in billing and claims processing.|Pattern match and required context| | ||
| |US_HEALTH_INSURANCE_MEMBER_ID|A US health insurance member or subscriber identifier printed on an insurance card. Detection requires healthcare or insurance context.|Pattern match and required context| |
There was a problem hiding this comment.
The PR description mentions negative-context checks for order, tracking, case and invoice numbers. There is no deny list or negative-context mechanism in the code; those tests pass only because the positive context word is absent. Either implement it or drop the claim.
| from presidio_analyzer import Pattern, PatternRecognizer | ||
|
|
||
|
|
||
| class _HealthcareAdminPatternRecognizer(PatternRecognizer): |
There was a problem hiding this comment.
What's the added value of using this class?
Change Description
Adds conservative, context-aware US healthcare identifier recognizers to Presidio Analyzer.
New disabled-by-default recognizers:
US_HEALTH_INSURANCE_MEMBER_IDUS_PRIOR_AUTHORIZATION_NUMBERUS_CLAIM_NUMBERUS_PRESCRIPTION_NUMBERUS_REFERRAL_NUMBERUS_PROVIDER_TAX_IDThese recognizers require both a plausible identifier pattern and nearby healthcare/insurance workflow context to reduce false positives. They also include negative-context checks for similar-looking non-healthcare IDs such as order numbers, tracking numbers, case numbers and invoice numbers where applicable.
Issue reference
Fixes Feature Request: Healthcare Recognizer for Common Healthcare Identifiers (Member ID, Claims, Prior Authorization, etc.)
#2136
Checklist