GapFill Flowee

This commit is contained in:
2026-06-18 11:24:05 +02:00
parent 64442af182
commit c68daf4313
25 changed files with 2965 additions and 48 deletions
+202
View File
@@ -0,0 +1,202 @@
"""
Parser specifico per messaggi REM (Registered Electronic Mail europea).
Standard di riferimento: ETSI EN 319 532-4
"Electronic Registered Delivery Services REMDispatch and Evidence"
La REM europea e' il protocollo transfrontaliero equivalente alla PEC italiana.
Usa header X-REM-* invece degli header X-Ricevuta / X-TipoRicevuta della PEC italiana.
Header REM principali letti:
X-REM-Evidence-Type → tipo evidenza (SubmissionAcceptance, DeliveryInformation, ecc.)
X-REM-Type → alias usato da alcuni provider (stesso significato)
X-REM-Orig-Message-Id → Message-ID del messaggio originale (correlazione)
X-REM-Message-Id → alias per il Message-ID di riferimento
X-REM-Delivery-Status → success/failure per DeliveryInformation
X-REM-Timestamp → timestamp ISO 8601 (informativo)
X-REM-Provider → nome provider REM (informativo)
Strategia di mapping:
I tipi evidenza REM vengono mappati agli stessi enum DB della PEC italiana
(pec_type enum: posta_certificata, accettazione, presa_in_carico, ecc.)
senza aggiungere nuovi tipi DB. Il valore originale viene conservato in
rem_evidence_type per trasparenza.
"""
import email.message
import logging
from dataclasses import dataclass
logger = logging.getLogger(__name__)
# ─── Mapping evidence type REM → enum DB (riusa enum PEC italiana) ───────────
#
# ETSI EN 319 532-4 evidence types e loro equivalenti PEC italiani:
#
# REMDispatch → posta_certificata (il messaggio inviato)
# SubmissionAcceptance → accettazione (accettato dal provider mittente)
# RelayAcceptance → presa_in_carico (accettato per inoltro al provider destinatario)
# DeliveryInformation → avvenuta_consegna (consegnato, se X-REM-Delivery-Status = success)
# → errore_consegna (se X-REM-Delivery-Status = failed/error)
# DeliveryNonAcceptance → non_accettazione (consegna rifiutata dal destinatario)
# DeliveryExpiry → mancata_consegna (scadenza senza consegna)
# RetrievalNonAcceptance → mancata_consegna (non recuperato dal destinatario)
# ReceivedByNonREM → presa_in_carico (consegnato a sistema non-REM)
# SubmissionRejection → non_accettazione (rifiutato dal provider mittente)
# RelayRejection → non_accettazione (rifiutato durante relay)
_REM_TYPE_MAP: dict[str, str] = {
# ── Nomi standard ETSI (PascalCase) ──────────────────────────────────────
"remdispatch": "posta_certificata",
"submissionacceptance": "accettazione",
"relayacceptance": "presa_in_carico",
"deliveryinformation": "avvenuta_consegna", # rifinito con X-REM-Delivery-Status
"deliverynonacceptance": "non_accettazione",
"deliveryexpiry": "mancata_consegna",
"retrievalnonacceptance": "mancata_consegna",
"receivedbynonrem": "presa_in_carico",
"submissionrejection": "non_accettazione",
"relayrejection": "non_accettazione",
# ── Varianti con trattini (usate da alcuni provider europei) ─────────────
"rem-dispatch": "posta_certificata",
"submission-acceptance": "accettazione",
"relay-acceptance": "presa_in_carico",
"delivery-information": "avvenuta_consegna",
"delivery-non-acceptance": "non_accettazione",
"delivery-expiry": "mancata_consegna",
"retrieval-non-acceptance": "mancata_consegna",
"received-by-non-rem": "presa_in_carico",
"submission-rejection": "non_accettazione",
"relay-rejection": "non_accettazione",
# ── Varianti con underscore ───────────────────────────────────────────────
"rem_dispatch": "posta_certificata",
"submission_acceptance": "accettazione",
"relay_acceptance": "presa_in_carico",
"delivery_information": "avvenuta_consegna",
"delivery_non_acceptance": "non_accettazione",
"delivery_expiry": "mancata_consegna",
"retrieval_non_acceptance": "mancata_consegna",
"received_by_non_rem": "presa_in_carico",
"submission_rejection": "non_accettazione",
"relay_rejection": "non_accettazione",
}
# Valori X-REM-Delivery-Status che indicano fallimento della consegna
# (usati per distinguere DeliveryInformation success da failure)
_DELIVERY_FAILURE_STATUSES: frozenset[str] = frozenset({
"failed", "failure", "error", "rejected", "undeliverable",
"not-delivered", "not_delivered", "ko",
})
@dataclass
class RemClassification:
"""Risultato della classificazione di un messaggio REM."""
pec_type: str # valore enum DB (riusa enum PEC italiana)
rem_evidence_type: str | None # valore raw dell'header X-REM-Evidence-Type
riferimento_message_id: str | None # X-REM-Orig-Message-Id (correlazione outbound)
is_receipt: bool # True se e' un'evidenza (non il messaggio originale)
def classify_rem_message(msg: email.message.Message) -> RemClassification:
"""
Classifica il tipo di messaggio REM analizzando gli header X-REM-*.
Header letti (in ordine di priorita'):
1. X-REM-Evidence-Type (tipo evidenza principale, ETSI EN 319 532-4)
2. X-REM-Type (alias usato da alcuni provider)
3. X-REM-Orig-Message-Id (correlazione con il messaggio originale)
4. X-REM-Message-Id (alias per il Message-ID di riferimento)
5. X-REM-Delivery-Status (per DeliveryInformation: distingue success/failure)
Se X-REM-Evidence-Type non e' presente, il messaggio viene classificato
come 'posta_certificata' (REMDispatch: il messaggio originale).
Returns:
RemClassification con pec_type mappato all'enum DB esistente.
"""
# Header principali
raw_evidence = _clean(
msg.get("X-REM-Evidence-Type") or msg.get("X-REM-Type")
)
x_ref = _clean(
msg.get("X-REM-Orig-Message-Id") or msg.get("X-REM-Message-Id")
)
delivery_status = _clean(msg.get("X-REM-Delivery-Status"))
# Normalizza la chiave per il lookup nella mappa
evidence_key = (raw_evidence or "").lower().strip()
pec_type = _REM_TYPE_MAP.get(evidence_key, "posta_certificata")
# Gestione speciale DeliveryInformation:
# Se X-REM-Delivery-Status indica un fallimento, ri-mappa su errore_consegna
if pec_type == "avvenuta_consegna" and delivery_status:
if delivery_status.lower().strip() in _DELIVERY_FAILURE_STATUSES:
pec_type = "errore_consegna"
logger.debug(
f"REM DeliveryInformation con status={delivery_status!r}: "
f"rimappato a errore_consegna"
)
is_receipt = pec_type != "posta_certificata"
logger.debug(
f"REM classify: evidence={raw_evidence!r} → pec_type={pec_type!r} "
f"is_receipt={is_receipt} ref={x_ref!r}"
)
return RemClassification(
pec_type=pec_type,
rem_evidence_type=raw_evidence,
riferimento_message_id=x_ref,
is_receipt=is_receipt,
)
def detect_rem_headers(msg: email.message.Message) -> bool:
"""
Verifica se un messaggio contiene header REM (X-REM-*).
Usato da detect_protocol() in pec_parser.py per determinare automaticamente
se un messaggio e' REM europea o PEC italiana durante la sincronizzazione.
Returns:
True se almeno un header X-REM-* e' presente, False altrimenti.
"""
for header_name in msg.keys():
if header_name.upper().startswith("X-REM-"):
return True
return False
# ─── Verifica file di sistema REM ─────────────────────────────────────────────
# Nomi file usati dall'infrastruttura REM (non allegati utente)
# Analoghi a daticert.xml / postacert.eml della PEC italiana
REM_SYSTEM_FILENAMES: frozenset[str] = frozenset({
"remevidence.xml",
"rem-evidence.xml",
"rem_evidence.xml",
"remreceipt.xml",
"rem-receipt.xml",
"remdispatch.xml",
"rem-dispatch.xml",
"remdelivery.xml",
"rem-delivery.xml",
"remdispatch.eml",
"rem-dispatch.eml",
"remsignature.p7s",
"rem-signature.p7s",
"signed-rem-dispatch.p7m",
})
def _clean(value: str | None) -> str | None:
"""Pulisce e normalizza il valore di un header REM."""
if not value:
return None
return value.strip()