Files
clientflow_backend/app/operation_noise.py
2026-06-09 22:55:58 +01:00

114 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Noise detection helpers for the operator work queue.
These helpers are intentionally conservative. They identify messages that are
not useful ClientFlow work items because they are system delivery reports,
postmaster notifications, automatic bounces/NDRs, or similar mailbox noise.
Chatwoot/Thunderbird remain the source for reading those emails; ClientFlow
should not turn them into tasks/opportunities.
"""
from __future__ import annotations
import re
from typing import Any, Mapping
SYSTEM_SENDER_PATTERNS = [
r"\bpostmaster\b",
r"\bmailer[-_ ]daemon\b",
r"\bmail delivery subsystem\b",
r"\bmail delivery system\b",
r"\bmicrosoft exchange\b",
r"\boffice 365\b",
r"\bmicrosoft outlook\b",
]
BOUNCE_NDR_PATTERNS = [
r"\bundeliverable\b",
r"\breturned mail\b",
r"\bmail delivery failed\b",
r"\bdelivery status notification\b",
r"\bnon[- ]delivery report\b",
r"\bdelivery has failed\b",
r"\bfailure notice\b",
r"\bunknown to address\b",
r"\brecipient (?:wasn[']?t|was not) found\b",
r"\byour message (?:to .* )?couldn[']?t be delivered\b",
r"\bremote server returned\b",
r"\b550\s+5\.1\.1\b",
r"\b5\.1\.10\b",
r"\bwasn[']?t found at\b",
]
TECHNICAL_NOISE_ACTION_CODES = {
"IGNORE_BOUNCE",
"IGNORE_SPAM",
"NO_ACTION",
}
LOW_VALUE_NO_OPPORTUNITY_ACTION_CODES = {
"REMOVE_FROM_LIST",
}
def normalize_noise_text(value: Any) -> str:
"""Normalize text for deterministic noise matching."""
text = str(value or "").casefold()
text = re.sub(r"<[^>]+>", " ", text)
text = re.sub(r"https?://\S+", " ", text)
text = re.sub(r"\s+", " ", text).strip()
return text
def contains_any_pattern(text: str, patterns: list[str]) -> bool:
normalized = normalize_noise_text(text)
return any(re.search(pattern, normalized, flags=re.I) for pattern in patterns)
def is_system_sender(value: Any) -> bool:
return contains_any_pattern(str(value or ""), SYSTEM_SENDER_PATTERNS)
def is_bounce_or_ndr_text(value: Any) -> bool:
return contains_any_pattern(str(value or ""), SYSTEM_SENDER_PATTERNS + BOUNCE_NDR_PATTERNS)
def _join_item_text(item: Mapping[str, Any]) -> str:
return "\n".join(
str(item.get(key) or "")
for key in (
"customer_name",
"contact_display_name",
"fiscal_customer_name",
"message_subject",
"title",
"detail",
"request_text",
"source_system",
"action_code",
"queue",
)
)
def is_noise_operation_item(item: Mapping[str, Any]) -> bool:
"""Return True for items that should not appear in Operations."""
action_code = str(item.get("action_code") or "").strip().upper()
status = str(item.get("status") or "").strip().lower()
reason = str(item.get("no_opportunity_reason") or "").strip().lower()
if status in {"ignored", "skipped", "cancelled"}:
return True
if action_code in TECHNICAL_NOISE_ACTION_CODES:
return True
if reason in {"system_or_bounce_message", "bounce_ignored", "non_commercial_system"}:
return True
return is_bounce_or_ndr_text(_join_item_text(item))
def is_low_value_no_opportunity_item(item: Mapping[str, Any]) -> bool:
"""Return True for non-commercial work that can be hidden from the main queue."""
action_code = str(item.get("action_code") or "").strip().upper()
reason = str(item.get("no_opportunity_reason") or "").strip().lower()
if action_code in LOW_VALUE_NO_OPPORTUNITY_ACTION_CODES:
return True
return reason in {"marketing_without_commercial_intent", "non_commercial"}