Skip to content

Commit c142cf5

Browse files
authored
fix number verification issues for mt, gb and de (#42)
* fix: changed personal_tin to be idnr for de * feat: added nin as personal tin for gb * fix: updated personal tin validation for mt
1 parent 75e790c commit c142cf5

File tree

5 files changed

+153
-41
lines changed

5 files changed

+153
-41
lines changed

stdnum/de/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@
2222

2323
# provide businessid as an alias
2424
from stdnum.de import handelsregisternummer as businessid # noqa: F401
25-
from stdnum.de import stnr as personal_tin # noqa: F401
25+
from stdnum.de import idnr as personal_tin # noqa: F401
2626
from stdnum.de import stnr as business_tin # noqa: F401

stdnum/de/personal_tin.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

stdnum/gb/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@
1919
# 02110-1301 USA
2020

2121
"""Collection of United Kingdom numbers."""
22+
23+
from stdnum.gb import nin as personal_tin # noqa: F401

stdnum/gb/nin.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# nin.py - functions for handling United Kingdom National Insurance Numbers
2+
3+
"""NIN (United Kingdom National Insurance Number).
4+
5+
The National Insurance Number (NIN or NINO) is used in the United Kingdom to
6+
identify individuals for the social security system. It consists of two letters,
7+
six digits, and a final letter (suffix).
8+
9+
The format has several restrictions:
10+
- First letter cannot be D, F, I, Q, U, or V
11+
- Second letter cannot be D, F, I, Q, U, V, or O
12+
- Certain prefix combinations are invalid: BG, GB, NK, KN, NT, TN, ZZ
13+
- The suffix must be A, B, C, or D
14+
15+
More information:
16+
17+
* https://en.wikipedia.org/wiki/National_Insurance_number
18+
* https://www.gov.uk/hmrc-internal-manuals/national-insurance-manual/nim39110
19+
20+
>>> validate('AB123456C')
21+
'AB123456C'
22+
>>> validate('ab 12 34 56 c')
23+
'AB123456C'
24+
>>> validate('DQ123456C')
25+
Traceback (most recent call last):
26+
...
27+
stdnum.exceptions.InvalidFormat: First letter cannot be D, F, I, Q, U, or V
28+
>>> validate('BG123456A') # invalid prefix
29+
Traceback (most recent call last):
30+
...
31+
stdnum.exceptions.InvalidFormat: Invalid prefix combination
32+
>>> validate('DF123456A') # invalid first two letters
33+
Traceback (most recent call last):
34+
...
35+
stdnum.exceptions.InvalidFormat: First letter cannot be D, F, I, Q, U, or V
36+
>>> format('AB123456C')
37+
'AB 12 34 56 C'
38+
"""
39+
40+
from stdnum.exceptions import *
41+
from stdnum.util import clean, isdigits
42+
43+
44+
def compact(number):
45+
"""Convert the number to the minimal representation. This strips the
46+
number of any valid separators and removes surrounding whitespace."""
47+
return clean(number, ' -').strip().upper()
48+
49+
50+
def validate(number):
51+
"""Check if the number is valid. This checks the format against
52+
the official specifications."""
53+
number = compact(number)
54+
55+
# Basic length check
56+
if len(number) != 9:
57+
raise InvalidLength()
58+
59+
# Check first letter restrictions
60+
if number[0] in 'DFIQUV':
61+
raise InvalidFormat('First letter cannot be D, F, I, Q, U, or V')
62+
63+
# Check second letter restrictions
64+
if number[1] in 'DFIQUVO':
65+
raise InvalidFormat('Second letter cannot be D, F, I, Q, U, V, or O')
66+
67+
# Check for invalid prefix combinations
68+
invalid_prefixes = {'BG', 'GB', 'NK', 'KN', 'NT', 'TN', 'ZZ'}
69+
if number[:2] in invalid_prefixes:
70+
raise InvalidFormat('Invalid prefix combination')
71+
72+
# Check if middle 6 characters are digits
73+
if not isdigits(number[2:8]):
74+
raise InvalidFormat('Middle 6 characters must be digits')
75+
76+
# Check if suffix is valid
77+
if number[8] not in 'ABCD':
78+
raise InvalidFormat('Suffix must be A, B, C, or D')
79+
80+
return number
81+
82+
83+
def is_valid(number):
84+
"""Check if the number is valid."""
85+
try:
86+
return bool(validate(number))
87+
except ValidationError:
88+
return False
89+
90+
91+
def format(number, separator=' '):
92+
"""Reformat the number to the standard presentation format."""
93+
number = compact(number)
94+
return separator.join([number[0:2], number[2:4], number[4:6], number[6:8], number[8:]])

stdnum/mt/personal_tin.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,74 @@
1+
# personal_tin.py - functions for handling Maltese Personal Tax Identification Numbers
2+
3+
"""MT Personal TIN (Maltese Personal Tax Identification Number).
4+
5+
The Maltese Personal Tax Identification Number is used for tax purposes in Malta.
6+
It can be in one of two formats:
7+
- National format: 7 digits followed by a letter (M, G, A, P, L, H, B, or Z)
8+
- Non-national format: 9 digits
9+
10+
More information:
11+
* https://www.oecd.org/content/dam/oecd/en/topics/policy-issue-focus/aeoi/malta-tin.pdf
12+
13+
>>> validate('1234567M')
14+
'1234567M'
15+
>>> validate('123 456 7M')
16+
'1234567M'
17+
>>> validate('123456789')
18+
'123456789'
19+
>>> validate('123-456-789')
20+
'123456789'
21+
>>> validate('1234567X') # invalid letter
22+
Traceback (most recent call last):
23+
...
24+
stdnum.exceptions.InvalidFormat: National format must be 7 digits followed by M, G, A, P, L, H, B, or Z
25+
>>> validate('1234567') # invalid length
26+
Traceback (most recent call last):
27+
...
28+
stdnum.exceptions.InvalidLength: Number must be 8 characters (national format) or 9 digits (non-national format)
29+
>>> validate('12345678A') # invalid format (8 digits + letter)
30+
Traceback (most recent call last):
31+
...
32+
stdnum.exceptions.InvalidFormat: Non-national format must be 9 digits
33+
>>> is_valid('1234567M')
34+
True
35+
>>> is_valid('1234567X')
36+
False
37+
"""
38+
39+
import re
140
from stdnum.exceptions import *
241
from stdnum.util import clean, isdigits
342

443

544
def compact(number):
45+
"""Convert the number to the minimal representation. This strips the
46+
number of any valid separators and removes surrounding whitespace."""
647
number = clean(number, ' -,/').upper().strip()
748
return number
849

950

1051
def validate(number):
11-
"""
12-
TODO
13-
simple regex check
14-
"""
52+
"""Check if the number is valid. This checks the format against
53+
the official specifications."""
1554
number = compact(number)
16-
if (not isdigits(number[:-1])) or (number[-1] not in ['M', 'G', 'A', 'P', 'L', 'H', 'B', 'Z']):
17-
raise InvalidFormat()
18-
if len(number) != 8:
19-
raise InvalidLength()
55+
56+
# Check Maltese national format: 7 digits + 1 valid letter
57+
if len(number) == 8:
58+
if not re.fullmatch(r'^[0-9]{7}[MGAPLHBZ]$', number, flags=re.IGNORECASE):
59+
raise InvalidFormat('National format must be 7 digits followed by M, G, A, P, L, H, B, or Z')
60+
# Check non-national format: 9 digits
61+
elif len(number) == 9:
62+
if not isdigits(number):
63+
raise InvalidFormat('Non-national format must be 9 digits')
64+
else:
65+
raise InvalidLength('Number must be 8 characters (national format) or 9 digits (non-national format)')
66+
2067
return number
2168

2269

2370
def is_valid(number):
71+
"""Check if the number is valid."""
2472
try:
2573
return bool(validate(number))
2674
except ValidationError:

0 commit comments

Comments
 (0)