Skip to content

Instantly share code, notes, and snippets.

@bmispelon
Created February 14, 2013 13:55
Show Gist options
  • Select an option

  • Save bmispelon/4952993 to your computer and use it in GitHub Desktop.

Select an option

Save bmispelon/4952993 to your computer and use it in GitHub Desktop.
French social security number validation
from collections import namedtuple
FrenchSSNTuple = namedtuple('FrenchSSNTuple', [
'sex',
'year',
'month',
'departement',
'city',
'country',
'id_month_city',
'control_key',
])
def split_SSN(ssn):
"""
Split a French SSN into its parts, according to the rules described on:
https://fr.wikipedia.org/wiki/Num%C3%A9ro_de_s%C3%A9curit%C3%A9_sociale_en_France#Signification_des_chiffres_du_NIR
The order in which fields are returned allows to use ''.join(*t) to get
the SSN back.
"""
assert len(ssn) == 15
ssb = ssn.lower()
sex = ssn[:1]
year = ssn[1:3]
month = ssn[3:5]
location = ssn[5:10]
id_month_city = ssn[10:13]
control_key = ssn[13:]
dpt = location[:2]
if dpt.isdigit() and 1 <= int(dpt) <= 95 or dpt in ('2a', '2b'):
departement = location[:2]
city = location[2:]
country = ''
elif dpt.isdigit() and 97 <= int(dpt) <= 98:
departement = location[:3]
city = location[3:]
country = ''
elif dpt == '99':
departement = '99'
city = ''
country = location[2:]
else:
raise ValueError('Invalid location for SSN: %s' % location)
return (sex, year, month, departement, city, country, id_month_city, control_key)
class FrenchSSN(FrenchSSNTuple):
@classmethod
def fromstring(cls, ssn):
splitted = split_SSN(ssn)
return cls(*splitted)
def __str__(self):
return ''.join(self)
@property
def valid(self):
return all(
getattr(self, '_validate_%s' % field, lambda: True)()
for field in self._fields
)
def _validate_sex(self):
return len(self.sex) == 1 and self.sex in '123'
def _validate_year(self):
return len(self.year) == 2 and self.year.isdigit()
def _validate_month(self):
try:
month = int(self.month)
except ValueError:
return False
return (
( 1 <= month <= 12) or
(20 <= month <= 42) or
(50 <= month <= 99)
)
def _validate_departement(self):
try:
dpt = int(self.departement)
except ValueError:
return dpt in ('2a', '2b') # Corsica
return (
(1 <= dpt <= 95) or # France (mainland)
(970 < dpt <= 989) or # France (outre-mer)
(dpt == 99) # abroad
)
def _validate_city(self):
return True # TODO
def _validate_country(self):
return True # TODO
def _validate_id_month_city(self):
try:
id_month_city = int(self.id_month_city)
except ValueError:
return False
return 1 <= id_month_city <= 999
def _validate_control_key(self):
ssn_without_key = self._replace(
departement={'2a': '19', '2b': 18}.get(self.departement, self.departement),
control_key=''
)
try:
ssn_without_key = int(''.join(ssn_without_key))
key = int(self.control_key)
except ValueError:
return False
return key == (97 - ssn_without_key % 97)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment