python-stdnum branch master updated. 2.1-23-gd332ee1
[
Date Prev][
Date Next]
[
Thread Prev][
Thread Next]
python-stdnum branch master updated. 2.1-23-gd332ee1
- From: Commits of the python-stdnum project <python-stdnum-commits [at] lists.arthurdejong.org>
- To: python-stdnum-commits [at] lists.arthurdejong.org
- Reply-to: python-stdnum-users [at] lists.arthurdejong.org, python-stdnum-commits [at] lists.arthurdejong.org
- Subject: python-stdnum branch master updated. 2.1-23-gd332ee1
- Date: Sun, 4 Jan 2026 16:39:36 +0100 (CET)
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-stdnum".
The branch, master has been updated
via d332ee1ca2a800900ba47015c6dbf8047b7e50de (commit)
via dd221232a2e42522ffed8bf3ac865c54642754b1 (commit)
from d3ec3bd7fefe0d0a708b6594a66de28777eb9b8d (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
https://arthurdejong.org/git/python-stdnum/commit/?id=d332ee1ca2a800900ba47015c6dbf8047b7e50de
commit d332ee1ca2a800900ba47015c6dbf8047b7e50de
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sun Jan 4 16:32:42 2026 +0100
Add check digit validation to Senegal NINEA
diff --git a/stdnum/sn/ninea.py b/stdnum/sn/ninea.py
index 51f4f34..af84591 100644
--- a/stdnum/sn/ninea.py
+++ b/stdnum/sn/ninea.py
@@ -2,6 +2,7 @@
# coding: utf-8
#
# Copyright (C) 2023 Leandro Regueiro
+# Copyright (C) 2026 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -41,6 +42,10 @@ More information:
'30672212G2'
>>> validate('3067221 2G2')
'30672212G2'
+>>> validate('3067222')
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
>>> validate('1234567 0AZ')
Traceback (most recent call last):
...
@@ -109,6 +114,12 @@ def _validate_cofi(number: str) -> None:
raise InvalidComponent()
+def _checksum(number: str) -> int:
+ number = number.zfill(9)
+ weights = (1, 2, 1, 2, 1, 2, 1, 2, 1)
+ return sum(int(n) * w for n, w in zip(number, weights)) % 10
+
+
def validate(number: str) -> str:
"""Check if the number is a valid Senegal NINEA.
@@ -125,6 +136,8 @@ def validate(number: str) -> str:
raise InvalidFormat()
if cofi:
_validate_cofi(cofi)
+ if _checksum(number) != 0:
+ raise InvalidChecksum()
return number + cofi
diff --git a/tests/test_sn_ninea.doctest b/tests/test_sn_ninea.doctest
index df5ae9e..557fafd 100644
--- a/tests/test_sn_ninea.doctest
+++ b/tests/test_sn_ninea.doctest
@@ -86,7 +86,6 @@ These have been found online and should all be valid numbers.
... 0020884 2 G 3
... 002420983 2G3
... 002502343
-... 00284430 C0
... 004237633 2B2
... 004343430
... 0044440722V1
@@ -109,7 +108,6 @@ These have been found online and should all be valid
numbers.
... 005721809
... 005754339 2V2
... 005844700
-... 006,364,472 2L2
... 00605 33 92
... 006208434
... 006269436
@@ -161,7 +159,6 @@ These have been found online and should all be valid
numbers.
... 21948852B9
... 22486742 S 3
... 24312110V9
-... 244982000
... 25437852G3
... 255 44 772 S 3
... 25833512R2
https://arthurdejong.org/git/python-stdnum/commit/?id=dd221232a2e42522ffed8bf3ac865c54642754b1
commit dd221232a2e42522ffed8bf3ac865c54642754b1
Author: Leandro Regueiro <leandro.regueiro@gmail.com>
Date: Sun Feb 12 16:43:37 2023 +0100
Add support for Senegal TIN
Closes https://github.com/arthurdejong/python-stdnum/pull/395
Closes https://github.com/arthurdejong/python-stdnum/issues/357
diff --git a/stdnum/sn/__init__.py b/stdnum/sn/__init__.py
new file mode 100644
index 0000000..64d908b
--- /dev/null
+++ b/stdnum/sn/__init__.py
@@ -0,0 +1,24 @@
+# __init__.py - collection of Senegal numbers
+# coding: utf-8
+#
+# Copyright (C) 2023 Leandro Regueiro
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Collection of Senegal numbers."""
+
+# provide aliases
+from stdnum.sn import ninea as vat # noqa: F401
diff --git a/stdnum/sn/ninea.py b/stdnum/sn/ninea.py
new file mode 100644
index 0000000..51f4f34
--- /dev/null
+++ b/stdnum/sn/ninea.py
@@ -0,0 +1,144 @@
+# ninea.py - functions for handling Senegal NINEA numbers
+# coding: utf-8
+#
+# Copyright (C) 2023 Leandro Regueiro
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""NINEA (Numéro d'Identification Nationale des Entreprises et Associations,
Senegal tax number).
+
+The National Identification Number for Businesses and Associations (NINEA) is
+a unique tax identifier for tax purposes in Senegal.
+
+This number consists of 7 digits and is usually followed by a 3 digit tax
+identification code called COFI (Code d’Identification Fiscale) that is used
+to indicate the company's tax status and legal structure.
+
+More information:
+
+*
https://www.wikiprocedure.com/index.php/Senegal_-_Obtain_a_Tax_Identification_Number
+* https://nkac-audit.com/comprendre-le-ninea-votre-guide-de-lecture-simplifie/
+* https://www.creationdentreprise.sn/rechercher-une-societe
+* https://www.dci-sn.sn/index.php/obtenir-son-ninea
+* https://e-ninea.ansd.sn/search_annuaire
+
+>>> validate('306 7221')
+'3067221'
+>>> validate('30672212G2')
+'30672212G2'
+>>> validate('3067221 2G2')
+'30672212G2'
+>>> validate('1234567 0AZ')
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> format('30672212G2')
+'3067221 2G2'
+""" # noqa: E501
+
+from stdnum.exceptions import *
+from stdnum.util import clean, isdigits
+
+
+def compact(number: str) -> str:
+ """Convert the number to the minimal representation.
+
+ This strips the number of any valid separators and removes surrounding
+ whitespace.
+ """
+ return clean(number, ' -/,').upper().strip()
+
+
+def _validate_cofi(number: str) -> None:
+ # The first digit of the COFI indicates the tax status
+ # 0: taxpayer subject to the real scheme, not subject to VAT.
+ # 1: taxpayer subject to the single global contribution (TOU).
+ # 2: taxpayer subject to the real scheme and subject to VAT.
+ if number[0] not in '012':
+ raise InvalidComponent()
+ # The second character is a letter that indicates the tax centre:
+ # A: Dakar Plateau 1
+ # B: Dakar Plateau 2
+ # C: Grand Dakar
+ # D: Pikine
+ # E: Rufisque
+ # F: Thiès
+ # G: Centre des grandes Entreprises
+ # H: Louga
+ # J: Diourbel
+ # K: Saint-Louis
+ # L: Tambacounda
+ # M: Kaolack
+ # N: Fatick
+ # P: Ziguinchor
+ # Q: Kolda
+ # R: Prarcelles assainies
+ # S: Professions libérales
+ # T: Guédiawaye
+ # U: Dakar-Medina
+ # V: Dakar liberté
+ # W: Matam
+ # Z: Centre des Moyennes Entreprises
+ if number[1] not in 'ABCDEFGHJKLMNPQRSTUVWZ':
+ raise InvalidComponent()
+ # The third character is a digit that indicates the legal form:
+ # 1: Individual-Natural person
+ # 2: SARL
+ # 3: SA
+ # 4: Simple Limited Partnership
+ # 5: Share Sponsorship Company
+ # 6: GIE
+ # 7: Civil Society
+ # 8: Partnership
+ # 9: Cooperative Association
+ # 0: Other
+ if number[2] not in '0123456789':
+ raise InvalidComponent()
+
+
+def validate(number: str) -> str:
+ """Check if the number is a valid Senegal NINEA.
+
+ This checks the length and formatting.
+ """
+ cofi = ''
+ number = compact(number)
+ if len(number) > 9:
+ cofi = number[-3:]
+ number = number[:-3]
+ if len(number) not in (7, 9):
+ raise InvalidLength()
+ if not isdigits(number):
+ raise InvalidFormat()
+ if cofi:
+ _validate_cofi(cofi)
+ return number + cofi
+
+
+def is_valid(number: str) -> bool:
+ """Check if the number is a valid Senegal NINEA."""
+ try:
+ return bool(validate(number))
+ except ValidationError:
+ return False
+
+
+def format(number: str) -> str:
+ """Reformat the number to the standard presentation format."""
+ number = compact(number)
+ if len(number) in (7, 9):
+ return number
+ return ' '.join([number[:-3], number[-3:]])
diff --git a/tests/test_sn_ninea.doctest b/tests/test_sn_ninea.doctest
new file mode 100644
index 0000000..df5ae9e
--- /dev/null
+++ b/tests/test_sn_ninea.doctest
@@ -0,0 +1,188 @@
+test_sn_ninea.doctest - more detailed doctests for stdnum.sn.ninea module
+
+Copyright (C) 2023 Leandro Regueiro
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.sn.ninea module. It
+tries to test more corner cases and detailed functionality that is not really
+useful as module documentation.
+
+>>> from stdnum.sn import ninea
+
+
+Tests for some corner cases.
+
+>>> ninea.validate('3067221')
+'3067221'
+>>> ninea.validate('30672212G2')
+'30672212G2'
+>>> ninea.validate('306 7221')
+'3067221'
+>>> ninea.validate('3067221 2G2')
+'30672212G2'
+>>> ninea.validate('3067221/2/G/2')
+'30672212G2'
+>>> ninea.validate('3067221-2G2')
+'30672212G2'
+>>> ninea.validate('12345')
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+>>> ninea.validate('VV34567')
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
+>>> ninea.validate('VV345670A0')
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
+>>> ninea.validate('12345679A0')
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ninea.validate('12345670I0')
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ninea.validate('12345670AV')
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ninea.format('306 7221')
+'3067221'
+>>> ninea.format('30672212G2')
+'3067221 2G2'
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 0,017,766 2G3
+... 0,027,476 2G3
+... 0,059 990 2G3
+... 0,513,475 2C1
+... 00140012G3
+... 0014051-2G3
+... 0015041
+... 00153142G3
+... 00154212G3
+... 0019366
+... 0020884 2 G 3
+... 002420983 2G3
+... 002502343
+... 00284430 C0
+... 004237633 2B2
+... 004343430
+... 0044440722V1
+... 0045799442C2
+... 0046 00096 2S9
+... 004641363
+... 004912269
+... 005,830,866 1V1
+... 005023081
+... 005046174
+... 0051126442L1
+... 005117355
+... 005131305 2G3
+... 005216371 2V3
+... 005241550 2C2
+... 0053655402R2
+... 005371026
+... 005623998
+... 00569042P2
+... 005721809
+... 005754339 2V2
+... 005844700
+... 006,364,472 2L2
+... 00605 33 92
+... 006208434
+... 006269436
+... 006295879
+... 0063150572G2
+... 006325741
+... 006373295/0A9
+... 006416681
+... 00661012S3
+... 006715314 2G3
+... 006777463
+... 006900387
+... 006946034
+... 007039292
+... 007057947
+... 00722992 G 3
+... 00722992G3
+... 007266126
+... 007307748 1V1
+... 007389100
+... 007660740
+... 007912662
+... 007992482 2A3
+... 008086242 1E1
+... 008135114
+... 00830 48 0 C 9
+... 008517560
+... 008895586
+... 008895677
+... 0108531 2G3
+... 0120 212
+... 0149642
+... 0185844 2 R 2
+... 0283 408-2C2
+... 0288846 2G3
+... 0316093
+... 0316390
+... 0332891
+... 0366 709 2S2
+... 0404913 2B1
+... 1928863 2B2
+... 19370542G2
+... 2,838,516 2B3
+... 2079376/2/G/3
+... 20839132 S 3
+... 2139378 2V2
+... 21409612D1
+... 2160472-2G3
+... 21948852B9
+... 22486742 S 3
+... 24312110V9
+... 244982000
+... 25437852G3
+... 255 44 772 S 3
+... 25833512R2
+... 2599770 2 B 2
+... 26080342R2
+... 26132492D6
+... 26581702G2
+... 270 773 72 S2
+... 2929406 0G0
+... 30092572G3
+... 30672212G2
+... 4069367 2G3
+... 41130152C2
+... 48522250G0
+... 49615470C0
+... 5,729,803 2V2
+... 50 63 699 2E1
+... 5435 468 0G0
+... 61523762A2
+... 81329702S1
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not ninea.is_valid(x)]
+[]
-----------------------------------------------------------------------
Summary of changes:
stdnum/{gn => sn}/__init__.py | 8 +-
stdnum/sn/ninea.py | 157 +++++++++++++++++++++++++++++++++++
tests/test_sn_ninea.doctest | 185 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 345 insertions(+), 5 deletions(-)
copy stdnum/{gn => sn}/__init__.py (83%)
create mode 100644 stdnum/sn/ninea.py
create mode 100644 tests/test_sn_ninea.doctest
hooks/post-receive
--
python-stdnum
- python-stdnum branch master updated. 2.1-23-gd332ee1,
Commits of the python-stdnum project