hc: auto-reply for 'already revalidated' replies + permanent suppression
A lead replied with proof their Medicare revalidation was already approved (CMS
data-lag: the public Revalidation Due Date List still showed them overdue weeks
after approval). Two of these arrived same-day, so:
- Carbonio auto-reply (deployed on co.carrierone.com): created mailbox
hc-replies@ on the info@ distribution list with a Sieve that auto-acknowledges
'my revalidation is already complete' replies (tag + mark read + file into a
'Reval Completed (auto-acked)' folder + on-brand reply explaining the CMS lag).
CRITICAL: info@ is the shared reply-to for ALL campaigns (healthcare, trucking,
telecom), so every rule is anchored to Medicare/revalidation context -- a
trucking 'MCS-150 done, this is bogus' or telecom 'RMD done' reply does NOT
trigger it (tested + passing). A buyer guard ('please file / how much') also
suppresses the auto-reply so a human handles the sale.
Carbonio 25.x Sieve quirks documented (vacation/imap4flags/body :text all
unsupported; use reply/flag/tag/body :contains).
- Permanent suppression: new data/hc_suppress.txt do-not-contact list the warmup
honors at import AND --prune removes from the live lists. Seeded with the two
completed providers (Pangea Lab, Yakima Valley FWC); both also blocklisted in
listmonk_hc and removed from lists 3 + 4.
This commit is contained in:
parent
9cb10b18e0
commit
a78d60a127
3 changed files with 155 additions and 0 deletions
|
|
@ -49,6 +49,10 @@ def _token() -> str:
|
|||
VERIFIED_CSV = os.getenv("HC_VERIFIED_CSV", "/opt/performancewest/data/hc_warmup_week1_verified.csv")
|
||||
STATE_DIR = os.getenv("HC_STATE_DIR", "/opt/performancewest/data")
|
||||
WARMUP_STAMP = "/etc/postfix/hc-warmup-start"
|
||||
# Permanent do-not-contact list (one email per line). Providers who tell us they
|
||||
# already revalidated, asked to stop, etc. go here so the warmup NEVER imports or
|
||||
# re-mails them, regardless of segment or stale CMS data. Append + run --prune.
|
||||
SUPPRESS_FILE = os.getenv("HC_SUPPRESS_FILE", os.path.join(STATE_DIR, "hc_suppress.txt"))
|
||||
|
||||
FROM_EMAIL = "Performance West Compliance <compliance@performancewest.net>"
|
||||
REPLY_TO = "info@performancewest.net"
|
||||
|
|
@ -58,6 +62,12 @@ REPLY_TO = "info@performancewest.net"
|
|||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from build_healthcare_campaigns import SEGMENTS, template_path # noqa: E402
|
||||
|
||||
|
||||
def load_suppressed() -> set[str]:
|
||||
if os.path.exists(SUPPRESS_FILE):
|
||||
return {ln.strip().lower() for ln in open(SUPPRESS_FILE) if ln.strip() and not ln.startswith("#")}
|
||||
return set()
|
||||
|
||||
# Which segments to warm, in priority order. Revalidation stays the lead (it has
|
||||
# the most verified data + the official-record card). The others warm in
|
||||
# parallel on smaller slices so we collect engagement data across all programs
|
||||
|
|
@ -276,9 +286,11 @@ def warm_segment(seg_key: str, rows: list[dict], slice_n: int,
|
|||
campaign active. Returns how many NEW subscribers were imported."""
|
||||
seg = SEGMENTS[seg_key]
|
||||
imported = load_imported(seg_key)
|
||||
suppressed = load_suppressed()
|
||||
candidates = [r for r in rows
|
||||
if r.get("email", "").strip()
|
||||
and r["email"].strip().lower() not in imported
|
||||
and r["email"].strip().lower() not in suppressed
|
||||
and not _is_google_hosted(r)
|
||||
and row_matches(seg_key, r)]
|
||||
todo = candidates[:slice_n]
|
||||
|
|
@ -339,6 +351,7 @@ def prune_holdouts(dry_run: bool) -> int:
|
|||
return 0
|
||||
rows = list(csv.DictReader(open(master_path)))
|
||||
by_email = {r.get("email", "").strip().lower(): r for r in rows if r.get("email")}
|
||||
suppressed = load_suppressed()
|
||||
removed = 0
|
||||
for seg_key, seg in SEGMENTS.items():
|
||||
try:
|
||||
|
|
@ -351,6 +364,11 @@ def prune_holdouts(dry_run: bool) -> int:
|
|||
continue
|
||||
drop_ids = []
|
||||
for sid, email in _all_list_subscribers(list_id):
|
||||
# Always remove anyone on the permanent do-not-contact list (they
|
||||
# told us to stop / already revalidated), regardless of source data.
|
||||
if email in suppressed:
|
||||
drop_ids.append(sid)
|
||||
continue
|
||||
r = by_email.get(email)
|
||||
if r is None:
|
||||
continue # not in our source data; leave it alone
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue