Spaces:
Sleeping
Sleeping
Alessandro Tomassini
deploy(hf): overlay README/Dockerfile da huggingface/, senza docs/binari/model
c42a5a1 | """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, | |
| } | |