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