From 6b561cc0ee607109669d3f93e2c532f1f16a56a1 Mon Sep 17 00:00:00 2001 From: shauryaraghav Date: Sun, 24 May 2026 11:37:37 +0530 Subject: [PATCH 1/7] Add Indian UPI ID recognizer for NPCI payment compliance --- code in_upi_recognizer.py | 65 +++++++++++++++++++ .../predefined_recognizers/__init__.py | 2 + .../country_specific/india/__init__.py | 5 +- .../india/in_upi_recognizer.py | 65 +++++++++++++++++++ .../tests/test_in_upi_recognizer.py | 56 ++++++++++++++++ 5 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 code in_upi_recognizer.py create mode 100644 presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py create mode 100644 presidio-analyzer/tests/test_in_upi_recognizer.py diff --git a/code in_upi_recognizer.py b/code in_upi_recognizer.py new file mode 100644 index 0000000000..d8831b373a --- /dev/null +++ b/code in_upi_recognizer.py @@ -0,0 +1,65 @@ +from typing import List, Optional, Tuple +from presidio_analyzer import Pattern, PatternRecognizer + + +class InUpiRecognizer(PatternRecognizer): + """ + Recognizes Indian UPI (Unified Payments Interface) IDs. + + UPI IDs are used for digital payments in India and follow the format: + username@bankhandle (e.g., shaurya@okicici, 9876543210@paytm) + + Common UPI handles include: okicici, okhdfcbank, okaxis, paytm, + ybl, upi, apl, ibl, axl, timecosmos, waicici, wahdfcbank + + This recognizer identifies UPI IDs using regex and context words. + Reference: https://www.npci.org.in/what-we-do/upi/product-overview + + :param patterns: List of patterns to be used by this recognizer + :param context: List of context words to increase confidence in detection + :param supported_language: Language this recognizer supports + :param supported_entity: The entity this recognizer can detect + """ + + COUNTRY_CODE = "in" + + PATTERNS = [ + Pattern( + "UPI ID (High)", + r"\b([a-zA-Z0-9.\-_]{2,256}@(okicici|okhdfcbank|okaxis|oksbi|paytm|ybl|upi|apl|ibl|axl|waicici|wahdfcbank|timecosmos|rapl|mbk|ikwik|freecharge))\b", + 0.7, + ), + Pattern( + "UPI ID (Medium)", + r"\b([a-zA-Z0-9.\-_]{2,256}@[a-zA-Z]{2,64})\b", + 0.4, + ), + ] + + CONTEXT = [ + "upi", + "upi id", + "payment", + "gpay", + "phonepe", + "paytm", + "bhim", + "transfer", + "pay", + ] + + def __init__( + self, + patterns: Optional[List[Pattern]] = None, + context: Optional[List[str]] = None, + supported_language: str = "en", + supported_entity: str = "IN_UPI", + ): + patterns = patterns if patterns else self.PATTERNS + context = context if context else self.CONTEXT + super().__init__( + supported_entity=supported_entity, + patterns=patterns, + context=context, + supported_language=supported_language, + ) \ No newline at end of file diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py index 09643bcc45..c0ec6dafe9 100644 --- a/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py @@ -50,6 +50,7 @@ from .country_specific.india.in_pan_recognizer import InPanRecognizer from .country_specific.india.in_passport_recognizer import InPassportRecognizer from .country_specific.india.in_voter_recognizer import InVoterRecognizer +from .country_specific.india.in_upi_recognizer import InUpiRecognizer # Italy recognizers from .country_specific.italy.it_driver_license_recognizer import ( @@ -221,6 +222,7 @@ "SgUenRecognizer", "InVoterRecognizer", "InPassportRecognizer", + "InUpiRecognizer", "FiPersonalIdentityCodeRecognizer", "EsNieRecognizer", "EsPassportRecognizer", diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/__init__.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/__init__.py index 7cec47f7e4..1f2f6d60f8 100644 --- a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/__init__.py +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/__init__.py @@ -1,11 +1,11 @@ """India-specific recognizers.""" - from .in_aadhaar_recognizer import InAadhaarRecognizer from .in_gstin_recognizer import InGstinRecognizer from .in_pan_recognizer import InPanRecognizer from .in_passport_recognizer import InPassportRecognizer from .in_vehicle_registration_recognizer import InVehicleRegistrationRecognizer from .in_voter_recognizer import InVoterRecognizer +from .in_upi_recognizer import InUpiRecognizer __all__ = [ "InAadhaarRecognizer", @@ -14,4 +14,5 @@ "InVoterRecognizer", "InVehicleRegistrationRecognizer", "InPassportRecognizer", -] + "InUpiRecognizer", +] \ No newline at end of file diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py new file mode 100644 index 0000000000..604468f5c2 --- /dev/null +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py @@ -0,0 +1,65 @@ +from typing import List, Optional +from presidio_analyzer import Pattern, PatternRecognizer + + +class InUpiRecognizer(PatternRecognizer): + """ + Recognizes Indian UPI (Unified Payments Interface) IDs. + + UPI IDs are used for digital payments in India and follow the format: + username@bankhandle (e.g., shaurya@okicici, 9876543210@paytm) + + Common UPI handles include: okicici, okhdfcbank, okaxis, paytm, + ybl, upi, apl, ibl, axl, timecosmos, waicici, wahdfcbank + + This recognizer identifies UPI IDs using regex and context words. + Reference: https://www.npci.org.in/what-we-do/upi/product-overview + + :param patterns: List of patterns to be used by this recognizer + :param context: List of context words to increase confidence in detection + :param supported_language: Language this recognizer supports + :param supported_entity: The entity this recognizer can detect + """ + + COUNTRY_CODE = "in" + + PATTERNS = [ + Pattern( + "UPI ID (High)", + r"\b([a-zA-Z0-9.\-_]{2,256}@(okicici|okhdfcbank|okaxis|oksbi|paytm|ybl|upi|apl|ibl|axl|waicici|wahdfcbank|timecosmos|rapl|mbk|ikwik|freecharge))\b", + 0.7, + ), + Pattern( + "UPI ID (Medium)", + r"\b([a-zA-Z0-9.\-_]{2,256}@[a-zA-Z]{2,64})\b", + 0.4, + ), + ] + + CONTEXT = [ + "upi", + "upi id", + "payment", + "gpay", + "phonepe", + "paytm", + "bhim", + "transfer", + "pay", + ] + + def __init__( + self, + patterns: Optional[List[Pattern]] = None, + context: Optional[List[str]] = None, + supported_language: str = "en", + supported_entity: str = "IN_UPI", + ): + patterns = patterns if patterns else self.PATTERNS + context = context if context else self.CONTEXT + super().__init__( + supported_entity=supported_entity, + patterns=patterns, + context=context, + supported_language=supported_language, + ) \ No newline at end of file diff --git a/presidio-analyzer/tests/test_in_upi_recognizer.py b/presidio-analyzer/tests/test_in_upi_recognizer.py new file mode 100644 index 0000000000..a910efec8a --- /dev/null +++ b/presidio-analyzer/tests/test_in_upi_recognizer.py @@ -0,0 +1,56 @@ +import pytest +from tests import assert_result +from presidio_analyzer.predefined_recognizers import InUpiRecognizer + + +@pytest.fixture(scope="module") +def recognizer(): + return InUpiRecognizer() + + +@pytest.fixture(scope="module") +def entities(): + return ["IN_UPI"] + + +@pytest.mark.parametrize( + "text, expected_len, expected_position, expected_score", + [ + # fmt: off + # Valid UPI IDs with known handles (High confidence) + ("shaurya@okicici", 1, (0, 15), 0.7), + ("9876543210@paytm", 1, (0, 16), 0.7), + ("john.doe@okhdfcbank", 1, (0, 19), 0.7), + ("user123@ybl", 1, (0, 11), 0.7), + + # Valid UPI IDs with unknown handles (Medium confidence) + ("myname@somebank", 1, (0, 15), 0.4), + + # Invalid UPI IDs + ("notaupiid", 0, (), ()), + ("@okicici", 0, (), ()), + + # UPI in sentence + ("Please pay to shaurya@okicici for the order", 1, (14, 29), 0.7), + # fmt: on + ], +) +def test_when_upi_in_text_then_all_upis_found( + text, + expected_len, + expected_position, + expected_score, + recognizer, + entities, +): + results = recognizer.analyze(text, entities) + print(results) + assert len(results) == expected_len + if results: + assert_result( + results[0], + entities[0], + expected_position[0], + expected_position[1], + expected_score, + ) \ No newline at end of file From e55bc51e1710d04a41e7743ccff96398a8e8f736 Mon Sep 17 00:00:00 2001 From: shauryaraghav Date: Sun, 24 May 2026 11:48:33 +0530 Subject: [PATCH 2/7] Remove accidentally committed file --- code in_upi_recognizer.py | 65 --------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 code in_upi_recognizer.py diff --git a/code in_upi_recognizer.py b/code in_upi_recognizer.py deleted file mode 100644 index d8831b373a..0000000000 --- a/code in_upi_recognizer.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import List, Optional, Tuple -from presidio_analyzer import Pattern, PatternRecognizer - - -class InUpiRecognizer(PatternRecognizer): - """ - Recognizes Indian UPI (Unified Payments Interface) IDs. - - UPI IDs are used for digital payments in India and follow the format: - username@bankhandle (e.g., shaurya@okicici, 9876543210@paytm) - - Common UPI handles include: okicici, okhdfcbank, okaxis, paytm, - ybl, upi, apl, ibl, axl, timecosmos, waicici, wahdfcbank - - This recognizer identifies UPI IDs using regex and context words. - Reference: https://www.npci.org.in/what-we-do/upi/product-overview - - :param patterns: List of patterns to be used by this recognizer - :param context: List of context words to increase confidence in detection - :param supported_language: Language this recognizer supports - :param supported_entity: The entity this recognizer can detect - """ - - COUNTRY_CODE = "in" - - PATTERNS = [ - Pattern( - "UPI ID (High)", - r"\b([a-zA-Z0-9.\-_]{2,256}@(okicici|okhdfcbank|okaxis|oksbi|paytm|ybl|upi|apl|ibl|axl|waicici|wahdfcbank|timecosmos|rapl|mbk|ikwik|freecharge))\b", - 0.7, - ), - Pattern( - "UPI ID (Medium)", - r"\b([a-zA-Z0-9.\-_]{2,256}@[a-zA-Z]{2,64})\b", - 0.4, - ), - ] - - CONTEXT = [ - "upi", - "upi id", - "payment", - "gpay", - "phonepe", - "paytm", - "bhim", - "transfer", - "pay", - ] - - def __init__( - self, - patterns: Optional[List[Pattern]] = None, - context: Optional[List[str]] = None, - supported_language: str = "en", - supported_entity: str = "IN_UPI", - ): - patterns = patterns if patterns else self.PATTERNS - context = context if context else self.CONTEXT - super().__init__( - supported_entity=supported_entity, - patterns=patterns, - context=context, - supported_language=supported_language, - ) \ No newline at end of file From 4989a1be05e1500fe046ac7a89a846ef53cad846 Mon Sep 17 00:00:00 2001 From: Shaurya Raghav Date: Mon, 29 Jun 2026 22:22:07 +0530 Subject: [PATCH 3/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../country_specific/india/in_upi_recognizer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py index 604468f5c2..548d5b169f 100644 --- a/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/country_specific/india/in_upi_recognizer.py @@ -26,13 +26,13 @@ class InUpiRecognizer(PatternRecognizer): PATTERNS = [ Pattern( "UPI ID (High)", - r"\b([a-zA-Z0-9.\-_]{2,256}@(okicici|okhdfcbank|okaxis|oksbi|paytm|ybl|upi|apl|ibl|axl|waicici|wahdfcbank|timecosmos|rapl|mbk|ikwik|freecharge))\b", + r"\b([a-zA-Z0-9.\-_]{2,256}@(okicici|okhdfcbank|okaxis|oksbi|paytm|ybl|upi|apl|ibl|axl|waicici|wahdfcbank|timecosmos|rapl|mbk|ikwik|freecharge))(?![\w.-])", 0.7, ), Pattern( "UPI ID (Medium)", - r"\b([a-zA-Z0-9.\-_]{2,256}@[a-zA-Z]{2,64})\b", - 0.4, + r"\b([a-zA-Z0-9.\-_]{2,256}@[a-zA-Z]{2,64})(?![\w.-])", + 0.2, ), ] From 6830345b24f72237e7e72af2aa9d14b19ac71066 Mon Sep 17 00:00:00 2001 From: Shaurya Raghav Date: Sat, 11 Jul 2026 11:37:35 +0530 Subject: [PATCH 4/7] Add InUpiRecognizer to default recognizers fix: register InUpiRecognizer in default_recognizers.yaml --- .../presidio_analyzer/conf/default_recognizers.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml b/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml index 0ad50b1603..e3e24c06e8 100644 --- a/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml +++ b/presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml @@ -185,6 +185,13 @@ recognizers: enabled: false country_code: in + - name: InUpiRecognizer + supported_languages: + - en + type: predefined + enabled: false + country_code: in + - name: InAadhaarRecognizer supported_languages: - en From 6d1655a06fca89ef0b7bdb766104c15d687b1421 Mon Sep 17 00:00:00 2001 From: Shaurya Raghav Date: Sat, 11 Jul 2026 11:43:11 +0530 Subject: [PATCH 5/7] Enhance UPI recognizer tests with invalid cases Added test cases for invalid UPI IDs in the recognizer tests. --- presidio-analyzer/tests/test_in_upi_recognizer.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/presidio-analyzer/tests/test_in_upi_recognizer.py b/presidio-analyzer/tests/test_in_upi_recognizer.py index a910efec8a..0a6ebcedb9 100644 --- a/presidio-analyzer/tests/test_in_upi_recognizer.py +++ b/presidio-analyzer/tests/test_in_upi_recognizer.py @@ -22,13 +22,17 @@ def entities(): ("9876543210@paytm", 1, (0, 16), 0.7), ("john.doe@okhdfcbank", 1, (0, 19), 0.7), ("user123@ybl", 1, (0, 11), 0.7), + # Invalid UPI IDs + ("notaupiid", 0, (), ()), + ("@okicici", 0, (), ()), + ("john.doe@gmail.com", 0, (), ()), + ("admin@localhost", 0, (), ()), + ("user@company.com", 0, (), ()), # Valid UPI IDs with unknown handles (Medium confidence) ("myname@somebank", 1, (0, 15), 0.4), - # Invalid UPI IDs - ("notaupiid", 0, (), ()), - ("@okicici", 0, (), ()), + # UPI in sentence ("Please pay to shaurya@okicici for the order", 1, (14, 29), 0.7), @@ -44,7 +48,6 @@ def test_when_upi_in_text_then_all_upis_found( entities, ): results = recognizer.analyze(text, entities) - print(results) assert len(results) == expected_len if results: assert_result( @@ -53,4 +56,4 @@ def test_when_upi_in_text_then_all_upis_found( expected_position[0], expected_position[1], expected_score, - ) \ No newline at end of file + ) From e3d0cac7a2630562d3e6b2e9238fad7a5206297c Mon Sep 17 00:00:00 2001 From: Shaurya Raghav Date: Sat, 11 Jul 2026 11:45:30 +0530 Subject: [PATCH 6/7] Add UPI ID support to supported entities documentation --- docs/supported_entities.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/supported_entities.md b/docs/supported_entities.md index 4273461a41..80772512c6 100644 --- a/docs/supported_entities.md +++ b/docs/supported_entities.md @@ -98,6 +98,7 @@ For more information, refer to the [adding new recognizers documentation](analyz | IN_VOTER | Indian Election Commission issued 10 digit alpha numeric voter id for all indian citizens (age 18 or above) | Pattern match, context | | IN_PASSPORT | Indian Passport Number | Pattern match, Context | | IN_GSTIN | The Indian Goods and Services Tax Identification Number (GSTIN) is a 15-character identifier with state code (01-37), PAN, registration number, 'Z', and checksum. | Pattern match, context, and validation | +| IN_UPI | Indian UPI (Unified Payments Interface) ID used for digital payments, in the format username@handle. | Pattern match, context | ### Finland | FieldType | Description | Detection Method | From e06dbb84bb82e64486ce22019b85a6bf5b8cdbd6 Mon Sep 17 00:00:00 2001 From: Shaurya Raghav Date: Sat, 11 Jul 2026 11:48:05 +0530 Subject: [PATCH 7/7] Update CHANGELOG with new features and fixes Added a new recognizer for Indian UPI payment IDs and fixed documentation build issues. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ebd1bb44..d9c2eb76e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. ## [unreleased] +### Analyzer +#### Added + +* Added `InUpiRecognizer` for detecting Indian UPI (Unified Payments Interface) payment IDs (Thanks @shauryaraghav) ### Anonymizer ### General