Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2014-10-17 21:54:28 +0200
committerArthur de Jong <arthur@arthurdejong.org>2014-10-17 21:54:28 +0200
commite2948bb6bd0ebf7b3828ac989a56d0e61982c545 (patch)
tree23008d1a5df284a5b304c7ca5108e781f96bd43e
parent2700b7a064b0d0b7dcb71c91de33207826c15cea (diff)
parente5250be45092363504ff61c868938439ec958985 (diff)
Add Ecuadorian CI and RUC numbers
Add modules for Ecuadorian Identification Card (CI - Cédula de identidad) and Fiscal Numbers (RUC - Registro Único de Contribuyentes) See: https://github.com/arthurdejong/python-stdnum/pull/12
-rw-r--r--stdnum/ec/__init__.py24
-rw-r--r--stdnum/ec/ci.py78
-rw-r--r--stdnum/ec/ruc.py93
-rw-r--r--tests/test_ec_ci.doctest65
-rw-r--r--tests/test_ec_ruc.doctest164
5 files changed, 424 insertions, 0 deletions
diff --git a/stdnum/ec/__init__.py b/stdnum/ec/__init__.py
new file mode 100644
index 0000000..18d02a8
--- /dev/null
+++ b/stdnum/ec/__init__.py
@@ -0,0 +1,24 @@
+# __init__.py - collection of Ecuadorian numbers
+# coding: utf-8
+#
+# Copyright (C) 2014 Jonathan Finlay
+#
+# 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 Ecuadorian numbers."""
+
+# provide vat as an alias
+from stdnum.ec import ruc as vat
diff --git a/stdnum/ec/ci.py b/stdnum/ec/ci.py
new file mode 100644
index 0000000..9343e47
--- /dev/null
+++ b/stdnum/ec/ci.py
@@ -0,0 +1,78 @@
+# ci.py - functions for handling Ecuadorian personal identity codes
+# coding: utf-8
+#
+# Copyright (C) 2014 Jonathan Finlay
+# Copyright (C) 2014 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
+# 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
+
+"""CI (Cédula de identidad, Ecuadorian personal identity code).
+
+The CI is a 10 digit number used to identify Ecuadorian citizens.
+
+>>> validate('171430710-3')
+'1714307103'
+>>> validate('1714307104') # invalid check digit
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+>>> validate('171430710') # digit missing
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+def compact(number):
+ """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 _checksum(number):
+ """Calculate a checksum over the number."""
+ fold = lambda x: x - 9 if x > 9 else x
+ return sum(fold((2 - (i % 2)) * int(n))
+ for i, n in enumerate(number)) % 10
+
+
+def validate(number):
+ """Checks to see if the number provided is a valid CI number. This
+ checks the length, formatting and check digit."""
+ number = compact(number)
+ if len(number) != 10:
+ raise InvalidLength()
+ if not number.isdigit():
+ raise InvalidFormat()
+ if number[:2] < '01' or number[:2] > '24':
+ raise InvalidComponent() # invalid province code
+ if number[2] > '5':
+ raise InvalidComponent() # third digit wrong
+ if _checksum(number) != 0:
+ raise InvalidChecksum()
+ return number
+
+
+def is_valid(number):
+ """Checks to see if the number provided is a valid CI number. This
+ checks the length, formatting and check digit."""
+ try:
+ return bool(validate(number))
+ except ValidationError:
+ return False
diff --git a/stdnum/ec/ruc.py b/stdnum/ec/ruc.py
new file mode 100644
index 0000000..8887467
--- /dev/null
+++ b/stdnum/ec/ruc.py
@@ -0,0 +1,93 @@
+# ruc.py - functions for handling Ecuadorian fiscal numbers
+# coding: utf-8
+#
+# Copyright (C) 2014 Jonathan Finlay
+# Copyright (C) 2014 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
+# 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
+
+"""RUC (Registro Único de Contribuyentes, Ecuadorian company tax number).
+
+The RUC is a tax identification number for legal entities. It has 13 digits
+where the third digit is a number denoting the type of entity.
+
+>>> validate('1792060346-001')
+'1792060346001'
+>>> validate('1763154690001') # invalid check digit
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+>>> validate('179206034601') # too short
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+"""
+
+from stdnum.ec import ci
+from stdnum.exceptions import *
+
+
+__all__ = ['compact', 'validate', 'is_valid']
+
+
+# use the same compact function as CI
+compact = ci.compact
+
+
+def _checksum(number, weights):
+ """Calculate a checksum over the number given the weights."""
+ return sum(weights[i] * int(n) for i, n in enumerate(number)) % 11
+
+
+def validate(number):
+ """Checks to see if the number provided is a valid RUC number. This
+ checks the length, formatting, check digit and check sum."""
+ number = compact(number)
+ if len(number) != 13:
+ raise InvalidLength()
+ if not number.isdigit():
+ raise InvalidFormat()
+ if number[:2] < '01' or number[:2] > '24':
+ raise InvalidComponent() # invalid province code
+ if number[2] < '6':
+ # 0..5 = natural RUC: CI plus establishment number
+ if number[-3:] == '000':
+ raise InvalidComponent() # establishment number wrong
+ ci.validate(number[:10])
+ elif number[2] == '6':
+ # 6 = public RUC
+ if number[-4:] == '0000':
+ raise InvalidComponent() # establishment number wrong
+ if _checksum(number[:9], (3, 2, 7, 6, 5, 4, 3, 2, 1)) != 0:
+ raise InvalidChecksum()
+ elif number[2] == '9':
+ # 9 = juridical RUC
+ if number[-3:] == '000':
+ raise InvalidComponent() # establishment number wrong
+ if _checksum(number[:10], (4, 3, 2, 7, 6, 5, 4, 3, 2, 1)) != 0:
+ raise InvalidChecksum()
+ else:
+ raise InvalidComponent() # third digit wrong
+ return number
+
+
+def is_valid(number):
+ """Checks to see if the number provided is a valid RUC number. This
+ checks the length, formatting and check digit."""
+ try:
+ return bool(validate(number))
+ except ValidationError:
+ return False
diff --git a/tests/test_ec_ci.doctest b/tests/test_ec_ci.doctest
new file mode 100644
index 0000000..08efb8b
--- /dev/null
+++ b/tests/test_ec_ci.doctest
@@ -0,0 +1,65 @@
+test_ec_ci.doctest - more detailed doctests for stdnum.ec.ci module
+
+Copyright (C) 2014 Jonathan Finlay
+Copyright (C) 2014 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
+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.ec.ci. It tries to
+cover more corner cases and detailed functionality that is not really useful
+as module documentation.
+
+>>> from stdnum.ec import ci
+>>> from stdnum.exceptions import *
+
+
+Normal values that should just work.
+
+>>> ci.validate('1714307103')
+'1714307103'
+>>> ci.validate('171430710-3')
+'1714307103'
+>>> ci.validate('0602910945')
+'0602910945'
+>>> ci.validate('0926687856')
+'0926687856'
+>>> ci.validate('0910005917')
+'0910005917'
+
+
+Some invalid numbers.
+
+>>> ci.validate('1714307104') # invalid check digit
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+>>> ci.validate('171430710') # digit missing
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+>>> ci.validate('123A567890') # contains a letter
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
+>>> ci.validate('1784307108') # third digit wrong
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ci.validate('8814307107') # invalid province code
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
diff --git a/tests/test_ec_ruc.doctest b/tests/test_ec_ruc.doctest
new file mode 100644
index 0000000..2f1065f
--- /dev/null
+++ b/tests/test_ec_ruc.doctest
@@ -0,0 +1,164 @@
+test_ec_ruc.doctest - more detailed doctests for stdnum.ec.ruc module
+
+Copyright (C) 2014 Jonathan Finlay
+Copyright (C) 2014 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
+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.ec.ruc. It tries to
+cover more corner cases and detailed functionality that is not really useful
+as module documentation.
+
+>>> from stdnum.ec import ruc
+>>> from stdnum.exceptions import *
+
+
+Normal natural RUC values (third digit less than 6) that should just work.
+
+>>> numbers = '''
+... 0101016905001
+... 0602910945001
+... 0910005917001
+... 0926687856001
+... 1001152287001
+... 1102755442001
+... 1104552037001
+... 1311919078001
+... 1700672486001
+... 1702264233001
+... 1704159860001
+... 1710034065001
+... 1710585264001
+... 1710589373001
+... 1713238234001
+... 1714307103001
+... 1721788659001
+... 1803557964001
+... '''
+>>> [x for x in numbers.splitlines() if x and not ruc.is_valid(x)]
+[]
+
+
+Normal public RUC values (third digit is 6) that should just work.
+
+>>> numbers = '''
+... 0160001910001
+... 0260001060001
+... 0360001040001
+... 0560000540001
+... 0660000280001
+... 0660000600001
+... 0660000870001
+... 0968529830001
+... 1060000420001
+... 1060000690001
+... 1060008080001
+... 1060024600001
+... 1360000630001
+... 1560000780001
+... 1760001040001
+... 1760001550001
+... 1760009880001
+... 1768007390001
+... 1768152130001
+... 2160011760001
+... '''
+>>> [x for x in numbers.splitlines() if x and not ruc.is_valid(x)]
+[]
+
+
+Normal juridical RUC values (third digit is 9) that should just work.
+
+>>> numbers = '''
+... 0190155722001
+... 0490002669001
+... 0590041920001
+... 0790024656001
+... 0990138850001
+... 0992397535001
+... 1190015110001
+... 1390007791001
+... 1390089410001
+... 1390091474001
+... 1790011674001
+... 1790085783001
+... 1790325083001
+... 1791280172001
+... 1791714350001
+... 1792060346001
+... 1792141869001
+... 1792373255001
+... 1890001323001
+... 1890037646001
+... '''
+>>> [x for x in numbers.splitlines() if x and not ruc.is_valid(x)]
+[]
+
+
+Values that are invalid in one way or another:
+
+>>> ruc.validate('179206034601') # too short
+Traceback (most recent call last):
+ ...
+InvalidLength: ...
+>>> ruc.validate('17920603A6001') # contains a character
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
+>>> ruc.validate('0170000610001') # third digit invalid
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+
+>>> ruc.validate('1763154690001') # invalid check digit in natural RUC
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+>>> ruc.validate('0160000610001') # invalid check digit in public RUC
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+>>> ruc.validate('0190115799001') # invalid check digit in juridical RUC
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+
+>>> ruc.validate('8810034069001') # invalid province code in natural RUC
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ruc.validate('8868152120001') # invalid province code in public RUC
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ruc.validate('8892397539001') # invalid province code in juridical RUC
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+
+>>> ruc.validate('0926687856000') # invalid establishment in natural RUC
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ruc.validate('1760001550000') # invalid establishment in public RUC
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+>>> ruc.validate('0992397535000') # invalid establishment in juridical RUC
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...