(["npi-reactivation", "nppes-update"]);
- const hasPaper855 = npiAccessOrders.some(o => PAPER_855_SLUGS.has(o.service_slug as string));
+ const hasStandardFiling = npiAccessOrders.some(o => STANDARD_FILING_SLUGS.has(o.service_slug as string));
const hasNppesOnly = npiAccessOrders.some(o => NPPES_ONLY_SLUGS.has(o.service_slug as string));
- const paper855Block = hasPaper855 ? `
- Option 1 (easiest): Paper CMS-855 — no account needed
+ const standardFilingBlock = hasStandardFiling ? `
+ Standard filing — no account needed
- We complete the correct CMS-855 form for you. You e-sign the certification page
- from a secure link we send (takes about a minute), and we print it and mail it to
- your Medicare Administrative Contractor (MAC). Nothing for you to set up.
+ We complete the correct CMS-855 for you. You review and sign the certification
+ from a secure link we send (takes about a minute), and we submit it to your
+ Medicare Administrative Contractor (MAC) and track it to confirmation. Nothing
+ for you to set up.
` : "";
- const surrogacyHeading = hasPaper855
- ? `Option 2 (faster): CMS I&A surrogate access`
+ const surrogacyHeading = hasStandardFiling
+ ? `Expedited filing — CMS I&A surrogate access`
: `Grant CMS I&A surrogate access`;
const npiSection = hasNpiAccess ? `
Action Required: Choose How We File
- ${paper855Block}
+ ${standardFilingBlock}
${surrogacyHeading}
${hasNppesOnly ? "NPPES updates and reactivations are online-only, so this is required for those. " : ""}You add us as a
Surrogate in CMS Identity & Access (I&A) — you never share your password.
- We then file in PECOS / NPPES under our own credentials, e-sign where permitted, and capture the tracking ID.
+ We then file in PECOS / NPPES under our own credentials and capture the tracking ID the same day.
- Log in to CMS I&A (I&A System)
@@ -2201,7 +2202,7 @@ export async function sendComplianceIntakeEmail(
- ${hasPaper855 ? "Prefer the paper option? Just reply to this email and we'll send your CMS-855 to e-sign — no further action needed from you here." : "Clicking this notifies our team so we can begin your filing."}
+ ${hasStandardFiling ? "Prefer standard filing? Just reply to this email and we'll send your CMS-855 to sign — no further action needed from you here." : "Clicking this notifies our team so we can begin your filing."}
` : "";
diff --git a/scripts/document_gen/templates/cms855_pdf_filler.py b/scripts/document_gen/templates/cms855_pdf_filler.py
index 2cd8e9c..c673554 100644
--- a/scripts/document_gen/templates/cms855_pdf_filler.py
+++ b/scripts/document_gen/templates/cms855_pdf_filler.py
@@ -23,6 +23,7 @@ from __future__ import annotations
import io
import logging
+import re
from pathlib import Path
LOG = logging.getLogger("cms855_pdf_filler")
@@ -93,15 +94,45 @@ def _split_name(full: str) -> tuple[str, str, str]:
return parts[0], parts[1][0], " ".join(parts[2:])
+def _lookup_enumeration_type(npi: str) -> str:
+ """Best-effort NPPES lookup of an NPI's enumeration_type (NPI-1 / NPI-2).
+
+ Returns "" if the NPI is missing, malformed, or the lookup fails — callers
+ fall back to the individual form (855I), the safe default.
+ """
+ npi = (npi or "").strip()
+ if not re.fullmatch(r"\d{10}", npi):
+ return ""
+ try:
+ import json as _json
+ from urllib.request import urlopen
+ url = f"https://npiregistry.cms.hhs.gov/api/?version=2.1&number={npi}"
+ with urlopen(url, timeout=8) as resp: # nosec - public, read-only
+ data = _json.loads(resp.read().decode("utf-8"))
+ results = data.get("results") or []
+ if results:
+ return (results[0].get("enumeration_type") or "").upper()
+ except Exception:
+ LOG.warning("NPPES enumeration_type lookup failed for %s", npi)
+ return ""
+
+
def determine_form_type(slug: str, intake: dict) -> str:
- """Pick which 855 form applies for the order."""
+ """Pick which 855 form applies for the order.
+
+ Org NPIs (Type 2 / NPI-2) revalidate/enroll on 855B; individuals on 855I.
+ The wizard does not collect enumeration_type, so when it is absent we look
+ it up live from NPPES rather than silently defaulting an organization to the
+ wrong (individual) form, which CMS would reject.
+ """
+ if slug not in ("npi-revalidation", "medicare-enrollment"):
+ return "855i"
+
enum_type = (intake.get("enumeration_type") or "").upper()
- if slug == "npi-revalidation":
- # Org NPIs (Type 2) revalidate on 855B; individuals on 855I.
- return "855b" if enum_type in ("NPI-2", "2", "ORGANIZATION") else "855i"
- if slug == "medicare-enrollment":
- return "855b" if enum_type in ("NPI-2", "2", "ORGANIZATION") else "855i"
- return "855i"
+ if not enum_type:
+ enum_type = _lookup_enumeration_type(intake.get("npi", ""))
+
+ return "855b" if enum_type in ("NPI-2", "2", "ORGANIZATION") else "855i"
def fill_cms855(form_type: str, intake: dict, order_number: str = "") -> tuple[bytes, list[dict], list[str]]:
diff --git a/scripts/test_healthcare_e2e.py b/scripts/test_healthcare_e2e.py
new file mode 100644
index 0000000..fbc240e
--- /dev/null
+++ b/scripts/test_healthcare_e2e.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+"""End-to-end consistency + flow test for the Healthcare / NPI ordering line.
+
+Validates the full order path across all wiring points and runs the worker
+handlers for real (PDF generation, todo creation), catching logical errors.
+
+Checks:
+ 1. Slug consistency across all 5 registration points.
+ 2. Pricing agreement (catalog vs intake manifest vs ERPNext www/orders).
+ 3. Intake manifest steps + the wizard collects every server-required field.
+ 4. Worker dispatch maps every slug to a handler.
+ 5. Each handler runs end-to-end (mocked DB/MinIO/esign) and produces the
+ right artifacts: CMS-855 PDF + signature anchor for filing slugs, todo
+ for all.
+ 6. The free-tool action_urls point at real order/service slugs.
+
+Run: python3 scripts/test_healthcare_e2e.py
+Exit code 0 = all pass, 1 = failures.
+"""
+import json
+import re
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT / "scripts"))
+
+HEALTHCARE_SLUGS = [
+ "npi-revalidation", "npi-reactivation", "nppes-update",
+ "medicare-enrollment", "oig-sam-screening", "provider-compliance-bundle",
+]
+# Slugs that should generate a CMS-855 PDF + e-sign anchor.
+FILING_SLUGS = {"npi-revalidation", "npi-reactivation", "medicare-enrollment"}
+
+failures: list[str] = []
+passes: list[str] = []
+
+
+def ok(msg):
+ passes.append(msg)
+
+
+def fail(msg):
+ failures.append(msg)
+
+
+def read(p: Path) -> str:
+ return (ROOT / p).read_text()
+
+
+# ── 1. Slug consistency across the 5 registration points ──────────────────
+def check_slug_consistency():
+ sources = {
+ "compliance-orders.ts (catalog)": ("api/src/routes/compliance-orders.ts",
+ r'"(npi-[a-z-]+|nppes-update|medicare-enrollment|oig-sam-screening|provider-compliance-bundle)":\s*\{'),
+ "compliance-orders.ts (intake reqs)": ("api/src/routes/compliance-orders.ts",
+ r'"(npi-[a-z-]+|nppes-update|medicare-enrollment|oig-sam-screening|provider-compliance-bundle)":\s*\{ required'),
+ "intake_manifest.ts (steps)": ("site/src/lib/intake_manifest.ts",
+ r'"(npi-[a-z-]+|nppes-update|medicare-enrollment|oig-sam-screening|provider-compliance-bundle)":\s*\['),
+ "intake_manifest.ts (meta)": ("site/src/lib/intake_manifest.ts",
+ r'"(npi-[a-z-]+|nppes-update|medicare-enrollment|oig-sam-screening|provider-compliance-bundle)":\s*\{ name'),
+ "www/orders.py": ("performancewest_erpnext/performancewest_erpnext/www/orders.py",
+ r'"(npi-[a-z-]+|nppes-update|medicare-enrollment|oig-sam-screening|provider-compliance-bundle)":'),
+ "services/__init__.py (dispatch)": ("scripts/workers/services/__init__.py",
+ r'"(npi-[a-z-]+|nppes-update|medicare-enrollment|oig-sam-screening|provider-compliance-bundle)":\s*\w+Handler'),
+ }
+ want = set(HEALTHCARE_SLUGS)
+ for label, (path, pat) in sources.items():
+ try:
+ txt = read(Path(path))
+ except FileNotFoundError:
+ fail(f"[slugs] file missing: {path}")
+ continue
+ found = set(re.findall(pat, txt))
+ missing = want - found
+ extra = found - want
+ if missing:
+ fail(f"[slugs] {label}: MISSING {sorted(missing)}")
+ elif extra:
+ fail(f"[slugs] {label}: UNEXPECTED {sorted(extra)}")
+ else:
+ ok(f"[slugs] {label}: all 6 present")
+
+
+# ── 2. Pricing agreement ──────────────────────────────────────────────────
+def check_pricing():
+ cat = read(Path("api/src/routes/compliance-orders.ts"))
+ man = read(Path("site/src/lib/intake_manifest.ts"))
+
+ def prices(txt):
+ out = {}
+ for slug in HEALTHCARE_SLUGS:
+ m = re.search(rf'"{re.escape(slug)}":\s*\{{[^}}]*?price_cents:\s*(\d+)', txt, re.S)
+ if m:
+ out[slug] = int(m.group(1))
+ return out
+
+ cprices, mprices = prices(cat), prices(man)
+ for slug in HEALTHCARE_SLUGS:
+ c, m = cprices.get(slug), mprices.get(slug)
+ if c is None:
+ fail(f"[price] {slug}: no price in catalog")
+ elif m is None:
+ fail(f"[price] {slug}: no price in intake manifest")
+ elif c != m:
+ fail(f"[price] {slug}: catalog {c} != manifest {m}")
+ else:
+ ok(f"[price] {slug}: ${c/100:.0f} consistent")
+
+
+# ── 3. Intake: wizard collects every server-required field ────────────────
+def check_intake_fields():
+ cat = read(Path("api/src/routes/compliance-orders.ts"))
+ step = read(Path("site/src/components/intake/steps/NpiIntakeStep.astro"))
+
+ # Fields the wizard writes into intake_data (from PW.set({...}) block).
+ set_block = re.search(r"PW\.set\(\{[^}]*intake_data:\s*\{(.+?)\}\}\)", step, re.S)
+ collected = set(re.findall(r"(\w+):", set_block.group(1))) if set_block else set()
+
+ # Fields the wizard *enforces* as required (the `missing.push` validations).
+ enforced = set()
+ if "npi-provider-name" in step and "missing.push" in step:
+ if 'val("npi-provider-name")' in step: enforced.add("provider_name")
+ if 'val("npi-number")' in step or "val(\"npi-number\")" in step: enforced.add("npi")
+ if 'val("npi-email")' in step: enforced.add("email")
+
+ # Slug-conditional enforcement: e.g. practice_state is only required when
+ # the active service is medicare-enrollment. Map field -> {slugs}.
+ conditional_enforced = {}
+ if 'activeSlugs.includes("medicare-enrollment")' in step and 'val("npi-practice-state")' in step:
+ conditional_enforced["practice_state"] = {"medicare-enrollment"}
+
+ for slug in HEALTHCARE_SLUGS:
+ m = re.search(rf'"{re.escape(slug)}":\s*\{{ required:\s*\[([^\]]*)\]', cat)
+ if not m:
+ fail(f"[intake] {slug}: no required-fields entry in catalog")
+ continue
+ required = set(re.findall(r'"([a-z_]+)"', m.group(1)))
+ # every required field must be collectable by the wizard
+ not_collected = required - collected
+ if not_collected:
+ fail(f"[intake] {slug}: required {sorted(not_collected)} NOT collected by wizard")
+ # every required field must be *enforced* — either always, or
+ # conditionally for this slug.
+ not_enforced = set()
+ for fld in required - enforced:
+ if slug not in conditional_enforced.get(fld, set()):
+ not_enforced.add(fld)
+ if not_enforced:
+ fail(f"[intake] {slug}: required {sorted(not_enforced)} collected but NOT validated as required in wizard")
+ if not not_collected and not not_enforced:
+ ok(f"[intake] {slug}: all required fields collected + enforced")
+
+
+# ── 4 & 5. Worker dispatch + handler execution ────────────────────────────
+def check_handlers():
+ import asyncio
+ import workers.services.npi_provider as npi
+ from workers.services import SERVICE_HANDLERS
+
+ for slug in HEALTHCARE_SLUGS:
+ if slug not in SERVICE_HANDLERS:
+ fail(f"[dispatch] {slug}: no handler registered")
+ continue
+ ok(f"[dispatch] {slug}: -> {SERVICE_HANDLERS[slug].__name__}")
+
+ # Run each handler with mocked IO and verify artifacts.
+ captured = {}
+
+ def fake_todo(self, order_number, intake, title, description, priority="normal"):
+ captured.setdefault(self.SERVICE_SLUG, {})["todo"] = {"title": title, "desc": description}
+
+ esign_calls = {}
+
+ def fake_esign(self, order_number, intake, provider, customer_email, form_type, document_key, anchors):
+ esign_calls[self.SERVICE_SLUG] = {"form_type": form_type, "anchors": anchors, "key": document_key}
+ return True # pretend esign + email succeeded
+
+ # Stub MinIO upload inside the filler path by stubbing _generate_855... upload
+ orig_gen = npi._BaseNPIHandler._generate_855_for_signing
+
+ def gen_no_upload(self, order_number, intake, provider, customer_email):
+ from document_gen.templates.cms855_pdf_filler import determine_form_type, fill_cms855
+ ft = determine_form_type(self.SERVICE_SLUG, intake)
+ pdf, anchors, missing = fill_cms855(ft, intake, order_number)
+ captured.setdefault(self.SERVICE_SLUG, {})["pdf"] = {"len": len(pdf), "anchors": anchors, "missing": missing, "form_type": ft}
+ # exercise the esign record path (stubbed)
+ self._create_855_esign_record(order_number, intake, provider, customer_email, ft, f"compliance/{order_number}/cms{ft}.pdf", anchors)
+ return f"CMS-{ft.upper()} generated ({len(pdf)} bytes)"
+
+ npi._BaseNPIHandler._create_todo = fake_todo
+ npi._BaseNPIHandler._create_855_esign_record = fake_esign
+ npi._BaseNPIHandler._generate_855_for_signing = gen_no_upload
+
+ intake = {
+ "npi": "1234567893", "provider_name": "Jane Q Smith", "email": "jane@example.com",
+ "dob": "01011980", "practice_state": "CA", "enumeration_type": "NPI-1",
+ }
+ for slug in HEALTHCARE_SLUGS:
+ h = SERVICE_HANDLERS[slug]()
+ order = {"order_number": f"CO-T-{slug}", "customer_name": "Jane Q Smith",
+ "customer_email": "jane@example.com", "intake_data": dict(intake)}
+ try:
+ asyncio.run(h.process(order))
+ except Exception as e:
+ fail(f"[handler] {slug}: raised {type(e).__name__}: {e}")
+ continue
+
+ c = captured.get(slug, {})
+ if "todo" not in c:
+ fail(f"[handler] {slug}: no admin todo created")
+ else:
+ ok(f"[handler] {slug}: admin todo created")
+
+ if slug in FILING_SLUGS:
+ if "pdf" not in c:
+ fail(f"[handler] {slug}: expected CMS-855 PDF, none generated")
+ else:
+ pdf = c["pdf"]
+ if pdf["len"] < 10000:
+ fail(f"[handler] {slug}: PDF suspiciously small ({pdf['len']} bytes)")
+ elif not pdf["anchors"]:
+ fail(f"[handler] {slug}: PDF has NO signature anchor (form={pdf['form_type']})")
+ else:
+ a = pdf["anchors"][0]
+ need = {"field", "page", "x", "y", "w", "h", "page_w", "page_h"}
+ if not need.issubset(a):
+ fail(f"[handler] {slug}: anchor missing keys {need - set(a)}")
+ else:
+ ok(f"[handler] {slug}: CMS-{pdf['form_type'].upper()} {pdf['len']}B + anchor on page {a['page']}")
+ if slug not in esign_calls:
+ fail(f"[handler] {slug}: esign record was not requested")
+ else:
+ ok(f"[handler] {slug}: esign record requested (form {esign_calls[slug]['form_type']})")
+ else:
+ if "pdf" in c:
+ fail(f"[handler] {slug}: unexpectedly generated a CMS-855 PDF (should not)")
+ else:
+ ok(f"[handler] {slug}: correctly skips CMS-855 generation")
+
+
+# ── 6. Free-tool action_urls point at real slugs ──────────────────────────
+def check_free_tool_links():
+ try:
+ lookup = read(Path("api/src/routes/npi-lookup.ts"))
+ except FileNotFoundError:
+ fail("[tool] npi-lookup.ts missing")
+ return
+ urls = set(re.findall(r'action_url:\s*"(/(?:order|services/healthcare)[^"]*)"', lookup))
+ order_pages = {p.stem for p in (ROOT / "site/src/pages/order").glob("*.astro")}
+ for u in sorted(urls):
+ if u.startswith("/order/"):
+ slug = u.split("/order/")[1].strip("/")
+ if slug not in order_pages:
+ fail(f"[tool] action_url {u} -> no order page /order/{slug}.astro")
+ else:
+ ok(f"[tool] action_url {u} resolves")
+ else:
+ # /services/healthcare[/x]
+ tail = u.replace("/services/healthcare", "").strip("/")
+ target = ROOT / "site/src/pages/services/healthcare" / (f"{tail}.astro" if tail else "index.astro")
+ if not target.exists():
+ fail(f"[tool] action_url {u} -> no page {target.relative_to(ROOT)}")
+ else:
+ ok(f"[tool] action_url {u} resolves")
+
+
+def main():
+ check_slug_consistency()
+ check_pricing()
+ check_intake_fields()
+ check_handlers()
+ check_free_tool_links()
+
+ print("\n".join(f" PASS {p}" for p in passes))
+ if failures:
+ print("\n".join(f" FAIL {f}" for f in failures))
+ print(f"\n{len(passes)} passed, {len(failures)} FAILED")
+ sys.exit(1)
+ print(f"\nALL {len(passes)} CHECKS PASSED")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/workers/services/npi_provider.py b/scripts/workers/services/npi_provider.py
index c1fcbb7..bf7c237 100644
--- a/scripts/workers/services/npi_provider.py
+++ b/scripts/workers/services/npi_provider.py
@@ -41,7 +41,7 @@ _SLUG_META = {
"enrollment record, and submit. Capture the PECOS tracking ID."
),
"access": (
- "PECOS via CMS I&A surrogacy (preferred). Fallback: paper CMS-855I/B/R, provider wet-signs cert page, mail to provider's MAC."
+ "Standard: prepare CMS-855I/B/R, provider signs cert, submit to MAC. Expedited: file in PECOS via CMS I&A surrogate access."
),
"priority": "high",
},
@@ -53,7 +53,7 @@ _SLUG_META = {
"reason, correct any stale data, and re-certify the record."
),
"access": (
- "NPPES via CMS I&A surrogacy. No paper option (NPPES is web-only)."
+ "NPPES via CMS I&A surrogate access (online-only)."
),
"priority": "high",
},
@@ -66,7 +66,7 @@ _SLUG_META = {
"changes and certify."
),
"access": (
- "NPPES via CMS I&A surrogacy. No paper option (NPPES is web-only)."
+ "NPPES via CMS I&A surrogate access (online-only)."
),
"priority": "normal",
},
@@ -78,7 +78,7 @@ _SLUG_META = {
"Confirm taxonomy, practice location, and authorized official."
),
"access": (
- "PECOS via CMS I&A surrogacy (preferred). Fallback: paper CMS-855, provider wet-signs, mail to MAC."
+ "Standard: prepare CMS-855, provider signs cert, submit to MAC. Expedited: file in PECOS via CMS I&A surrogate access."
),
"priority": "high",
},
@@ -104,17 +104,17 @@ _SLUG_META = {
"record. Set the next revalidation reminder."
),
"access": (
- "PECOS/NPPES via CMS I&A surrogacy; screening is public. Paper CMS-855 fallback for the enrollment/revalidation piece."
+ "Standard CMS-855 filing for the enrollment/revalidation piece; NPPES + PECOS via CMS I&A surrogate access; screening is public."
),
"priority": "high",
},
}
-# Slugs whose fulfilment includes a paper CMS-855 (auto-filled official form,
-# e-signed, then printed + USPS Priority-mailed to the provider's MAC). The
+# Slugs whose fulfilment includes a CMS-855 (auto-filled official form, signed
+# via the secure e-sign link, then submitted to the provider's MAC). The
# bundle's revalidation piece is handled by the dedicated revalidation order it
# spawns, so it is not listed here.
-_PAPER_855_SLUGS = {
+_STANDARD_FILING_SLUGS = {
"npi-revalidation",
"npi-reactivation",
"medicare-enrollment",
@@ -151,16 +151,16 @@ class _BaseNPIHandler:
)
# For PECOS enrollment/revalidation we generate the official CMS-855,
- # send it for e-signature, then a human prints + USPS-mails it to the MAC.
- paper_note = ""
- if self.SERVICE_SLUG in _PAPER_855_SLUGS:
+ # send it for e-signature, then a human submits it to the MAC.
+ filing_note = ""
+ if self.SERVICE_SLUG in _STANDARD_FILING_SLUGS:
try:
- paper_note = self._generate_855_for_signing(
+ filing_note = self._generate_855_for_signing(
order_number, intake, provider, customer_email
)
except Exception as exc: # never block the admin todo on PDF issues
LOG.error("[%s] CMS-855 generation failed: %s", order_number, exc)
- paper_note = f"CMS-855 auto-generation FAILED ({exc}); prepare the form manually."
+ filing_note = f"CMS-855 auto-generation FAILED ({exc}); prepare the form manually."
description = (
f"{meta['action']}\n\n"
@@ -171,9 +171,9 @@ class _BaseNPIHandler:
f"Practice state: {practice_state or 'not provided'}\n"
f"Portal: {meta['portal']}\n"
f"Access model: {meta['access']}\n"
- + (f"\n{paper_note}\n" if paper_note else "")
+ + (f"\n{filing_note}\n" if filing_note else "")
+ "\nReview-staged: complete/verify the form, get it signed, then "
- "print and USPS Priority Mail it to the provider's MAC (or file in "
+ "submit it to the provider's MAC (standard), or file in "
"PECOS if surrogate access was granted). Mark this order complete."
)
@@ -191,7 +191,7 @@ class _BaseNPIHandler:
Returns a human-readable note for the admin todo describing what was
generated and what still needs manual completion. The signed PDF is
- printed and USPS Priority-mailed to the MAC by the fulfilment team.
+ submitted to the MAC by the fulfilment team.
"""
try:
from scripts.document_gen.templates.cms855_pdf_filler import (
@@ -231,13 +231,13 @@ class _BaseNPIHandler:
)
note_lines = [
- f"PAPER CMS-{form_type.upper()} generated (official form, auto-filled where possible).",
+ f"CMS-{form_type.upper()} generated (official form, auto-filled where possible).",
f"Unsigned PDF: {document_key}",
]
if signed and customer_email:
- note_lines.append(f"E-sign link emailed to {customer_email}. After signing, print + USPS Priority Mail to the MAC.")
+ note_lines.append(f"E-sign link emailed to {customer_email}. After signing, submit to the MAC (standard) or file via PECOS surrogate access (expedited).")
else:
- note_lines.append("No customer email or esign infra — send the form for wet signature manually.")
+ note_lines.append("No customer email or esign infra — send the form for signature manually.")
if missing:
note_lines.append("MANUAL COMPLETION NEEDED:")
note_lines.extend(f" - {m}" for m in missing)
diff --git a/site/src/components/intake/steps/NpiIntakeStep.astro b/site/src/components/intake/steps/NpiIntakeStep.astro
index 66a1f27..a6b17f2 100644
--- a/site/src/components/intake/steps/NpiIntakeStep.astro
+++ b/site/src/components/intake/steps/NpiIntakeStep.astro
@@ -157,6 +157,16 @@
if (!email) missing.push("Email");
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) missing.push("a valid Email");
+ // Medicare enrollment requires the practice/MAC routing state (server-side
+ // required field). Enforce it here so the order can't be submitted without
+ // it and then stall in fulfilment.
+ const activeSlugs = PW.get().batch_slugs
+ || [document.querySelector(".pw-wizard[data-service]")?.getAttribute("data-service")
+ || PW.get().service_slug || ""];
+ if (activeSlugs.includes("medicare-enrollment") && !val("npi-practice-state")) {
+ missing.push("Practice State (required for Medicare enrollment)");
+ }
+
if (missing.length) {
evt.preventDefault();
errDiv.hidden = false;
diff --git a/site/src/pages/services/healthcare/medicare-enrollment.astro b/site/src/pages/services/healthcare/medicare-enrollment.astro
index 4a19bbd..3b13145 100644
--- a/site/src/pages/services/healthcare/medicare-enrollment.astro
+++ b/site/src/pages/services/healthcare/medicare-enrollment.astro
@@ -46,14 +46,15 @@ const description = "New to Medicare or adding a practice location? We assemble
How it works (without sharing your password)
- You place the order and give us your NPI and basic practice details.
- - We send a one-time link to add us as a Surrogate in CMS I&A. You never share your login.
- We pull your NPPES record and identify exactly which 855 forms apply.
- - We file in Internet-based PECOS under our own CMS credentials and track it through your MAC.
+ - We prepare your CMS-855 package and send a secure link for you to review and sign the certification.
+ - We submit it to your Medicare Administrative Contractor (MAC) and track it through to approval.
- Prefer paper? We can complete a paper CMS-855 instead,
- send it for your wet signature, and mail it to your Medicare
- Administrative Contractor (CMS does not accept faxed 855s).
+ Need it filed faster? Choose expedited at
+ checkout and add us as a Surrogate in CMS Identity &
+ Access. We then file directly in PECOS under our own credentials and
+ capture the tracking ID the same day. You never share your login.
diff --git a/site/src/pages/services/healthcare/npi-revalidation.astro b/site/src/pages/services/healthcare/npi-revalidation.astro
index bffaad1..54bc5f9 100644
--- a/site/src/pages/services/healthcare/npi-revalidation.astro
+++ b/site/src/pages/services/healthcare/npi-revalidation.astro
@@ -73,8 +73,8 @@ const description = "CMS requires every enrolled provider and supplier to revali
password.
- - Paper CMS-855 (easiest, nothing to set up). We complete the correct CMS-855 form, you e-sign the certification page from a secure link (about a minute), and we print it and mail it to your Medicare Administrative Contractor (MAC). CMS does not accept faxed 855s, so we handle the mailing.
- - Internet-based PECOS (faster, needs a CMS account). You add us as a Surrogate in the CMS Identity & Access (I&A) system; we then file in PECOS under our own credentials and capture the tracking ID.
+ - Standard filing (nothing to set up). We complete the correct CMS-855, you approve and sign the certification from a secure link in about a minute, and we submit it to your Medicare Administrative Contractor (MAC) and track it to confirmation.
+ - Expedited filing (needs a CMS account). You add us as a Surrogate in the CMS Identity & Access (I&A) system; we then file directly in PECOS under our own credentials and capture the tracking ID the same day.