diff --git a/api/src/routes/checkout.ts b/api/src/routes/checkout.ts index 37c8aef..165f79b 100644 --- a/api/src/routes/checkout.ts +++ b/api/src/routes/checkout.ts @@ -2152,42 +2152,43 @@ export async function sendComplianceIntakeEmail(

` : ""; - // CMS filing-method section for PECOS / NPPES orders. We offer two paths and - // let the provider pick the one that's easiest for them: - // (1) Paper CMS-855 — they e-sign one page, we print + mail to their MAC. - // Zero account setup. Default for revalidation/enrollment. - // (2) I&A surrogacy — faster/trackable, but needs a CMS I&A account. - // NPPES-only services (reactivation, update) are web-only, so surrogacy is - // the only path for those. We never ask for the provider's password. + // CMS filing-method section for PECOS / NPPES orders. We offer two service + // tiers and let the provider pick the one that's easiest for them: + // (1) Standard filing — they review + sign one certification, we submit to + // their MAC. Zero account setup. Default for reval/enrollment. + // (2) Expedited filing — faster/same-day-trackable via CMS I&A surrogate access. + // NPPES-only services (reactivation, update) are web-only, so surrogate access + // is the only path for those. We never ask for the provider's password. const npiConfirmUrl = `${SITE_DOMAIN}/order/success?action=ia_surrogacy&order_id=${orderId}`; - // Which ordered services can use the paper CMS-855 path (PECOS enrollment/reval) - // vs. are NPPES-web-only (surrogacy required). - const PAPER_855_SLUGS = new Set(["npi-revalidation", "medicare-enrollment", "provider-compliance-bundle"]); + // Which ordered services support the standard (no-account) filing path + // vs. are NPPES-web-only (surrogate access required). + const STANDARD_FILING_SLUGS = new Set(["npi-revalidation", "medicare-enrollment", "provider-compliance-bundle"]); const NPPES_ONLY_SLUGS = new Set(["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.

  1. Log in to CMS I&A (I&A System)
  2. @@ -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)

  1. You place the order and give us your NPI and basic practice details.
  2. -
  3. We send a one-time link to add us as a Surrogate in CMS I&A. You never share your login.
  4. We pull your NPPES record and identify exactly which 855 forms apply.
  5. -
  6. We file in Internet-based PECOS under our own CMS credentials and track it through your MAC.
  7. +
  8. We prepare your CMS-855 package and send a secure link for you to review and sign the certification.
  9. +
  10. 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.