Skip to content
This repository was archived by the owner on Jan 8, 2025. It is now read-only.

Commit 22f0409

Browse files
authored
Fix #10 - implement bangladesh NID (#223)
1 parent 4a66ab3 commit 22f0409

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

idnumbers/nationalid/BGD.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import re
2+
from enum import Enum
3+
from types import SimpleNamespace
4+
from typing import TypedDict, Optional
5+
6+
from .util import validate_regexp
7+
8+
9+
class ResidentialType(Enum):
10+
RURAL = 1
11+
MUNICIPALITY = 2
12+
CITY = 3
13+
OTHERS = 4
14+
CANTONMENT = 5
15+
CITY_CORPORATION = 9
16+
17+
18+
class OldParseResult(TypedDict):
19+
distinct: str
20+
"""distinct code"""
21+
residential_type: ResidentialType
22+
"""RMO/residential type"""
23+
policy_station_no: str
24+
"""upazilla/ thana/police station number"""
25+
union_code: str
26+
"""your union code/ward number"""
27+
sn: str
28+
"""serial no"""
29+
30+
31+
class ParseResult(OldParseResult):
32+
yyyy: str
33+
"""birth year"""
34+
35+
36+
class OldNationalID:
37+
"""
38+
Bangladesh National ID number, জাতীয় পরিচয়পত্র, NID, BD
39+
https://en.wikipedia.org/wiki/National_identity_card_(Bangladesh)
40+
http://nationalidcardbangladesh.blogspot.com/2016/04/voter-id-national-id-card-number.html
41+
https://www.facebook.com/428195627559147/photos/a.428251897553520/428251617553548/?type=3
42+
"""
43+
METADATA = SimpleNamespace(**{
44+
'iso3166_alpha2': 'BD',
45+
# length without insignificant chars
46+
'min_length': 13,
47+
'max_length': 13,
48+
'parsable': True,
49+
'checksum': False,
50+
'regexp': re.compile(r'^(?P<distinct>\d{2})'
51+
r'(?P<rmo>\d)'
52+
r'(?P<police>\d{2})'
53+
r'(?P<union>\d{2})'
54+
r'(?P<sn>\d{6})$')
55+
})
56+
57+
RMO_MAP = {
58+
'1': ResidentialType.RURAL,
59+
'2': ResidentialType.MUNICIPALITY,
60+
'3': ResidentialType.CITY,
61+
'4': ResidentialType.OTHERS,
62+
'5': ResidentialType.CANTONMENT,
63+
'9': ResidentialType.CITY_CORPORATION
64+
}
65+
66+
@staticmethod
67+
def validate(id_number: str) -> bool:
68+
"""
69+
Validate
70+
"""
71+
if not validate_regexp(id_number, OldNationalID.METADATA.regexp):
72+
return False
73+
return OldNationalID.parse(id_number) is not None
74+
75+
@staticmethod
76+
def parse(id_number: str) -> Optional[OldParseResult]:
77+
"""
78+
Parse the result
79+
"""
80+
match_obj = OldNationalID.METADATA.regexp.match(id_number)
81+
if not match_obj:
82+
return None
83+
84+
if match_obj.group('rmo') not in OldNationalID.RMO_MAP:
85+
return None
86+
87+
try:
88+
return {
89+
'distinct': match_obj.group('distinct'),
90+
'residential_type': OldNationalID.RMO_MAP[match_obj.group('rmo')],
91+
'policy_station_no': match_obj.group('police'),
92+
'union_code': match_obj.group('union'),
93+
'sn': match_obj.group('sn')
94+
}
95+
except ValueError:
96+
return None
97+
98+
99+
class NationalID(OldNationalID):
100+
"""
101+
Bangladesh National ID number, জাতীয় পরিচয়পত্র, NID, BD
102+
https://en.wikipedia.org/wiki/National_identity_card_(Bangladesh)
103+
http://nationalidcardbangladesh.blogspot.com/2016/04/voter-id-national-id-card-number.html
104+
https://www.facebook.com/428195627559147/photos/a.428251897553520/428251617553548/?type=3
105+
"""
106+
METADATA = SimpleNamespace(**{
107+
'iso3166_alpha2': 'BD',
108+
# length without insignificant chars
109+
'min_length': 13,
110+
'max_length': 13,
111+
'parsable': True,
112+
'checksum': False,
113+
'regexp': re.compile(r'^(?P<yyyy>\d{4})'
114+
r'(?P<distinct>\d{2})'
115+
r'(?P<rmo>\d)'
116+
r'(?P<police>\d{2})'
117+
r'(?P<union>\d{2})'
118+
r'(?P<sn>\d{6})$')
119+
})
120+
121+
@staticmethod
122+
def validate(id_number: str) -> bool:
123+
"""
124+
Validate
125+
"""
126+
if not validate_regexp(id_number, NationalID.METADATA.regexp):
127+
return False
128+
return NationalID.parse(id_number) is not None
129+
130+
@staticmethod
131+
def parse(id_number: str) -> Optional[ParseResult]:
132+
"""
133+
Parse the result
134+
"""
135+
match_obj = NationalID.METADATA.regexp.match(id_number)
136+
if not match_obj:
137+
return None
138+
old_result = OldNationalID.parse(id_number[4:])
139+
if not old_result:
140+
return None
141+
return {
142+
**old_result,
143+
'yyyy': int(id_number[0:4])
144+
}

tests/nationalid/test_BGD.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from unittest import TestCase
2+
3+
from idnumbers.nationalid import BGD
4+
5+
6+
class TestBGDValidation(TestCase):
7+
def test_normal_case(self):
8+
self.assertTrue(BGD.OldNationalID.validate('1592824588424'))
9+
self.assertTrue(BGD.OldNationalID.validate('2610413965404'))
10+
self.assertTrue(BGD.NationalID.validate('19841592824588424'))
11+
self.assertTrue(BGD.NationalID.validate('19892610413965404'))
12+
13+
def test_error_case(self):
14+
self.assertFalse(BGD.OldNationalID.validate('159282458842'))
15+
self.assertFalse(BGD.OldNationalID.validate('1572824588424'))
16+
self.assertFalse(BGD.NationalID.validate('1984159282458844'))
17+
18+
def test_parse(self):
19+
old_result = BGD.OldNationalID.parse('1592824588424')
20+
self.assertEqual('15', old_result['distinct'])
21+
self.assertEqual(BGD.ResidentialType.CITY_CORPORATION, old_result['residential_type'])
22+
self.assertEqual('28', old_result['policy_station_no'])
23+
self.assertEqual('24', old_result['union_code'])
24+
self.assertEqual('588424', old_result['sn'])
25+
26+
result = BGD.NationalID.parse('19892610413965404')
27+
self.assertEqual(1989, result['yyyy'])
28+
self.assertEqual('26', result['distinct'])
29+
self.assertEqual(BGD.ResidentialType.RURAL, result['residential_type'])
30+
self.assertEqual('04', result['policy_station_no'])
31+
self.assertEqual('13', result['union_code'])
32+
self.assertEqual('965404', result['sn'])

0 commit comments

Comments
 (0)