Skip to content

feat(analyzer): Add Healthcare identifiers recognizer - #2159

Open
bhargavikalicheti wants to merge 8 commits into
data-privacy-stack:mainfrom
bhargavikalicheti:feature/us-healthcare-recognizer
Open

feat(analyzer): Add Healthcare identifiers recognizer#2159
bhargavikalicheti wants to merge 8 commits into
data-privacy-stack:mainfrom
bhargavikalicheti:feature/us-healthcare-recognizer

Conversation

@bhargavikalicheti

Copy link
Copy Markdown

Change Description

Adds conservative, context-aware US healthcare identifier recognizers to Presidio Analyzer.

New disabled-by-default recognizers:

  • US_HEALTH_INSURANCE_MEMBER_ID
  • US_PRIOR_AUTHORIZATION_NUMBER
  • US_CLAIM_NUMBER
  • US_PRESCRIPTION_NUMBER
  • US_REFERRAL_NUMBER
  • US_PROVIDER_TAX_ID

These 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

  • I have reviewed the contribution guidelines
  • I agree to follow this project's Code of Conduct
  • I confirm that I have the right to submit this contribution and that it does not knowingly contain proprietary or confidential code.
  • My code includes unit tests
  • All unit tests and lint checks pass locally
  • My PR contains documentation updates / additions if required

@bhargavikalicheti

Copy link
Copy Markdown
Author

Hi @SharonHart @omri374 , No rush - gentle reminder on PR whenever you get a chance. Thank you!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 omri374 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Please add references to be able to trace where the regex pattern is coming from, and see the comment around context management.

@bhargavikalicheti

Copy link
Copy Markdown
Author

@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!

@bhargavikalicheti

Copy link
Copy Markdown
Author

@omri374 @SharonHart - Hi! just checking if you are okay with this and ready to merge please?

omri374 added 2 commits August 4, 2026 11:25
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 omri374 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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|

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the added value of using this class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants