8000 Add Italian AIC codes · unho/python-stdnum@8433821 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8433821

Browse files
FabrizioMontanariarthurdejong
authored andcommitted
Add Italian AIC codes
Closes< 10000 /span> arthurdejong#193
1 parent f7b968c commit 8433821

File tree

2 files changed

+260
-0
lines changed

2 files changed

+260
-0
lines changed

stdnum/it/aic.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# aic.py - functions for handling Italian AIC codes
2+
# coding: utf-8
3+
#
4+
# This file is based on pyAIC Python library.
5+
# https://github.com/FabrizioMontanari/pyAIC
6+
#
7+
# Copyright (C) 2020 Fabrizio Montanari
8+
#
9+
# This library is free software; you can redistribute it and/or
10+
# modify it under the terms of the GNU Lesser General Public
11+
# License as published by the Free Software Foundation; either
12+
# version 2.1 of the License, or (at your option) any later version.
13+
#
14+
# This library is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
# Lesser General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU Lesser General Public
20+
# License along with this library; if not, write to the Free Software
21+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22+
# 02110-1301 USA
23+
24+
"""AIC (Italian code for identification of drugs).
25+
26+
AIC codes are used to identify drugs allowed to be sold in Italy. Codes are
27+
issued by the Italian Drugs Agency (AIFA, Agenzia Italiana del Farmaco), the
28+
government authority responsible for drugs regulation in Italy.
29+
30+
The number consists of 9 digits and includes a check digit.
31+
32+
More information:
33+
34+
* https://www.gazzettaufficiale.it/do/atto/serie_generale/caricaPdf?cdimg=14A0566800100010110001&dgu=2014-07-18&art.dataPubblicazioneGazzetta=2014-07-18&art.codiceRedazionale=14A05668&art.num=1&art.tiposerie=SG
35+
36+
>>> validate('000307052') # BASE10 format
37+
'000307052'
38+
>>> validate('009CVD') # BASE32 format is converted
39+
'000307052'
40+
>>> validate_base10('000307052')
41+
'000307052'
42+
>>> validate_base32('009CVD')
43+
'000307052'
44+
>>> to_base32('000307052')
45+
'009CVD'
46+
>>> from_base32('009CVD')
47+
'000307052'
48+
"""
49+
50+
from stdnum.exceptions import *
51+
from stdnum.util import clean, isdigits
52+
53+
54+
# the table of AIC BASE32 allowed chars.
55+
_base32_alphabet = '0123456789BCDFGHJKLMNPQRSTUVWXYZ'
56+
57+
58+
def compact(number):
59+
"""Convert the number to the minimal representation."""
60+
return clean(number, ' ').upper().strip()
61+
62+
63+
def from_base32(number):
64+
"""Convert a BASE32 representation of an AIC to a BASE10 one."""
65+
number = compact(number)
66+
if not all(x in _base32_alphabet for x in number):
67+
raise InvalidFormat()
68+
s = sum(_base32_alphabet.index(n) * 32 ** i
69+
for i, n in enumerate(reversed(number)))
70+
return str(s).zfill(9)
71+
72+
73+
def to_base32(number):
74+
"""Convert a BASE10 representation of an AIC to a BASE32 one."""
75+
number = compact(number)
76+
if not isdigits(number):
77+
raise InvalidFormat()
78+
res = ''
79+
remainder = int(number)
80+
while remainder > 31:
81+
res = _base32_alphabet[remainder % 32] + res
82+
remainder = remainder // 32
83+
res = _base32_alphabet[remainder] + res
84+
return res.zfill(6)
85+
86+
87+
def calc_check_digit(number):
88+
"""Calculate the check digit for the BASE10 AIC code."""
89+
number = compact(number)
90+
weights = (1, 2, 1, 2, 1, 2, 1, 2)
91+
return str(sum((x // 10) + (x % 10)
92+
for x in (w * int(n) for w, n in zip(weights, number))) % 10)
93+
94+
95+
def validate_base10(number):
96+
"""Check if a string is a valid BASE10 representation of an AIC."""
97+
number = compact(number)
98+
if len(number) != 9:
99+
raise InvalidLength()
100+
if not isdigits(number):
101+
raise InvalidFormat()
102+
if number[0] != '0':
103+
raise InvalidComponent()
104+
if calc_check_digit(number) != number[-1]:
105+
raise InvalidChecksum()
106+
return number
107+
108+
109+
def validate_base32(number):
110+
"""Check if a string is a valid BASE32 representation of an AIC."""
111+
number = compact(number)
112+
if len(number) != 6:
113+
raise InvalidLength()
114+
return validate_base10(from_base32(number))
115+
116+
117+
def validate(number):
118+
"""Check if a string is a valid AIC. BASE10 is the canonical form and
119+
is 9 chars long, while BASE32 is 6 chars."""
120+
number = compact(number)
121+
if len(number) == 6:
122+
return validate_base32(number)
123+
else:
124+
return validate_base10(number)
125+
126+
127+
def is_valid(number):
128+
"""Check if the given string is a valid AIC code."""
129+
try:
130+
return bool(validate(number))
131+
except ValidationError:
132+
return False

tests/test_it_aic.doctest

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
test_it_aic.doctest - tests for the stdnum.it.aic module
2+
3+
Copyright (C) 2020 Fabrizio Montanari
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18+
02110-1301 USA
19+
20+
21+
This file contains more detailed doctests for the stdnum.it.aic module.
22+
23+
>>> from stdnum.it import aic
24+
>>> from stdnum.exceptions import *
25+
26+
27+
Some valid codes with their BASE10/BASE32 representations.
28+
29+
>>> aic10_valid = ['000307052', '000307037', '001738032', '001738020', '042645046', '045359015']
30+
>>> aic32_valid = ['009CVD', '009CUX', '01P19J', '01P194', '18PFKQ', '1C87X7']
31+
>>> [x for x in aic10_valid + aic32_valid if x and not aic.is_valid(x)]
32+
[]
33+
>>> [aic.to_base32(x) for x in aic10_valid] == aic32_valid
34+
True
35+
>>> [aic.from_base32(x) for x in aic32_valid] == aic10_valid
36+
True
37+
38+
39+
We offer conversion functions between BASE10 and BASE32 formats.
40+
41+
>>> aic.to_base32('000307037')
42+
'009CUX'
43+
>>> aic.from_base32('009CUX')
44+
'000307037'
45+
>>> aic.to_base32('009CUX')
46+
Traceback (most recent call last):
47+
...
48+
InvalidFormat: ...
49+
>>> aic.from_base32('009CV$')
50+
Traceback (most recent call last):
51+
...
52+
InvalidFormat: ...
53+
54+
55+
Check digit calculation corner cases.
56+
57+
>>> aic.calc_check_digit('00030705')
58+
'2'
59+
>>> aic.calc_check_digit('010307052')
60+
'4'
61+
62+
63+
Tests for various corner cases.
64+
65+
>>> aic.validate('000307052')
66+
'000307052'
67+
>>> aic.validate('00030705.')
68+
Traceback (most recent call last):
69+
...
70+
InvalidFormat: ...
71+
>>> aic.validate('00307052')
72+
Traceback (most recent call last):
73+
...
74+
InvalidLength: ...
75+
>>> aic.validate('000307053')
76+
Traceback (most recent call last):
77+
...
78+
InvalidChecksum: ...
79+
>>> aic.validate(307053) # not a string type
80+
Traceback (most recent call last):
81+
...
82+
InvalidFormat: ...
83+
>>> aic.validate('100307053') # does not start with 0
84+
Traceback (most recent call last):
85+
...
86+
InvalidComponent: ...
87+
>>> aic.validate('0003070.3')
88+
Traceback (most recent call last):
89+
...
90+
InvalidFormat: ...
91+
>>> aic.validate('009CVD')
92+
'000307052'
93+
>>> aic.validate('09CVD')
94+
Traceback (most recent call last):
95+
...
96+
InvalidLength: ...
97+
>>> aic.validate('2ZP43F') # BASE10 format does not start with 0
98+
Traceback (most recent call last):
99+
...
100+
InvalidComponent: ...
101+
>>> aic.validate('009CV$')
102+
Traceback (most recent call last):
103+
...
104+
InvalidFormat: ...
105+
106+
107+
We can also validate BASE10 or BASE32 format explicitly.
108+
109+
>>> aic.validate_base10('009CVD')
110+
Traceback (most recent call last):
111+
...
112+
InvalidLength: ...
113+
>>> aic.validate_base32('009CVD1')
114+
Traceback (most recent call last):
115+
...
116+
InvalidLength: ...
117+
>>> aic.validate_base32('009CVL')
118+
Traceback (most recent call last):
119+
...
120+
InvalidChecksum: ...
121+
>>> aic.validate_base32('00$CVD')
122+
Traceback (most recent call last):
123+
...
124+
InvalidFormat: ...
125+
>>> aic.validate_base32('100307052')
126+
Traceback (most recent call last):
127+
...
128+
InvalidLength: ...

0 commit comments

Comments
 (0)
0