From feb677f6ce4e3f26000d8057abf96e75fb72e6e5 Mon Sep 17 00:00:00 2001 From: justin Date: Mon, 8 Jun 2026 03:27:22 -0500 Subject: [PATCH] fix(hc warmup): only mail slightly-overdue providers (deliverability) Mailing heavily-overdue NPIs (months/years past due) risks hitting practices that have closed, merged, or abandoned the inbox -> hard bounces, which are the fastest way to wreck a warming IP's reputation. The warmup now restricts the reval_overdue selector to an inclusive [HC_OVERDUE_MIN, HC_OVERDUE_MAX] window (default 1-90 days) and the OIG 'any' selector likewise excludes heavily-overdue and dropped-off-list rows. On the current cohort this trims the overdue audience 178->96 and the OIG audience 399->317, holding out the stale long tail (181-365d + 366d+). upcoming/active providers are unaffected. --- infra/cron/pw-hc-campaign | 3 ++ scripts/build_healthcare_campaigns_cron.py | 36 ++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/infra/cron/pw-hc-campaign b/infra/cron/pw-hc-campaign index 7670418..4635535 100644 --- a/infra/cron/pw-hc-campaign +++ b/infra/cron/pw-hc-campaign @@ -4,4 +4,7 @@ # America/Chicago). Delivery is throttled by pw-hc-rampcap's sliding-window # cap, sent ONLY via the hc HOT stream (.107-.109). Segment audiences are # source-grounded: a segment with no matching providers simply imports nobody. +# Deliverability guard: warmup only mails SLIGHTLY-overdue providers (1-90 days +# by default, HC_OVERDUE_MIN/MAX) -- recently-lapsed practices still have live +# inboxes; heavily-overdue ones likely bounce and burn the warming IPs. 0 7 * * 1-5 deploy cd /opt/performancewest && HC_VERIFIED_CSV=/opt/performancewest/data/hc_warmup_nongoogle.csv python3 scripts/build_healthcare_campaigns_cron.py --start-campaign >> /var/log/pw-hc-campaign.log 2>&1 diff --git a/scripts/build_healthcare_campaigns_cron.py b/scripts/build_healthcare_campaigns_cron.py index 39bab9d..ee7104e 100644 --- a/scripts/build_healthcare_campaigns_cron.py +++ b/scripts/build_healthcare_campaigns_cron.py @@ -67,6 +67,22 @@ ACTIVE_SEGMENTS = os.getenv( "revalidation_overdue,oig_screening,nppes_outdated,npi_reactivation,compliance_bundle", ).split(",") +# Warmup deliverability guard: only mail SLIGHTLY-overdue providers. A practice +# that lapsed recently is almost certainly still operating with a live inbox; one +# that is many months/years overdue has likely closed, merged, or abandoned the +# address, so its mail bounces -- and bounces are the fastest way to wreck a +# warming IP's reputation. Window is inclusive [MIN, MAX] days overdue. +WARMUP_OVERDUE_MIN = int(os.getenv("HC_OVERDUE_MIN", "1")) +WARMUP_OVERDUE_MAX = int(os.getenv("HC_OVERDUE_MAX", "90")) + + +def _overdue_days(r: dict): + v = (r.get("days_overdue") or "").strip() + try: + return int(v) + except ValueError: + return None + def warmup_day() -> int: try: @@ -197,14 +213,30 @@ def row_matches(seg_key: str, r: dict) -> bool: status = (r.get("reval_status") or "").strip().lower() excluded = (r.get("leie_excluded") or "").strip() not in ("", "0", "false") optout = (r.get("optout_ending") or "").strip() != "" - if sel == "reval_overdue": return status == "overdue" + if sel == "reval_overdue": + # Only SLIGHTLY-overdue: recently-lapsed practices are still active with + # deliverable inboxes. Heavily-overdue ones likely bounce and burn the + # warming IP's reputation, so we hold them out of the warmup window. + if status != "overdue": + return False + od = _overdue_days(r) + return od is not None and WARMUP_OVERDUE_MIN <= od <= WARMUP_OVERDUE_MAX if sel == "reval_upcoming": return status == "upcoming" if sel == "leie_or_deactivated": # Reactivation targets: flagged excluded, OR no longer on the reval list # (a strong deactivation proxy once revalidation lapses). return excluded or status in ("not_on_list", "no_reval_flag") if sel == "optout_ending": return optout - if sel == "any": return True + if sel == "any": + # OIG screening applies to any billing practice, but for warmup we still + # exclude the likely-undeliverable: providers heavily overdue (stale) or + # already dropped off the reval list. Recently-lapsed and upcoming stay. + if status in ("not_on_list", "no_reval_flag"): + return False + od = _overdue_days(r) + if status == "overdue" and (od is None or od > WARMUP_OVERDUE_MAX): + return False + return True return False