AiAnonymize / recognizers.py
TomassiniDigital's picture
Update recognizers.py
729da20 verified
"""Presidio analyzers + PatternRecognizer regex per gare d'appalto italiane."""
import re as _re
from presidio_analyzer import AnalyzerEngine, Pattern, PatternRecognizer
from presidio_analyzer.nlp_engine import NlpEngineProvider
from utils import ensure_spacy_model
# ---------------------------------------------------------------------------
# Spacy model (solo per tokenizzazione; NER è sul transformer)
# ---------------------------------------------------------------------------
ensure_spacy_model("it_core_news_lg")
_nlp_config = {
"nlp_engine_name": "transformers",
"models": [{
"lang_code": "it",
"model_name": {
"spacy": "it_core_news_lg",
"transformers": "DeepMount00/Italian_NER_XXL_v2",
},
}],
"ner_model_configuration": {
"model_to_presidio_entity_mapping": {
# Anagrafica
"NOME": "PERSON",
"COGNOME": "PERSON",
"DATA_NASCITA": "DATE_TIME",
"DATA_MORTE": "DATE_TIME",
"ETA": "ETA",
"PROFESSIONE": "PROFESSIONE",
"STATO_CIVILE": "STATO_CIVILE",
"CODICE_FISCALE": "CODICE_FISCALE",
# Contatti
"INDIRIZZO": "INDIRIZZO",
"NUMERO_TELEFONO": "PHONE_NUMBER",
"EMAIL": "EMAIL_ADDRESS",
"CODICE_POSTALE": "CAP",
# Finanziari
"VALUTA": "VALUTA",
"IMPORTO": "IMPORTO_GARA",
"NUMERO_CARTA": "CREDIT_CARD",
"CVV": "CVV",
"NUMERO_CONTO": "NUMERO_CONTO",
"IBAN": "IBAN_CODE",
"BIC": "BIC_SWIFT",
"P_IVA": "PARTITA_IVA",
"BANCA": "BANCA",
"TASSO_MUTUO": "TASSO_MUTUO",
"NUM_ASSEGNO_BANCARIO": "NUM_ASSEGNO",
# Legali
"RAGIONE_SOCIALE": "ORGANIZATION",
"TRIBUNALE": "TRIBUNALE",
"LEGGE": "LEGGE",
"N_SENTENZA": "N_SENTENZA",
"N_LICENZA": "N_LICENZA",
"AVV_NOTAIO": "AVV_NOTAIO",
"REGIME_PATRIMONIALE": "REGIME_PATRIMONIALE",
# Medico
"CARTELLA_CLINICA": "CARTELLA_CLINICA",
"MALATTIA": "MALATTIA",
"MEDICINA": "MEDICINA",
"STORIA_CLINICA": "STORIA_CLINICA",
"STRENGTH": "STRENGTH",
"FREQUENZA": "FREQUENZA",
"DURATION": "DURATION",
"DOSAGGIO": "DOSAGGIO",
"FORM": "FORM",
# Tecnico
"IP": "IP_ADDRESS",
"IPV6_1": "IP_ADDRESS",
"MAC": "MAC",
"USER_AGENT": "USER_AGENT",
"IMEI": "IMEI",
# Geo / temporale
"STATO": "LOCATION",
"LUOGO": "LOCATION",
"DATA": "DATE_TIME",
"ORARIO": "DATE_TIME",
# Documenti / veicoli / catasto
"NUMERO_DOCUMENTO": "NUMERO_DOCUMENTO",
"TARGA_VEICOLO": "TARGA_VEICOLO",
"FOGLIO": "FOGLIO_CATASTO",
"PARTICELLA": "PARTICELLA_CATASTO",
"MAPPALE": "MAPPALE_CATASTO",
"SUBALTERNO": "SUBALTERNO_CATASTO",
# Web / sicurezza
"URL": "URL",
"PASSWORD": "PASSWORD",
"PIN": "PIN",
"BRAND": "ORGANIZATION",
},
"low_confidence_score_multiplier": 0.4,
"low_score_entity_names": [],
},
}
_nlp_engine = NlpEngineProvider(nlp_configuration=_nlp_config).create_engine()
analyzer_ner_only = AnalyzerEngine(nlp_engine=_nlp_engine, supported_languages=["it"])
analyzer_full = AnalyzerEngine(nlp_engine=_nlp_engine, supported_languages=["it"])
# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------
def _r(entity: str, name: str, regex: str, score: float, context: list[str]) -> PatternRecognizer:
return PatternRecognizer(
supported_entity=entity,
supported_language="it",
patterns=[Pattern(name, regex, score)],
context=context,
)
# ---------------------------------------------------------------------------
# PatternRecognizer – gare d'appalto
# ---------------------------------------------------------------------------
_RECOGNIZERS: list[PatternRecognizer] = [
# CIG – Ordinario (\d{7}[0-9A-F]{3}) · Unico/Smart ([A-Z][0-9A-F]{9})
_r("CIG", "cig",
r"\b(?:\d{7}[0-9A-F]{3}|[A-Z][0-9A-F]{9})\b", 0.70,
["CIG", "cig", "Cig", "CIG:", "cig:", "CIG :", "cig :",
"codice CIG", "codice cig", "Codice CIG",
"codice identificativo gara", "Codice Identificativo Gara",
"codice identificativo della gara", "identificativo di gara",
"id gara", "ID gara", "ID Gara",
"smartcig", "SmartCIG", "smart CIG", "Smart CIG",
"lotto CIG", "lotto cig"]),
# CUP – struttura CIPE ufficiale (15 char)
_r("CUP", "cup",
r"\b[A-Z]\d{2}[A-Z][A-Z0-9]{2}\d{6}[A-Z0-9]{3}\b", 0.90,
["CUP", "cup", "Cup", "CUP:", "cup:", "CUP :", "cup :",
"codice CUP", "Codice CUP",
"codice unico di progetto", "Codice Unico di Progetto",
"codice unico progetto", "codice progetto",
"monitoraggio investimenti pubblici",
"investimento pubblico", "progetto pubblico"]),
# REA – sigla provincia + separatore + 4-7 cifre
_r("REA", "rea",
r"\b[A-Z]{2}[\s\-/\.]\d{4,7}\b", 0.85,
["REA", "rea", "Rea", "REA:", "rea:", "REA :", "REA n.",
"n. REA", "n. rea", "n.REA",
"registro imprese", "Registro Imprese", "registro delle imprese",
"registro economico amministrativo",
"CCIAA", "cciaa", "camera di commercio", "Camera di Commercio"]),
# Numero Gara ANAC – score basso (richiede context boost)
_r("NUMERO_GARA_ANAC", "num_gara",
r"\b\d{7,8}\b", 0.60,
["ANAC","ANAC:","ANAC :","anac:" "anac", "numero gara", "Numero Gara",
"n. gara", "N. gara", "N.Gara",
"gara ANAC", "Gara ANAC:" "gara n.", "gara n°",
"SIMOG", "simog", "identificativo ANAC", "codice ANAC"]),
# PEC – domini certificati italiani
_r("PEC", "pec",
r"\b[\w.\-]+@[\w.\-]*(?:pec|postacert|legalmail|pecmail|"
r"sicurezzapostale|registerpec|ticertifica)[\w.\-]*\.\w+\b", 0.90,
["PEC", "pec", "Pec", "PEC:", "pec:", "indirizzo PEC",
"posta certificata", "Posta Certificata",
"posta elettronica certificata", "p.e.c.", "P.E.C."]),
# Matricola INPS – score basso (richiede context boost)
_r("MATRICOLA_INPS", "inps",
r"(?<!\d)\d{10}(?!\d)", 0.60,
["INPS", "inps", "matricola INPS", "matricola inps", "Matricola INPS",
"matricola:", "matricola n.", "n. matricola",
"previdenziale", "iscrizione INPS",
"posizione INPS", "posizione previdenziale"]),
# Polizza – formato lungo (GEN-2024-IT-NNNNNN)
_r("POLIZZA_ASSICURATIVA", "polizza_full",
r"\b[A-Z]{2,5}-\d{4}-[A-Z]{2}-\d{6,12}\b", 0.90,
["polizza", "Polizza", "polizza:", "Polizza n.", "n. polizza", "N. Polizza",
"numero polizza", "Numero Polizza",
"assicurativa", "assicurazione",
"contratto assicurativo", "polizza fideiussoria", "fideiussione"]),
# Polizza – formato generico (score basso, richiede context)
_r("POLIZZA_ASSICURATIVA", "polizza_alt",
r"\b[A-Z0-9]{2,5}[\-/\.][A-Z0-9]{4,15}(?:[\-/\.][A-Z0-9]{2,10}){0,2}\b", 0.45,
["polizza", "Polizza", "n. polizza", "numero polizza",
"assicurativa", "fideiussoria", "fideiussione",
"contratto assicurativo"]),
# CPV – Common Procurement Vocabulary
_r("CPV", "cpv",
r"\b\d{8}-\d\b", 0.92,
["CPV", "cpv", "Cpv", "CPV:", "cpv:", "codice CPV", "Codice CPV",
"vocabolario comune appalti", "Vocabolario Comune degli Appalti",
"categoria merceologica", "oggetto della fornitura",
"classificazione CPV"]),
# NUTS – area territoriale
_r("NUTS", "nuts",
r"\bIT[A-Z0-9]{1,4}\b", 0.80,
["NUTS", "nuts", "NUTS:", "nuts:", "codice NUTS", "Codice NUTS",
"NUTS2", "NUTS3", "nuts2", "nuts3",
"area geografica", "territorio", "area NUTS",
"localizzazione geografica"]),
# ATECO 2007
_r("ATECO", "ateco",
r"\b\d{2}\.\d{2}(?:\.\d{1,2})?\b", 0.60,
["ATECO", "ateco", "Ateco", "ATECO:", "codice ATECO", "Codice ATECO",
"attività economica", "Attività Economica",
"classificazione ATECO", "codice attività",
"settore ATECO", "categoria ATECO"]),
# Percentuale di ribasso
_r("PERCENTUALE_RIBASSO", "ribasso",
r"\b\d{1,2}(?:,\d{1,4})?\s*%", 0.55,
["ribasso", "Ribasso", "al ribasso",
"ribasso d'asta", "ribasso offerto",
"rialzo", "percentuale di ribasso",
"offerta economica", "sconto", "aggiudicazione",
"percentuale offerta"]),
# Numero protocollo PA
_r("NUMERO_PROTOCOLLO", "protocollo",
r"(?:prot(?:ocollo)?\.?\s*(?:n\.?)?\s*)\d{4,8}(?:[/\-]\d{2,4})?", 0.85,
["protocollo", "Protocollo", "prot.", "Prot.", "PROT.",
"n. prot", "N. Prot", "N.Prot",
"numero protocollo", "Numero Protocollo",
"repertorio", "fascicolo", "prot. n.", "Prot. n."]),
# Atto amministrativo (determinazione / delibera / decreto)
_r("ATTO_AMMINISTRATIVO", "atto",
r"(?:det(?:erminazione)?\.?|delib(?:era)?\.?|decreto|d\.g\.?|d\.d\.?)"
r"\s*(?:n\.?)?\s*\d{1,6}(?:[/\-]\d{2,4})?", 0.78,
["determinazione", "Determinazione", "det.",
"delibera", "Delibera", "delib.",
"decreto", "Decreto",
"d.g.", "D.G.", "d.d.", "D.D.",
"atto", "Atto", "provvedimento", "Provvedimento",
"disposizione", "ordinanza", "Ordinanza"]),
# Codice ANAC operatore economico
_r("CODICE_ANAC", "anac",
r"\bIT-\d{11}\b", 0.92,
["codice ANAC", "Codice ANAC", "codice anac",
"BDNCP", "bdncp", "banca dati ANAC",
"operatore economico", "Operatore Economico",
"attestazione ANAC", "white list", "White List",
"iscrizione ANAC"]),
# Numero lotto gara
_r("LOTTO_GARA", "lotto",
r"\b(?:lotto|lot\.?)\s*(?:n\.?)?\s*\d{1,3}[a-z]?\b", 0.75,
["lotto", "Lotto", "LOTTO",
"lotto n.", "Lotto n.", "lotto n°",
"lotto:", "Lotto:", "sub-lotto",
"lotti", "Lotti", "suddivisione in lotti",
"aggiudicazione per lotti"]),
# SIOGG
_r("SIOGG", "siogg",
r"\bSOGG-?[A-Z0-9]{8,12}\b", 0.88,
["SIOGG", "siogg", "osservatorio", "Osservatorio",
"contratti pubblici", "Contratti Pubblici",
"osservatorio contratti"]),
# CF aziendale – 11 cifre con context "C.F." (disambigua da P.IVA)
# Score 0.75 > PARTITA_IVA (0.70) → vince in presenza di contesto C.F.
_r("CODICE_FISCALE", "cf_azienda",
r"\b\d{11}\b", 0.75,
["C.F.", "c.f.", "CF", "cf", "C.F.:", "c.f.:", "CF:",
"codice fiscale", "Codice Fiscale",
"codice fiscale azienda", "codice fiscale impresa",
"codice fiscale società"]),
# Forme societarie – nome (1-4 parole capitalizzate) + sigla giuridica
_r("SOCIETA", "societa",
r"\b(?:[A-Z][\w&\-']*\s+){1,4}"
r"(?:"
r"S\.?\s*r\.?\s*l\.?(?:\s*[Ss]\.?)?|" # S.r.l., Srl, S.r.l.s.
r"S\.?\s*p\.?\s*[Aa]\.?|" # S.p.A., SpA
r"S\.?\s*a\.?\s*p\.?\s*[Aa]\.?|" # S.a.p.A.
r"S\.?\s*n\.?\s*c\.?|" # S.n.c., Snc
r"S\.?\s*a\.?\s*s\.?|" # S.a.s., Sas
r"S\.?\s*s\.?|" # S.s.
r"S\.?\s*c\.?\s*a\.?\s*r\.?\s*l\.?|" # S.c.a.r.l.
r"Soc(?:\.|ietà)?\s+[Cc]oop(?:\.|erativa)?(?:\s+a\s+r\.?\s*l\.?)?|"
r"Cooperativa(?:\s+Sociale)?|"
r"Onlus|ONLUS|"
r"E\.?\s*T\.?\s*S\.?|" # ETS
r"A\.?\s*P\.?\s*S\.?|" # APS
r"O\.?\s*N\.?\s*G\.?|" # ONG
r"Lda\.?|Ltd\.?|GmbH|S\.?\s*A\.?|Inc\.?" # estere comuni
r")\b\.?", 0.85,
["società", "Società", "SOCIETÀ",
"impresa", "Impresa", "ditta", "Ditta", "azienda", "Azienda",
"ragione sociale", "Ragione Sociale",
"denominazione", "Denominazione",
"operatore economico", "Operatore Economico",
"subappaltatore", "appaltatore", "aggiudicatario",
"banca", "Banca", "istituto", "compagnia", "Compagnia",
"fornitore", "Fornitore", "concessionario"]),
# Iscrizione Albo professionale
_r("ISCRIZIONE_ALBO", "albo",
r"(?:[Aa]lbo\s+(?:degli?\s+)?\w+(?:\s+(?:di|della|del)\s+\w+)?"
r"(?:\s+(?:di|della|del)\s+\w+)?)\s*(?:al|n\.?)?\s*\d+(?:/[A-Z])?", 0.80,
["albo", "Albo", "iscrizione", "Iscrizione",
"iscritto", "iscritta",
"albo professionale", "Albo Professionale",
"ordine professionale"]),
]
for _rec in _RECOGNIZERS:
analyzer_full.registry.add_recognizer(_rec)
# ---------------------------------------------------------------------------
# POST_BOOST_PATTERNS – secondo passaggio regex (score +0.30 se fullmatch)
# ---------------------------------------------------------------------------
POST_BOOST_PATTERNS: dict[str, _re.Pattern] = {
"NUMERIC": _re.compile(r"^\d{4,7}$"),
"CIG": _re.compile(r"^(?:\d{7}[0-9A-F]{3}|[A-Z][0-9A-F]{9})$"),
"CUP": _re.compile(r"^[A-Z]\d{2}[A-Z][A-Z0-9]{2}\d{6}[A-Z0-9]{3}$"),
"REA": _re.compile(r"^[A-Z]{2}[\s\-/\.]\d{4,7}$"),
"CPV": _re.compile(r"^\d{8}-\d$"),
"NUTS": _re.compile(r"^IT[A-Z0-9]{1,4}$"),
"ATECO": _re.compile(r"^\d{2}\.\d{2}(?:\.\d{1,2})?$"),
"CODICE_ANAC": _re.compile(r"^IT-\d{11}$"),
"SIOGG": _re.compile(r"^SOGG-?[A-Z0-9]{8,12}$"),
"LOTTO_GARA": _re.compile(r"^(?:lotto|lot\.?)\s*(?:n\.?)?\s*\d{1,3}[a-z]?$", _re.IGNORECASE),
"POLIZZA_ASSICURATIVA": _re.compile(r"^[A-Z]{2,5}-\d{4}-[A-Z]{2}-\d{6,12}$"),
"PEC": _re.compile(
r"^[\w.\-]+@[\w.\-]*(?:pec|postacert|legalmail|pecmail|"
r"sicurezzapostale|registerpec|ticertifica)[\w.\-]*\.\w+$", _re.IGNORECASE),
"CODICE_FISCALE": _re.compile(r"^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$", _re.IGNORECASE),
"PARTITA_IVA": _re.compile(r"^(IT\s*)?\d{11}$", _re.IGNORECASE),
"IBAN_CODE": _re.compile(r"^IT\d{2}[A-Z]\d{10}[A-Z0-9]{12}$", _re.IGNORECASE),
"BIC_SWIFT": _re.compile(
r"^[A-Z]{4}(?:IT|DE|FR|ES|GB|CH|NL|BE|AT|PT|GR|LU|IE|FI|SE|DK|NO|PL)"
r"[A-Z0-9]{2}(?:[A-Z0-9]{3})?$"),
"CREDIT_CARD": _re.compile(
r"^(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))"
r"[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{3,4}$"),
"IP_ADDRESS": _re.compile(
r"^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$"),
"MAC": _re.compile(r"^(?:[0-9A-Fa-f]{2}[:\-]){5}[0-9A-Fa-f]{2}$"),
"TARGA_VEICOLO": _re.compile(
r"^(?:[A-Z]{2}\d{3}[A-Z]{2}|[A-Z]{2}\d{4}[A-Z]|"
r"(?:CC|EI|EE|RM|VF|CRI|SCC|CD)\s*\d{3,5})$", _re.IGNORECASE),
"NUMERO_PROTOCOLLO": _re.compile(
r"^prot(?:ocollo)?\.?\s*(?:n\.?)?\s*\d{4,8}(?:[/\-]\d{2,4})?$", _re.IGNORECASE),
}