Skip to content
Open
22 changes: 22 additions & 0 deletions stdnum/kz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# __init__.py - collection of Kazakhstan numbers
# coding: utf-8
#
# Copyright (C) 2022 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, see <https://www.gnu.org/licenses/>.

"""Collection of Kazakhstan numbers."""

# provide aliases
from stdnum.kz import bin as vat # noqa: F401
120 changes: 120 additions & 0 deletions stdnum/kz/bin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# bin.py - functions for handling Kazakhstan BIN numbers
# coding: utf-8
#
# Copyright (C) 2022 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, see <https://www.gnu.org/licenses/>.

"""BIN or "БСН" (Business Identification Number, "бизнес-сәйкестендіру нөмірі",
Kazakhstan tax number).

This number consists of 12 digits and consists of five parts:

* First part: consists of 4 digits representing the year (the last two digits)
and month of registration or re-registration.
* Second part: consists of 1 digit representing the type of legal entity or
individual entrepreneur (C):
4 - for resident legal entities;
5 - for non-resident legal entities;
6 - for IP (C).
* Third part: consists of 1 digit providing additional information:
0 - head unit of a legal entity or individual entrepreneur (S);
1 - branch of a legal entity or individual entrepreneur (S);
2 - representation of a legal entity or individual entrepreneur (S);
3 - a peasant (farm) farm operating on the basis of joint entrepreneurship.
* Fourth part: consists of 5 digits and is a serial number.
* Fifth part: consists of 1 check digit.

More information:

* https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Kazakhstan-TIN.pdf

>>> validate('940140000385')
'940140000385'
>>> validate('990 140 004 654')
'990140004654'
>>> validate('12345')
Traceback (most recent call last):
...
InvalidLength: ...
>>> validate('12345678901X')
Traceback (most recent call last):
...
InvalidFormat: ...
>>> validate('949940000385')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> validate('940190000385')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> validate('940149000385')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> validate('940140000386')
Traceback (most recent call last):
...
InvalidChecksum: ...
>>> format('990 140 004 654')
'990140004654'
""" # noqa: E501

from stdnum.exceptions import *
from stdnum.kz.iin import calc_check_digit
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, ' ')


def validate(number: str) -> str:
"""Check if the number is a valid Kazakhstan BIN number.

This checks the length and formatting.
"""
number = compact(number)
if len(number) != 12:
raise InvalidLength()
if not isdigits(number):
raise InvalidFormat()
if int(number[2:4]) not in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12):
raise InvalidComponent()
if number[4] not in ('4', '5', '6'):
raise InvalidComponent()
if number[5] not in ('0', '1', '2', '3'):
raise InvalidComponent()
if calc_check_digit(number[:-1]) != number[-1]:
raise InvalidChecksum()
return number


def is_valid(number: str) -> bool:
"""Check if the number is a valid Kazakhstan BIN number."""
try:
return bool(validate(number))
except ValidationError:
return False


def format(number: str) -> str:
"""Reformat the number to the standard presentation format."""
return compact(number)
136 changes: 136 additions & 0 deletions stdnum/kz/iin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# iin.py - functions for handling Kazakhstani individual identification numbers
# coding: utf-8
#
# Copyright (C) 2026 Luca Sicurello
#
# 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, see <https://www.gnu.org/licenses/>.

"""IIN (ЖСН, Жеке сәйкестендіру нөмірі, Kazakhstani Individual identification number).

It is a 12-digit number of which the first 6 digits denote the person's
birth date in the format YYMMDD, the next digit encodes the gender and
century of birth, the next four digits represent a birth serial number
and the last digit is a check digit.

* https://www.gov.kz/article/648?lang=kk
* https://ru.wikipedia.org/wiki/Индивидуальный_идентификационный_номер


>>> compact('921029 35085 1')
'921029350851'
>>> validate('921029350851')
'921029350851'
>>> get_birth_date('921029350851')
datetime.date(1992, 10, 29)
>>> validate('92102B50851') # invalid digit
Traceback (most recent call last):
...
InvalidFormat: ...
>>> validate('921529350859') # invalid date
Traceback (most recent call last):
...
InvalidComponent: ...
"""

from __future__ import annotations

import datetime

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, ' ')


def calc_check_digit(number: str) -> str:
"""Calculate the check digit. The number passed should not have
the check digit included."""
s = sum(w * int(n) for w, n in zip(range(1, 12), number[:11])) % 11
if s != 10:
return str(s)

s = sum(w * int(n) for w, n in zip(range(3, 14), number[:11])) % 11
if s != 10:
return str(s)

raise InvalidComponent()


def get_birth_date(number: str) -> datetime.date:
"""Get the birth date from the person's Individual identification number."""
number = compact(number)
year = int(number[0:2])
month = int(number[2:4])
day = int(number[4:6])

if number[6] == '0': # no century info for foreign nationals
today = datetime.date.today()
year += (today.year // 100) * 100
if year >= today.year:
year -= 100

elif number[6] in '12':
year += 1800
elif number[6] in '34':
year += 1900
elif number[6] in '56':
year += 2000
else:
raise InvalidComponent()

try:
return datetime.date(year, month, day)
except ValueError:
raise InvalidComponent()


def get_gender(number: str) -> str | None:
"""Get the gender of the person's Individual identification number."""
number = compact(number)
if number[6] == '0':
return None
elif int(number[6]) % 2:
return 'M'
else:
return 'F'


def validate(number: str) -> str:
"""Check if the given Individual identification number is valid.
This checks the length and whether the check digit is correct."""
number = compact(number)
if not isdigits(number):
raise InvalidFormat()
if len(number) != 12:
raise InvalidLength()
if get_birth_date(number) > datetime.date.today():
raise InvalidComponent()
if calc_check_digit(number[:-1]) != number[-1]:
raise InvalidChecksum()
return number


def is_valid(number: str) -> bool:
"""Check if the given Individual identification number is valid."""
try:
return bool(validate(number))
except ValidationError:
return False
Loading
Loading