Skip to content

Commit 5d13e54

Browse files
committed
feat(analyzer): add Korean bank account number recognizer (KR_BANK_ACCOUNT)
1 parent 6116c06 commit 5d13e54

6 files changed

Lines changed: 167 additions & 3 deletions

File tree

docs/supported_entities.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ For more information, refer to the [adding new recognizers documentation](analyz
113113
| KR_PASSPORT| The Korean Passport Number | Pattern match, context. |
114114
| KR_BRN | The Korean Business Registration Number (BRN) is a 10-digit number assigned to business entities for taxation purposes. | Pattern match, context and custom logic. |
115115
| KR_RRN | The Korean Resident Registration Number (RRN) is a 13-digit number issued to all Korean residents. | Pattern match, context and custom logic. |
116+
| KR_BANK_ACCOUNT | The Korean bank account number: 10-14 digit bank-specific formats with no unified checksum. | Pattern match, context. |
116117

117118

118119
### Nigeria

presidio-analyzer/presidio_analyzer/conf/default_recognizers.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,14 @@ recognizers:
279279
type: predefined
280280
country_code: pl
281281

282+
- name: KrBankAccountRecognizer
283+
supported_languages:
284+
- ko
285+
- kr
286+
type: predefined
287+
enabled: false
288+
country_code: kr
289+
282290
- name: KrBrnRecognizer
283291
supported_languages:
284292
- ko

presidio-analyzer/presidio_analyzer/predefined_recognizers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@
6262
from .country_specific.italy.it_vat_code import ItVatCodeRecognizer
6363

6464
# Korea recognizers
65+
from .country_specific.korea.kr_bank_account_recognizer import (
66+
KrBankAccountRecognizer,
67+
)
6568
from .country_specific.korea.kr_brn_recognizer import KrBrnRecognizer
6669
from .country_specific.korea.kr_driver_license_recognizer import (
6770
KrDriverLicenseRecognizer,
@@ -271,6 +274,7 @@
271274
"UkPostcodeRecognizer",
272275
"UkVehicleRegistrationRecognizer",
273276
"AzureHealthDeidRecognizer",
277+
"KrBankAccountRecognizer",
274278
"KrBrnRecognizer",
275279
"KrRrnRecognizer",
276280
"KrDriverLicenseRecognizer",
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
"""Korea-specific recognizers."""
22

3+
from .kr_bank_account_recognizer import KrBankAccountRecognizer
34
from .kr_brn_recognizer import KrBrnRecognizer
45
from .kr_driver_license_recognizer import KrDriverLicenseRecognizer
56
from .kr_frn_recognizer import KrFrnRecognizer
67
from .kr_passport_recognizer import KrPassportRecognizer
78
from .kr_rrn_recognizer import KrRrnRecognizer
89

910
__all__ = [
10-
"KrDriverLicenseRecognizer",
11-
"KrPassportRecognizer",
12-
"KrFrnRecognizer",
11+
"KrBankAccountRecognizer",
1312
"KrBrnRecognizer",
1413
"KrDriverLicenseRecognizer",
14+
"KrFrnRecognizer",
1515
"KrPassportRecognizer",
1616
"KrRrnRecognizer",
1717
]
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from typing import List, Optional
2+
3+
from presidio_analyzer import Pattern, PatternRecognizer
4+
5+
6+
class KrBankAccountRecognizer(PatternRecognizer):
7+
"""
8+
Recognize Korean bank account numbers.
9+
10+
Korean bank account numbers are 10-14 digit identifiers issued in
11+
bank-specific segment layouts, commonly written with hyphens
12+
(e.g. NongHyup's 302-XXXX-XXXX-XX personal accounts, or common
13+
three-segment forms such as 110-234-567890). No unified national
14+
format or check digit exists, so precision comes from structural
15+
patterns plus negative lookaheads that exclude look-alike shapes:
16+
Korean mobile/VoIP phone numbers (010/011/016/017/019/070),
17+
resident registration numbers and calendar dates.
18+
19+
Reference: per-bank layouts (e.g. NongHyup's 3YY-XXXX-XXXX-CC
20+
personal accounts) are documented at
21+
https://namu.wiki/w/%EA%B3%84%EC%A2%8C%EB%B2%88%ED%98%B8
22+
Patterns were validated in a production Korean PII-masking deployment.
23+
24+
:param patterns: List of patterns to be used by this recognizer
25+
:param context: List of context words to increase confidence in detection
26+
:param supported_language: Language this recognizer supports
27+
:param supported_entity: The entity this recognizer can detect
28+
"""
29+
30+
COUNTRY_CODE = "kr"
31+
32+
PATTERNS = [
33+
Pattern(
34+
"NH 4-segment account (Medium)",
35+
r"(?<!\d)302-\d{3,4}-\d{3,4}-\d{2}(?!\d)",
36+
0.6,
37+
),
38+
Pattern(
39+
"Hyphenated 3-segment account (Weak)",
40+
r"(?<!\d)(?!(?:01[01679]|070)-\d{3,4}-\d{4}(?!\d))"
41+
r"(?!\d{4}[-./]\d{1,2}[-./]\d{1,2}(?!\d))(?=(?:\d-?){9,16}(?!\d))"
42+
r"\d{2,3}-\d{2,6}-\d{1,7}(?!\d)",
43+
0.3,
44+
),
45+
Pattern(
46+
"Mixed-separator account (Very weak)",
47+
r"(?<!\d)(?!\d{6}-?\d{7}(?!\d))"
48+
r"(?!(?:01[01679]|070)[- ]?\d{3,4}[- ]?\d{4}(?!\d))"
49+
r"(?!\d{4}[-./]\d{1,2}[-./]\d{1,2}(?!\d))(?=(?:\d[- ]?){9,16}(?!\d))"
50+
r"\d{2,4}(?:[- ]?\d{2,6})(?:[- ]?\d{1,7})(?:[- ]?\d{1,3})?(?!\d)",
51+
0.15,
52+
),
53+
]
54+
55+
CONTEXT = [
56+
"계좌",
57+
"계좌번호",
58+
"예금주",
59+
"송금",
60+
"입금",
61+
"입금계좌",
62+
"출금",
63+
"이체",
64+
"타행",
65+
"은행",
66+
"bank account",
67+
"account number",
68+
]
69+
70+
def __init__(
71+
self,
72+
patterns: Optional[List[Pattern]] = None,
73+
context: Optional[List[str]] = None,
74+
supported_language: str = "ko",
75+
supported_entity: str = "KR_BANK_ACCOUNT",
76+
name: Optional[str] = None,
77+
):
78+
patterns = patterns if patterns else self.PATTERNS
79+
context = context if context else self.CONTEXT
80+
super().__init__(
81+
supported_entity=supported_entity,
82+
patterns=patterns,
83+
context=context,
84+
supported_language=supported_language,
85+
name=name,
86+
)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pytest
2+
3+
from tests import assert_result_within_score_range
4+
from presidio_analyzer.predefined_recognizers.country_specific.korea import (
5+
KrBankAccountRecognizer,
6+
)
7+
8+
9+
@pytest.fixture(scope="module")
10+
def recognizer():
11+
return KrBankAccountRecognizer()
12+
13+
14+
@pytest.fixture(scope="module")
15+
def entities():
16+
return ["KR_BANK_ACCOUNT"]
17+
18+
19+
# Patterns overlap by design (an NH account also matches the generic layouts),
20+
# so positive cases assert on the best-scoring match instead of result counts.
21+
@pytest.mark.parametrize(
22+
"text, start, end, score",
23+
[
24+
# NH (NongHyup) 302-prefixed 4-segment personal account
25+
("302-1234-5678-91", 0, 16, 0.6),
26+
("이체 계좌: 302-0123-4567-89", 7, 23, 0.6),
27+
# Common hyphenated 3-segment layouts
28+
("110-234-567890", 0, 14, 0.3),
29+
("457-910-012345", 0, 14, 0.3),
30+
# Mixed/plain digit runs (9-16 digits, excluding the 13-digit RRN shape)
31+
("987654321012", 0, 12, 0.15),
32+
("1002-123-456789", 0, 15, 0.15),
33+
],
34+
)
35+
def test_when_account_like_then_best_match_found(
36+
text, start, end, score, recognizer, entities
37+
):
38+
results = recognizer.analyze(text, entities)
39+
assert results
40+
best = max(results, key=lambda r: r.score)
41+
assert_result_within_score_range(best, entities[0], start, end, score, score)
42+
43+
44+
@pytest.mark.parametrize(
45+
"text",
46+
[
47+
# Korean mobile / VoIP phone numbers must not match
48+
"010-1234-5678",
49+
"070-1234-5678",
50+
"01012345678",
51+
"07012345678",
52+
# Resident registration number shapes must not match
53+
"960121-1234567",
54+
"9601211234567",
55+
# Any 13-digit pure run is left to KR_RRN's domain
56+
"9876543210123",
57+
# Dates must not match
58+
"2024-03-10",
59+
# Too short
60+
"45000",
61+
"12345678",
62+
],
63+
)
64+
def test_when_look_alike_then_no_match(text, recognizer, entities):
65+
assert recognizer.analyze(text, entities) == []

0 commit comments

Comments
 (0)