Reframe healthcare filing as standard vs expedited; e2e test + bug fixes

Copy: drop paper/electronic/fax framing across the revalidation + enrollment
marketing pages and the order-confirmation email; present two service tiers:
- Standard filing  (no CMS account; we prepare CMS-855, you sign, we submit to MAC)
- Expedited filing (CMS I&A surrogate access; same-day PECOS filing + tracking)
Internal worker todos + the _STANDARD_FILING_SLUGS identifier updated to match.

New scripts/test_healthcare_e2e.py validates the whole order line (slug
consistency x6 places, price agreement, intake field collection+enforcement,
worker dispatch, handler execution producing CMS-855 PDF+anchor, free-tool
action_urls). 45 checks.

Bugs found + fixed by the test:
- medicare-enrollment requires practice_state server-side but the wizard never
  enforced it -> orders could be paid then stall. Wizard now requires it.
- determine_form_type defaulted org NPIs to the individual 855I because
  enumeration_type is never collected -> wrong form, CMS rejection. Now does a
  live NPPES lookup (safe 855I fallback).
This commit is contained in:
justin 2026-06-05 03:58:46 -05:00
parent 5cfe9702e2
commit 695ace207c
7 changed files with 381 additions and 54 deletions

View file

@ -2152,42 +2152,43 @@ export async function sendComplianceIntakeEmail(
</p>
</div>` : "";
// 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<string>(["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<string>(["npi-revalidation", "medicare-enrollment", "provider-compliance-bundle"]);
const NPPES_ONLY_SLUGS = new Set<string>(["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 ? `
<p style="margin:0 0 6px;font-size:13px;font-weight:700;color:#115e59;">Option 1 (easiest): Paper CMS-855 &mdash; no account needed</p>
const standardFilingBlock = hasStandardFiling ? `
<p style="margin:0 0 6px;font-size:13px;font-weight:700;color:#115e59;">Standard filing &mdash; no account needed</p>
<p style="margin:0 0 12px;font-size:13px;color:#134e4a;line-height:1.5;">
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.
</p>` : "";
const surrogacyHeading = hasPaper855
? `Option 2 (faster): CMS I&amp;A surrogate access`
const surrogacyHeading = hasStandardFiling
? `Expedited filing &mdash; CMS I&amp;A surrogate access`
: `Grant CMS I&amp;A surrogate access`;
const npiSection = hasNpiAccess ? `
<div style="background:#ccfbf1;border:1px solid #5eead4;border-radius:8px;padding:16px 20px;margin:20px 0;">
<p style="margin:0 0 10px;font-size:14px;font-weight:700;color:#115e59;">Action Required: Choose How We File</p>
${paper855Block}
${standardFilingBlock}
<p style="margin:0 0 6px;font-size:13px;font-weight:700;color:#115e59;">${surrogacyHeading}</p>
<p style="margin:0 0 12px;font-size:13px;color:#134e4a;line-height:1.5;">
${hasNppesOnly ? "NPPES updates and reactivations are online-only, so this is required for those. " : ""}You add us as a
<strong>Surrogate</strong> in CMS Identity &amp; Access (I&amp;A) &mdash; 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.
</p>
<ol style="margin:0 0 12px;padding-left:20px;font-size:13px;color:#134e4a;line-height:1.7;">
<li>Log in to <a href="https://nppes.cms.hhs.gov/IAWeb/login.do" style="color:#0f766e;">CMS I&amp;A (I&amp;A System)</a></li>
@ -2201,7 +2202,7 @@ export async function sendComplianceIntakeEmail(
</a>
</p>
<p style="margin:8px 0 0;font-size:12px;color:#115e59;text-align:center;">
${hasPaper855 ? "Prefer the paper option? Just reply to this email and we'll send your CMS-855 to e-sign &mdash; 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 &mdash; no further action needed from you here." : "Clicking this notifies our team so we can begin your filing."}
</p>
</div>` : "";

View file

@ -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]]:

View file

@ -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()

View file

@ -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)

View file

@ -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;

View file

@ -46,14 +46,15 @@ const description = "New to Medicare or adding a practice location? We assemble
<h2>How it works (without sharing your password)</h2>
<ol>
<li>You place the order and give us your NPI and basic practice details.</li>
<li>We send a one-time link to add us as a <strong>Surrogate</strong> in CMS I&amp;A. You never share your login.</li>
<li>We pull your NPPES record and identify exactly which 855 forms apply.</li>
<li>We file in <strong>Internet-based PECOS</strong> under our own CMS credentials and track it through your MAC.</li>
<li>We prepare your CMS-855 package and send a secure link for you to review and sign the certification.</li>
<li>We submit it to your Medicare Administrative Contractor (MAC) and track it through to approval.</li>
</ol>
<div class="pw-callout">
<strong>Prefer paper?</strong> 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).
<strong>Need it filed faster?</strong> Choose <strong>expedited</strong> at
checkout and add us as a <strong>Surrogate</strong> in CMS Identity &amp;
Access. We then file directly in PECOS under our own credentials and
capture the tracking ID the same day. You never share your login.
</div>
</section>

View file

@ -73,8 +73,8 @@ const description = "CMS requires every enrolled provider and supplier to revali
password.</strong>
</p>
<ul>
<li><strong>Paper CMS-855 (easiest, nothing to set up).</strong> 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.</li>
<li><strong>Internet-based PECOS (faster, needs a CMS account).</strong> You add us as a <strong>Surrogate</strong> in the CMS Identity &amp; Access (I&amp;A) system; we then file in PECOS under our own credentials and capture the tracking ID.</li>
<li><strong>Standard filing (nothing to set up).</strong> 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.</li>
<li><strong>Expedited filing (needs a CMS account).</strong> You add us as a <strong>Surrogate</strong> in the CMS Identity &amp; Access (I&amp;A) system; we then file directly in PECOS under our own credentials and capture the tracking ID the same day.</li>
</ul>
</section>