1"""Finland."""
2
3# standard
4from functools import lru_cache
5import re
6
7# local
8from validators.utils import validator
9
10
11@lru_cache
12def _business_id_pattern():
13 """Business ID Pattern."""
14 return re.compile(r"^[0-9]{7}-[0-9]$")
15
16
17@lru_cache
18def _ssn_pattern(ssn_check_marks: str):
19 """SSN Pattern."""
20 return re.compile(
21 r"""^
22 (?P<date>(0[1-9]|[1-2]\d|3[01])
23 (0[1-9]|1[012])
24 (\d{{2}}))
25 [ABCDEFYXWVU+-]
26 (?P<serial>(\d{{3}}))
27 (?P<checksum>[{check_marks}])$""".format(
28 check_marks=ssn_check_marks
29 ),
30 re.VERBOSE,
31 )
32
33
34@validator
35def fi_business_id(value: str, /):
36 """Validate a Finnish Business ID.
37
38 Each company in Finland has a distinct business id. For more
39 information see [Finnish Trade Register][1]
40
41 [1]: http://en.wikipedia.org/wiki/Finnish_Trade_Register
42
43 Examples:
44 >>> fi_business_id('0112038-9') # Fast Monkeys Ltd
45 # Output: True
46 >>> fi_business_id('1234567-8') # Bogus ID
47 # Output: ValidationError(func=fi_business_id, ...)
48
49 Args:
50 value:
51 Business ID string to be validated.
52
53 Returns:
54 (Literal[True]): If `value` is a valid finnish business id.
55 (ValidationError): If `value` is an invalid finnish business id.
56 """
57 if not value:
58 return False
59 if not re.match(_business_id_pattern(), value):
60 return False
61 factors = [7, 9, 10, 5, 8, 4, 2]
62 numbers = map(int, value[:7])
63 checksum = int(value[8])
64 modulo = sum(f * n for f, n in zip(factors, numbers)) % 11
65 return (11 - modulo == checksum) or (modulo == checksum == 0)
66
67
68@validator
69def fi_ssn(value: str, /, *, allow_temporal_ssn: bool = True):
70 """Validate a Finnish Social Security Number.
71
72 This validator is based on [django-localflavor-fi][1].
73
74 [1]: https://github.com/django/django-localflavor-fi/
75
76 Examples:
77 >>> fi_ssn('010101-0101')
78 # Output: True
79 >>> fi_ssn('101010-0102')
80 # Output: ValidationError(func=fi_ssn, args=...)
81
82 Args:
83 value:
84 Social Security Number to be validated.
85 allow_temporal_ssn:
86 Whether to accept temporal SSN numbers. Temporal SSN numbers are the
87 ones where the serial is in the range [900-999]. By default temporal
88 SSN numbers are valid.
89
90 Returns:
91 (Literal[True]): If `value` is a valid finnish SSN.
92 (ValidationError): If `value` is an invalid finnish SSN.
93 """
94 if not value:
95 return False
96 ssn_check_marks = "0123456789ABCDEFHJKLMNPRSTUVWXY"
97 if not (result := re.match(_ssn_pattern(ssn_check_marks), value)):
98 return False
99 gd = result.groupdict()
100 checksum = int(gd["date"] + gd["serial"])
101 return (
102 int(gd["serial"]) >= 2
103 and (allow_temporal_ssn or int(gd["serial"]) <= 899)
104 and ssn_check_marks[checksum % len(ssn_check_marks)] == gd["checksum"]
105 )