clia: new CLIA certificate renewal service, order page, email template + harvest
Set up the CLIA recurring-renewal vein (every clinical lab renews its CLIA cert on a 2-year cycle; CMS publishes the full lab file with expiration dates): - service-catalog: clia-renewal ($449, discountable) + order page (npi-intake steps) + intake manifest entry. - harvest_clia_renewals.py: parse the CMS Provider-of-Services CLIA file, filter to labs expiring within a window (default 120d), emit name/address/phone/expiry. 676k labs -> ~70k expiring in the next ~4 months. - match_clia_to_nppes.py: CLIA has no NPI/email, so bridge to emailable NPPES orgs by normalized name+zip to recover NPI+email (yield TBD; labs that do not match still have clean phone+postal for a phone/mail channel). - hc_clia_renewal.html: warm turnover-safety-net email with the striped official- record card (CLIA #, expiry, status), verify-on-CMS-QCOR, founder guarantee card, full CAN-SPAM address.
This commit is contained in:
parent
d1a9260854
commit
9c7a08f5c9
7 changed files with 398 additions and 0 deletions
|
|
@ -522,6 +522,12 @@ export const COMPLIANCE_SERVICES: Record<string, ComplianceService> = {
|
|||
erpnext_item: "NPPES-UPDATE",
|
||||
discountable: true,
|
||||
},
|
||||
"clia-renewal": {
|
||||
name: "CLIA Certificate Renewal",
|
||||
price_cents: 44900,
|
||||
erpnext_item: "CLIA-RENEWAL",
|
||||
discountable: true,
|
||||
},
|
||||
"medicare-enrollment": {
|
||||
name: "Medicare Enrollment (PECOS)",
|
||||
price_cents: 69900,
|
||||
|
|
|
|||
117
data/hc_campaigns/hc_clia_renewal.html
Normal file
117
data/hc_campaigns/hc_clia_renewal.html
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>@media only screen and (max-width:600px){.pw-wrap{width:100%!important;border-radius:0!important;}.pw-pad{padding:24px 16px!important;}}body,table,td,p,a{-webkit-text-size-adjust:100%;}table{border-collapse:collapse!important;}img{border:0;}</style></head><body style="margin:0;padding:0;background:#eef0f3;">
|
||||
<center>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#eef0f3;"><tr><td style="padding:24px 10px;">
|
||||
<table role="presentation" class="pw-wrap" width="620" cellpadding="0" cellspacing="0" style="margin:0 auto;border-radius:10px;overflow:hidden;background:#fff;">
|
||||
|
||||
<!-- Header -->
|
||||
<tr><td style="background-color:#0f766e;background:linear-gradient(135deg,#0f766e 0%,#14b8a6 100%);padding:26px 28px;">
|
||||
<img src="https://performancewest.net/images/logo-white.png" alt="Performance West" style="height:44px;margin-bottom:10px;display:block" />
|
||||
<h1 style="color:#fff;margin:0;font-size:21px;font-weight:700;font-family:Inter,system-ui,sans-serif;">Your CLIA certificate is coming up for renewal</h1>
|
||||
<p style="color:#ccfbf1;margin:6px 0 0;font-size:13px;font-family:Inter,system-ui,sans-serif;">Let's get it filed before it lapses</p>
|
||||
</td></tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr><td class="pw-pad" style="padding:28px;font-family:Inter,system-ui,sans-serif;color:#1f2937;">
|
||||
<p style="font-size:15px;margin:0 0 16px;line-height:1.6;">Hi {{ .Subscriber.Name }},</p>
|
||||
|
||||
<p style="font-size:14px;line-height:1.75;margin:0 0 16px;">Whoever last handled the lab certification for <strong>{{ .Subscriber.Attribs.practice }}</strong> may have moved on — CLIA certificates run on a 2-year cycle, and renewal deadlines are easy to miss when staff change.</p>
|
||||
|
||||
<p style="font-size:14px;line-height:1.75;margin:0 0 18px;">That is exactly what we keep an eye on, so it does not become your problem. Here is where your certificate stands:</p>
|
||||
|
||||
<!-- Official-record card: CLIA data is from the public CMS POS file. -->
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:6px 0 22px;">
|
||||
<tr><td style="border:1px solid #cbd5e1;border-radius:10px;overflow:hidden;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr><td style="background:#2563eb;background-image:repeating-linear-gradient(45deg,#ef4444 0,#ef4444 14px,#ffffff 14px,#ffffff 28px,#2563eb 28px,#2563eb 42px,#ffffff 42px,#ffffff 56px);padding:0;">
|
||||
<p style="margin:0;padding:11px 16px;background:rgba(15,23,42,0.58);font-size:11px;letter-spacing:.4px;text-transform:uppercase;color:#ffffff;font-weight:700;text-align:center;">Official record · CMS CLIA Laboratory File</p>
|
||||
</td></tr>
|
||||
<tr><td style="background:#f8fafc;padding:6px 16px 14px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="font-size:13px;">
|
||||
<tr style="border-bottom:1px solid #e5e7eb;"><td style="padding:9px 0;color:#64748b;">CLIA number</td><td style="padding:9px 0;font-weight:700;text-align:right;color:#0f172a;">{{ .Subscriber.Attribs.clia }}</td></tr>
|
||||
<tr style="border-bottom:1px solid #e5e7eb;"><td style="padding:9px 0;color:#64748b;">Laboratory</td><td style="padding:9px 0;font-weight:600;text-align:right;color:#0f172a;">{{ .Subscriber.Attribs.practice }}</td></tr>
|
||||
<tr style="border-bottom:1px solid #e5e7eb;"><td style="padding:9px 0;color:#64748b;">Certificate expires</td><td style="padding:9px 0;font-weight:700;text-align:right;color:#b91c1c;">{{ .Subscriber.Attribs.clia_expiry }}</td></tr>
|
||||
<tr><td style="padding:9px 0;color:#64748b;">Status</td><td style="padding:9px 0;font-weight:700;text-align:right;color:#b91c1c;">{{ .Subscriber.Attribs.clia_status }}</td></tr>
|
||||
</table>
|
||||
<p style="margin:10px 0 0;font-size:11px;color:#94a3b8;line-height:1.5;">Source: CMS Provider of Services File - Clinical Laboratories (data.cms.gov), refreshed quarterly.</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<p style="font-size:14px;line-height:1.75;margin:0 0 18px;">If a CLIA certificate lapses, you cannot legally perform or bill for laboratory testing until it is reinstated — which can interrupt patient care and revenue. <strong>Starting now is the best way to make sure your renewal is filed and processed in time</strong> — CMS can take several weeks to process, so the earlier we begin, the more buffer you have against government processing delays.</p>
|
||||
|
||||
<!-- How it works -->
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:18px 0;"><tr><td style="background:#f0fdfa;border:1px solid #99f6e4;border-radius:10px;padding:18px;">
|
||||
<h3 style="margin:0 0 10px;font-size:15px;color:#0f766e;font-weight:700;">We do the work — you barely lift a finger</h3>
|
||||
<p style="font-size:13px;color:#134e4a;line-height:1.7;margin:0 0 10px;">We prepare and file your CLIA renewal (CMS Form 116) and track it through to confirmation. <strong>You never share a password and you never have to chase a government portal.</strong> The only thing we may need is a one-minute e-signature on a secure link.</p>
|
||||
<p style="font-size:13px;color:#134e4a;line-height:1.7;margin:0;">No matter who handled it before, we will make sure it gets done right.</p>
|
||||
</td></tr></table>
|
||||
|
||||
<!-- Verify-it-yourself -->
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:14px 0 18px;"><tr><td style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;padding:16px;">
|
||||
<p style="margin:0 0 6px;font-size:13px;color:#1e3a8a;font-weight:700;">Want to confirm it yourself?</p>
|
||||
<p style="margin:0 0 12px;font-size:13px;color:#1e40af;line-height:1.6;">Look up CLIA number <strong>{{ .Subscriber.Attribs.clia }}</strong> on the public CMS QCOR laboratory lookup and you will see the same expiration date shown above.</p>
|
||||
<a href="https://qcor.cms.gov/main.jsp" style="display:inline-block;padding:10px 22px;background:#fff;border:1px solid #1d4ed8;color:#1d4ed8;font-weight:700;border-radius:8px;text-decoration:none;font-size:13px;">Verify on CMS QCOR ↗</a>
|
||||
</td></tr></table>
|
||||
|
||||
<!-- CTA -->
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:22px 0;"><tr><td style="background:#ecfdf5;border:2px solid #10b981;border-radius:10px;padding:18px;text-align:center;">
|
||||
<p style="font-size:14px;color:#065f46;margin:0 0 6px;font-weight:600;">Let us take the CLIA renewal off your plate — the sooner we start, the better.</p>
|
||||
<p style="font-size:12px;color:#047857;margin:0 0 14px;">We submit most filings within 1-2 business days, then track it through CMS processing to confirmation.</p>
|
||||
<a href="https://performancewest.net/order/clia-renewal?clia={{ .Subscriber.Attribs.clia }}" style="display:inline-block;padding:14px 40px;background:#10b981;color:#fff;font-weight:700;border-radius:8px;text-decoration:none;font-size:15px;">Renew my CLIA certificate →</a>
|
||||
</td></tr></table>
|
||||
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:18px 0;"><tr><td style="background:#f0f4f8;border-radius:8px;padding:16px;font-size:13px;color:#374151;line-height:1.6;">
|
||||
<strong>Questions, or want to hand off your lab filings going forward?</strong> Just reply to this email or call <strong>(888) 411-0383</strong>. We are a dedicated healthcare compliance firm and we are happy to be the people who keep track of this for you.
|
||||
</td></tr></table>
|
||||
|
||||
<!-- Personal guarantee from the founder: photo links to the About page so
|
||||
readers can confirm a real person stands behind the work. -->
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:22px 0;"><tr><td style="background:#f8fafc;border:1px solid #e2e8f0;border-left:4px solid #0f766e;border-radius:10px;padding:20px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td width="92" valign="top" style="padding-right:16px;">
|
||||
<a href="https://performancewest.net/about">
|
||||
<img src="https://performancewest.net/images/justin-hannah.jpg" alt="Justin Hannah, Founder of Performance West" width="76" height="76" style="width:76px;height:76px;border-radius:50%;display:block;border:2px solid #0f766e;" />
|
||||
</a>
|
||||
</td>
|
||||
<td valign="top">
|
||||
<p style="margin:0 0 10px;font-size:14px;color:#1f2937;line-height:1.7;font-style:italic;">“If we handle this for you and you are not completely satisfied, I will personally make it right. You will not pay billable hours, and you will not be left chasing a government portal on your own. That is my promise.”</p>
|
||||
<p style="margin:0;">
|
||||
<a href="https://performancewest.net/about" style="text-decoration:none;">
|
||||
<img src="https://performancewest.net/images/justin-signature-v2.png" alt="Justin Hannah" width="150" style="width:150px;height:auto;display:block;margin:0 0 2px;" />
|
||||
</a>
|
||||
<span style="font-size:13px;font-weight:700;color:#0f172a;">Justin Hannah</span><br>
|
||||
<span style="font-size:12px;color:#64748b;">Founder & Principal Consultant, Performance West Inc.</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td></tr></table>
|
||||
|
||||
<!-- Trust signals + true social proof -->
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:20px 0 6px;border-top:1px solid #e5e7eb;padding-top:14px;">
|
||||
<tr>
|
||||
<td align="center" style="padding:10px 4px;font-family:Inter,system-ui,sans-serif;">
|
||||
<span style="display:inline-block;margin:0 8px;font-size:11px;font-weight:600;color:#0f766e;">🛡️ SOC 2 Type II hosting</span>
|
||||
<span style="display:inline-block;margin:0 8px;font-size:11px;font-weight:600;color:#0f766e;">✅ HIPAA & PCI compliant</span>
|
||||
<span style="display:inline-block;margin:0 8px;font-size:11px;font-weight:600;color:#0f766e;">🔒 256-bit TLS encrypted</span>
|
||||
<span style="display:inline-block;margin:0 8px;font-size:11px;font-weight:600;color:#0f766e;">💳 Secure payment by Stripe</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding:4px;font-family:Inter,system-ui,sans-serif;font-size:12px;color:#4b5563;">
|
||||
<strong style="color:#047857;">100% satisfaction guarantee</strong> · fixed pricing, no billable hours · trusted by providers nationwide
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr><td style="padding:16px 28px;background:#f8fafc;border-top:1px solid #e5e7eb;font-size:11px;color:#9ca3af;text-align:center;">
|
||||
<p style="margin:0 0 8px;">Performance West is an independent compliance firm, not affiliated with CMS or Medicare.</p>
|
||||
<p style="margin:0;">Performance West Inc. · 525 Randall Ave Ste 100-1195, Cheyenne, WY 82001 · <a href="https://performancewest.net" style="color:#6b7280;">performancewest.net</a></p>
|
||||
<p style="margin:6px 0 0;"><a href="{{ UnsubscribeURL }}" style="color:#6b7280;">Unsubscribe</a></p>
|
||||
</td></tr>
|
||||
|
||||
</table></td></tr></table></center></body></html>
|
||||
138
scripts/harvest_clia_renewals.py
Normal file
138
scripts/harvest_clia_renewals.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Harvest active CLIA labs with an upcoming certificate expiration.
|
||||
|
||||
Reads the CMS "Provider of Services File - Clinical Laboratories" CSV
|
||||
(clia.DATA.Qx_YYYY.csv from data.cms.gov) and writes the labs whose CLIA
|
||||
certificate expires within a configurable window. CLIA certificates run on a
|
||||
2-year cycle, so the expiration date (TRMNTN_EXPRTN_DT) is the recurring
|
||||
reminder trigger.
|
||||
|
||||
The POS/CLIA file has NO NPI and NO email -- only facility name, mailing
|
||||
address and phone. So this harvest emits the matchable identity columns
|
||||
(name + city/state/zip + phone) plus the cert dates; a separate matcher joins
|
||||
to NPPES (by name+zip) to recover an emailable NPI where possible. Labs that
|
||||
never match still have a clean phone + postal address for a phone/mail channel.
|
||||
|
||||
Usage:
|
||||
python3 scripts/harvest_clia_renewals.py CLIA_INPUT.csv OUT.csv [--window-days 120]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import sys
|
||||
from collections import Counter
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
# Columns we need from the POS CLIA file (by header name; robust to reordering).
|
||||
WANT = {
|
||||
"clia": "PRVDR_NUM",
|
||||
"name": "FAC_NAME",
|
||||
"addr": "ST_ADR",
|
||||
"city": "CITY_NAME",
|
||||
"state": "STATE_CD",
|
||||
"zip": "ZIP_CD",
|
||||
"phone": "PHNE_NUM",
|
||||
"expiry": "TRMNTN_EXPRTN_DT",
|
||||
"effective": "CRTFCT_EFCTV_DT",
|
||||
"cert_type": "CRTFCT_TYPE_CD",
|
||||
"compliance": "CMPLNC_STUS_CD",
|
||||
}
|
||||
|
||||
# CLIA certificate types worth reminding (all are renewable 2yr certs):
|
||||
# 1 = Registration, 2 = Compliance, 3 = Accreditation,
|
||||
# 4 = PPM (Provider-Performed Microscopy), 9 = Waiver
|
||||
# We keep all of them; the expiry window is the real filter.
|
||||
|
||||
|
||||
def parse_yyyymmdd(s: str):
|
||||
s = (s or "").strip()
|
||||
if len(s) == 8 and s.isdigit():
|
||||
try:
|
||||
return datetime.strptime(s, "%Y%m%d").date()
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("infile")
|
||||
ap.add_argument("outfile")
|
||||
ap.add_argument("--window-days", type=int, default=120,
|
||||
help="emit labs expiring within this many days from today (default 120)")
|
||||
ap.add_argument("--include-recently-expired-days", type=int, default=30,
|
||||
help="also include labs that expired up to N days ago (lapsed, still renewable)")
|
||||
args = ap.parse_args()
|
||||
|
||||
today = date.today()
|
||||
horizon = today + timedelta(days=args.window_days)
|
||||
grace = today - timedelta(days=args.include_recently_expired_days)
|
||||
|
||||
with open(args.infile, newline="", encoding="latin-1") as f:
|
||||
reader = csv.reader(f)
|
||||
header = next(reader)
|
||||
idx = {c: i for i, c in enumerate(header)}
|
||||
missing = [k for k, col in WANT.items() if col not in idx]
|
||||
if missing:
|
||||
print(f"ERROR: input missing columns: {[WANT[m] for m in missing]}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
stats = Counter()
|
||||
rows_out = []
|
||||
for row in reader:
|
||||
stats["total"] += 1
|
||||
if len(row) <= max(idx[c] for c in WANT.values()):
|
||||
stats["short_row"] += 1
|
||||
continue
|
||||
|
||||
exp = parse_yyyymmdd(row[idx[WANT["expiry"]]])
|
||||
if not exp:
|
||||
stats["no_expiry"] += 1
|
||||
continue
|
||||
if not (grace <= exp <= horizon):
|
||||
stats["outside_window"] += 1
|
||||
continue
|
||||
|
||||
name = row[idx[WANT["name"]]].strip()
|
||||
state = row[idx[WANT["state"]]].strip()
|
||||
zipc = row[idx[WANT["zip"]]].strip()[:5]
|
||||
if not name or not state:
|
||||
stats["no_name_state"] += 1
|
||||
continue
|
||||
|
||||
days_until = (exp - today).days
|
||||
rows_out.append({
|
||||
"clia": row[idx[WANT["clia"]]].strip(),
|
||||
"name": name,
|
||||
"addr": row[idx[WANT["addr"]]].strip(),
|
||||
"city": row[idx[WANT["city"]]].strip(),
|
||||
"state": state,
|
||||
"zip": zipc,
|
||||
"phone": row[idx[WANT["phone"]]].strip(),
|
||||
"expiry_date": exp.isoformat(),
|
||||
"days_until": days_until,
|
||||
"cert_type": row[idx[WANT["cert_type"]]].strip(),
|
||||
"status": "lapsed" if days_until < 0 else "upcoming",
|
||||
})
|
||||
stats["emitted"] += 1
|
||||
|
||||
rows_out.sort(key=lambda r: r["days_until"]) # most urgent first
|
||||
with open(args.outfile, "w", newline="", encoding="utf-8") as f:
|
||||
w = csv.DictWriter(f, fieldnames=list(rows_out[0].keys()) if rows_out else
|
||||
["clia", "name", "addr", "city", "state", "zip", "phone",
|
||||
"expiry_date", "days_until", "cert_type", "status"])
|
||||
w.writeheader()
|
||||
w.writerows(rows_out)
|
||||
|
||||
print(f"CLIA harvest: {stats['total']:,} rows scanned")
|
||||
for k in ("no_expiry", "outside_window", "no_name_state", "short_row"):
|
||||
if stats[k]:
|
||||
print(f" skipped {k}: {stats[k]:,}")
|
||||
print(f" EMITTED (expiring in [-{args.include_recently_expired_days}d, "
|
||||
f"+{args.window_days}d]): {stats['emitted']:,} -> {args.outfile}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
100
scripts/match_clia_to_nppes.py
Normal file
100
scripts/match_clia_to_nppes.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Match CLIA labs to an emailable NPPES org by (normalized name + zip5).
|
||||
|
||||
CLIA POS files have no NPI/email; our NPPES verified set is keyed by NPI. This
|
||||
bridges them: it streams the big NPPES npidata_pfile, keeps ONLY the orgs whose
|
||||
NPI already has a verified email (so the scan stays cheap), indexes them by
|
||||
normalized org-name + zip5, then matches each CLIA lab to recover its NPI+email.
|
||||
|
||||
Outputs the CLIA renewal rows that got an emailable match, with email +
|
||||
mx_provider appended (ready to feed the HC campaign builder as a CLIA segment).
|
||||
|
||||
Usage:
|
||||
python3 scripts/match_clia_to_nppes.py \
|
||||
CLIA_RENEWALS.csv NPPES_VERIFIED.csv NPIDATA_PFILE.csv OUT.csv
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import re
|
||||
import sys
|
||||
|
||||
csv.field_size_limit(10_000_000)
|
||||
|
||||
# npidata_pfile column names we use (stable header names in the NPPES file).
|
||||
COL_NPI = "NPI"
|
||||
COL_ORG = "Provider Organization Name (Legal Business Name)"
|
||||
COL_ZIP_PRACTICE = "Provider Business Practice Location Address Postal Code"
|
||||
COL_ENTITY = "Entity Type Code" # 2 = organization
|
||||
|
||||
|
||||
def norm_name(s: str) -> str:
|
||||
s = (s or "").upper()
|
||||
s = re.sub(r"[^A-Z0-9 ]", " ", s)
|
||||
# drop common suffixes/noise that differ between CLIA and NPPES spellings
|
||||
s = re.sub(r"\b(LLC|INC|PC|PLLC|PA|LTD|CORP|CO|LP|LLP|THE|DBA)\b", " ", s)
|
||||
s = re.sub(r"\s+", " ", s).strip()
|
||||
return s
|
||||
|
||||
|
||||
def main() -> int:
|
||||
clia_f, nppes_verified_f, npidata_f, out_f = sys.argv[1:5]
|
||||
|
||||
# 1) emailable NPIs -> (email, mx_provider)
|
||||
email_by_npi: dict[str, tuple[str, str]] = {}
|
||||
with open(nppes_verified_f, newline="", encoding="utf-8") as f:
|
||||
for r in csv.DictReader(f):
|
||||
npi = (r.get("npi") or "").strip()
|
||||
email = (r.get("email") or "").strip()
|
||||
if npi and email and (r.get("verify_ok", "Y") in ("Y", "", "true", "True")):
|
||||
email_by_npi[npi] = (email, r.get("mx_provider", ""))
|
||||
print(f"emailable NPIs: {len(email_by_npi):,}", file=sys.stderr)
|
||||
|
||||
# 2) stream npidata_pfile, keep only those NPIs -> index by (name, zip5)
|
||||
idx: dict[tuple[str, str], str] = {}
|
||||
with open(npidata_f, newline="", encoding="latin-1") as f:
|
||||
reader = csv.DictReader(f)
|
||||
seen = 0
|
||||
for row in reader:
|
||||
npi = (row.get(COL_NPI) or "").strip()
|
||||
if npi not in email_by_npi:
|
||||
continue
|
||||
org = norm_name(row.get(COL_ORG, ""))
|
||||
zip5 = (row.get(COL_ZIP_PRACTICE) or "").strip()[:5]
|
||||
if org and zip5:
|
||||
idx[(org, zip5)] = npi
|
||||
seen += 1
|
||||
if seen == len(email_by_npi):
|
||||
break
|
||||
print(f"indexed emailable orgs by name+zip: {len(idx):,}", file=sys.stderr)
|
||||
|
||||
# 3) match CLIA -> index
|
||||
matched = 0
|
||||
total = 0
|
||||
with open(clia_f, newline="", encoding="utf-8") as fin, \
|
||||
open(out_f, "w", newline="", encoding="utf-8") as fout:
|
||||
reader = csv.DictReader(fin)
|
||||
fieldnames = reader.fieldnames + ["npi", "email", "mx_provider"]
|
||||
w = csv.DictWriter(fout, fieldnames=fieldnames)
|
||||
w.writeheader()
|
||||
for row in reader:
|
||||
total += 1
|
||||
key = (norm_name(row["name"]), (row["zip"] or "")[:5])
|
||||
npi = idx.get(key)
|
||||
if not npi:
|
||||
continue
|
||||
email, mx = email_by_npi[npi]
|
||||
row["npi"] = npi
|
||||
row["email"] = email
|
||||
row["mx_provider"] = mx
|
||||
w.writerow(row)
|
||||
matched += 1
|
||||
|
||||
print(f"CLIA labs: {total:,} | matched to emailable NPPES org: {matched:,} "
|
||||
f"({100*matched/max(total,1):.1f}%)")
|
||||
print(f" -> {out_f}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -150,6 +150,7 @@ export const INTAKE_MANIFEST: Record<string, IntakeStep[]> = {
|
|||
"npi-revalidation": ["npi-intake", "review", "payment"],
|
||||
"npi-reactivation": ["npi-intake", "review", "payment"],
|
||||
"nppes-update": ["npi-intake", "review", "payment"],
|
||||
"clia-renewal": ["npi-intake", "review", "payment"],
|
||||
"medicare-enrollment": ["npi-intake", "review", "payment"],
|
||||
"oig-sam-screening": ["npi-intake", "review", "payment"],
|
||||
"provider-compliance-bundle": ["npi-intake", "review", "payment"],
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export const SERVICE_META: Record<string, ServiceMeta> = {
|
|||
"cdr-storage-tier1": { name: "CDR Storage Tier 1 (50 GB / 50M calls)", price_cents: 9900 },
|
||||
"cdr-storage-tier2": { name: "CDR Storage Tier 2 (250 GB / 250M calls)", price_cents: 29900 },
|
||||
"cdr-storage-tier3": { name: "CDR Storage Tier 3 (1 TB / 1B calls)", price_cents: 79900 },
|
||||
"clia-renewal": { name: "CLIA Certificate Renewal", price_cents: 44900 },
|
||||
"cores-frn-registration": { name: "CORES / FRN Registration", price_cents: 14900 },
|
||||
"corp-formation": { name: "Corporation Formation", price_cents: 24900 },
|
||||
"cpni-certification": { name: "CPNI Annual Certification", price_cents: 19900 },
|
||||
|
|
|
|||
35
site/src/pages/order/clia-renewal.astro
Normal file
35
site/src/pages/order/clia-renewal.astro
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import Base from "../../layouts/Base.astro";
|
||||
import VerticalOrderHeader from "../../components/VerticalOrderHeader.astro";
|
||||
import Wizard from "../../components/intake/Wizard.astro";
|
||||
import OrderPriceBanner from "../../components/OrderPriceBanner.astro";
|
||||
import { INTAKE_MANIFEST, SERVICE_META } from "../../lib/intake_manifest";
|
||||
|
||||
const slug = "clia-renewal";
|
||||
const steps = INTAKE_MANIFEST[slug];
|
||||
const meta = SERVICE_META[slug];
|
||||
const title = meta ? `Order ${meta.name}` : "Order";
|
||||
const description = "Renew your CLIA laboratory certificate with CMS before it expires. CLIA certificates run on a 2-year cycle; an expired certificate stops you from legally performing or billing for lab testing.";
|
||||
---
|
||||
|
||||
<Base title={title} description={description}>
|
||||
<main>
|
||||
<section class="pw-order-intro">
|
||||
<h1>{meta?.name}</h1>
|
||||
<p class="pw-desc">{description}</p>
|
||||
</section>
|
||||
|
||||
<VerticalOrderHeader vertical="healthcare" />
|
||||
|
||||
<OrderPriceBanner priceCents={meta?.price_cents} govFeeLabel={meta?.gov_fee_label} serviceSlug={slug} note="Choose card, ACH, or PayPal at payment." />
|
||||
|
||||
<Wizard service_slug={slug} steps={steps ?? ["npi-intake", "review", "payment"]} title={meta?.name ?? slug} />
|
||||
</main>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
|
||||
.pw-order-intro { margin-bottom: 1.5rem; }
|
||||
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
|
||||
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue