hc: add 'revalidation due soon' warmup segment (proactive, grows supply)
The HC warmup pool is supply-constrained (~400 verified providers, all fed by the same narrow 'revalidation 1-90 days OVERDUE' slice). This adds a mirror-image proactive segment that targets providers whose Medicare revalidation is UPCOMING within the next 1-90 days, drawn from the same CMS Revalidation Due Date List -- no new data source needed. 'Handle it before your deadline' is a strong pitch and roughly doubles the deliverable pool. - New selector reval_due_soon (status=upcoming, days_until in [HC_DUE_SOON_MIN, HC_DUE_SOON_MAX] default 1-90). - New segment revalidation_due_soon reusing the existing /order/npi-revalidation service ($599) with template hc_revalidation_due_soon.html. - attribs_for now exposes days_until (positive days to due date). - Added to ACTIVE_SEGMENTS.
This commit is contained in:
parent
773c443079
commit
c8c9a04c1d
3 changed files with 149 additions and 3 deletions
|
|
@ -59,6 +59,15 @@ SEGMENTS = {
|
|||
"campaign_name": "HC Warmup - Medicare Revalidation",
|
||||
"selector": "reval_overdue",
|
||||
},
|
||||
"revalidation_due_soon": {
|
||||
"subject": "Your Medicare revalidation deadline is approaching",
|
||||
"template": "hc_revalidation_due_soon.html",
|
||||
"cta_path": "/order/npi-revalidation",
|
||||
"price": "$599",
|
||||
"list_name": "HC Warmup - Revalidation Due Soon",
|
||||
"campaign_name": "HC Warmup - Revalidation Due Soon",
|
||||
"selector": "reval_due_soon",
|
||||
},
|
||||
"npi_reactivation": {
|
||||
"subject": "Your NPI / Medicare enrollment appears deactivated",
|
||||
"template": "hc_npi_reactivation.html",
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ def load_suppressed() -> set[str]:
|
|||
# without overwhelming the warming IPs.
|
||||
ACTIVE_SEGMENTS = os.getenv(
|
||||
"HC_SEGMENTS",
|
||||
"revalidation_overdue,oig_screening,nppes_outdated,npi_reactivation,compliance_bundle",
|
||||
"revalidation_overdue,revalidation_due_soon,oig_screening,nppes_outdated,npi_reactivation,compliance_bundle",
|
||||
).split(",")
|
||||
|
||||
# Warmup deliverability guard: only mail SLIGHTLY-overdue providers. A practice
|
||||
|
|
@ -84,6 +84,11 @@ ACTIVE_SEGMENTS = os.getenv(
|
|||
# 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"))
|
||||
# Proactive "revalidation due soon" window (days UNTIL the due date). Mirrors the
|
||||
# overdue window so we reach providers shortly before AND after their deadline,
|
||||
# roughly doubling the deliverable warmup pool from the same CMS data source.
|
||||
WARMUP_DUE_SOON_MIN = int(os.getenv("HC_DUE_SOON_MIN", "1"))
|
||||
WARMUP_DUE_SOON_MAX = int(os.getenv("HC_DUE_SOON_MAX", "90"))
|
||||
|
||||
|
||||
def _overdue_days(r: dict):
|
||||
|
|
@ -245,6 +250,20 @@ def row_matches(seg_key: str, r: dict) -> bool:
|
|||
return False
|
||||
od = _overdue_days(r)
|
||||
return od is not None and WARMUP_OVERDUE_MIN <= od <= WARMUP_OVERDUE_MAX
|
||||
if sel == "reval_due_soon":
|
||||
# Proactive: revalidation is UPCOMING within the lookahead window. Pitch
|
||||
# is "handle it before your deadline" -- taps the same CMS Revalidation
|
||||
# Due Date List as reval_overdue but the (much larger) not-yet-due slice,
|
||||
# so it grows warmup supply without touching a new data source.
|
||||
# days_overdue is negative for upcoming (days until due), so a provider
|
||||
# due in N days has days_overdue == -N.
|
||||
if status != "upcoming":
|
||||
return False
|
||||
od = _overdue_days(r)
|
||||
if od is None:
|
||||
return False
|
||||
days_until = -od
|
||||
return WARMUP_DUE_SOON_MIN <= days_until <= WARMUP_DUE_SOON_MAX
|
||||
if sel == "reval_upcoming": return status == "upcoming"
|
||||
if sel == "leie_or_deactivated":
|
||||
# Reactivation targets: flagged excluded, OR no longer on the reval list
|
||||
|
|
@ -265,16 +284,28 @@ def row_matches(seg_key: str, r: dict) -> bool:
|
|||
|
||||
|
||||
def attribs_for(r: dict) -> dict:
|
||||
# days_overdue is positive when past due and negative when upcoming (days
|
||||
# until the due date). Expose a clean positive "days_until" for the
|
||||
# due-soon segment's template.
|
||||
od_raw = (str(r.get("days_overdue", "")) or "").strip()
|
||||
days_until = ""
|
||||
try:
|
||||
od = int(od_raw)
|
||||
if od < 0:
|
||||
days_until = str(-od)
|
||||
except ValueError:
|
||||
pass
|
||||
return {
|
||||
"npi": r.get("npi", ""),
|
||||
"practice": r.get("name", ""),
|
||||
"specialty": r.get("specialty", ""),
|
||||
"state": r.get("state", ""),
|
||||
# Separate fields so the email's "official CMS record" card can render
|
||||
# the due date + overdue count cleanly (mirrors the CMS Revalidation Due
|
||||
# Date List, verified by NPI via the weekly hc_data_refresh).
|
||||
# the due date + overdue/until count cleanly (mirrors the CMS
|
||||
# Revalidation Due Date List, verified by NPI via the weekly refresh).
|
||||
"reval_due_date": r.get("reval_due_date", ""),
|
||||
"days_overdue": str(r.get("days_overdue", "")),
|
||||
"days_until": days_until,
|
||||
"detail": (f"{r.get('reval_due_date','')} ({r.get('days_overdue','')} days overdue)"
|
||||
if r.get("reval_status") == "overdue" else r.get("reval_due_date", "")),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue