Spaces:
Sleeping
Sleeping
| """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), | |
| } | |