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:
justin 2026-06-08 10:37:49 -05:00
parent 9cb10b18e0
commit a78d60a127
3 changed files with 155 additions and 0 deletions

View file

@ -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