"""Validatori di formato/checksum per ridurre i falsi positivi. Ogni validatore riceve la stringa gia' matchata e ritorna True se rispetta lo standard. Sono funzioni pure, senza stato. """ from __future__ import annotations import re _CF_ODD = { "0": 1, "1": 0, "2": 5, "3": 7, "4": 9, "5": 13, "6": 15, "7": 17, "8": 19, "9": 21, "A": 1, "B": 0, "C": 5, "D": 7, "E": 9, "F": 13, "G": 15, "H": 17, "I": 19, "J": 21, "K": 2, "L": 4, "M": 18, "N": 20, "O": 11, "P": 3, "Q": 6, "R": 8, "S": 12, "T": 14, "U": 16, "V": 10, "W": 22, "X": 25, "Y": 24, "Z": 23, } _CF_EVEN = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7, "I": 8, "J": 9, "K": 10, "L": 11, "M": 12, "N": 13, "O": 14, "P": 15, "Q": 16, "R": 17, "S": 18, "T": 19, "U": 20, "V": 21, "W": 22, "X": 23, "Y": 24, "Z": 25, } _CF_REMAINDER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # Codici paese ISO 3166-1 alpha-2: nel BIC/SWIFT occupano i caratteri 5-6 e # sono il discriminante piu' forte contro i falsi positivi (parole maiuscole # di 8 lettere come "SPECIALE" hanno "IA" in quella posizione, inesistente). _ISO_3166_ALPHA2 = frozenset( "AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI " "BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN " "CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK " "FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM " "HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN " "KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK " "ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP " "NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW " "SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF " "TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI " "VN VU WF WS YE YT ZA ZM ZW".split() ) def validate_codice_fiscale(value: str) -> bool: """Valida il codice fiscale persona fisica (16 caratteri, char di controllo).""" cf = value.strip().upper() if not re.fullmatch(r"[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]", cf): return False total = 0 for i, ch in enumerate(cf[:15]): total += _CF_ODD[ch] if (i % 2 == 0) else _CF_EVEN[ch] return _CF_REMAINDER[total % 26] == cf[15] def _luhn_check(digits: str) -> bool: total = 0 reverse = digits[::-1] for i, ch in enumerate(reverse): d = int(ch) if i % 2 == 1: d *= 2 if d > 9: d -= 9 total += d return total % 10 == 0 def validate_partita_iva(value: str) -> bool: """Valida la partita IVA italiana (11 cifre, checksum di Luhn pari).""" piva = re.sub(r"\D", "", value) if len(piva) != 11: return False return _luhn_check(piva) def validate_carta_credito(value: str) -> bool: """Valida una carta di credito tramite Luhn (13-19 cifre).""" digits = re.sub(r"\D", "", value) if not 13 <= len(digits) <= 19: return False return _luhn_check(digits) def validate_iban(value: str) -> bool: """Valida IBAN tramite resto mod-97 (ISO 13616).""" iban = re.sub(r"\s", "", value).upper() if not re.fullmatch(r"[A-Z]{2}\d{2}[A-Z0-9]{11,30}", iban): return False rearranged = iban[4:] + iban[:4] converted = "".join( str(ord(ch) - 55) if ch.isalpha() else ch for ch in rearranged ) try: return int(converted) % 97 == 1 except ValueError: return False def validate_cig(value: str) -> bool: """CIG: 10 caratteri alfanumerici (validazione di formato).""" return bool(re.fullmatch(r"[0-9A-Za-z]{10}", value.strip())) def validate_cup(value: str) -> bool: """CUP: 15 caratteri alfanumerici (validazione di formato).""" return bool(re.fullmatch(r"[0-9A-Za-z]{15}", value.strip())) def validate_cap(value: str) -> bool: return bool(re.fullmatch(r"\d{5}", value.strip())) def validate_bic(value: str) -> bool: """Valida la struttura di un BIC/SWIFT (ISO 9362). Formato: 4 lettere (banca) + 2 lettere (paese ISO 3166-1) + 2 alfanumerici (localita') + 3 alfanumerici opzionali (filiale). Il vincolo del codice paese ISO scarta i falsi positivi su parole maiuscole di 8 lettere (es. "SPECIALE", "ORDINARI"), il cui codice paese non esiste. """ v = value.strip().upper() if len(v) not in (8, 11): return False if not v[:6].isalpha(): return False if v[4:6] not in _ISO_3166_ALPHA2: return False return v[6:].isalnum() def always_valid(value: str) -> bool: # noqa: ARG001 return True def validate_codice_fiscale_any(value: str) -> bool: """CF persona (16 char, checksum) OPPURE CF azienda (11 cifre = P.IVA, Luhn).""" v = value.strip() if re.fullmatch(r"\d{11}", v): return validate_partita_iva(v) return validate_codice_fiscale(v) # Validatori applicati come FILTRO sui rilevamenti finali (qualunque layer li # abbia prodotti, incluso il NER che non controlla i checksum): uno span di # questi tipi con checksum/formato non valido viene scartato. Riduce i falsi # positivi sui codici a formato verificabile. FORMAT_VALIDATORS: dict = { "CODICE_FISCALE": validate_codice_fiscale_any, "PARTITA_IVA": validate_partita_iva, "IBAN": validate_iban, "CARTA_CREDITO": validate_carta_credito, "BIC": validate_bic, }