healthcare: verify CMS-10114 update path, correct NPI Enumerator address, build CMS-10114 filler

Verified firsthand against the live CMS-10114 (Rev. 02/25, OMB 0938-0931):
- Section 1A confirms paper is valid for Change of Information (#2) AND
  Reactivation (#4), not just initial enumeration. Resolves the UNCERTAIN flag.
- Current mailing address is CMS NPI Enumerator Services, Mail Stop DO-01-51,
  7500 Security Blvd, Baltimore MD 21244. The old Fargo PO Box 6059 is retired;
  corrected in mac_routing.NPI_ENUMERATOR + all docs.
- No electronic no-login equivalent exists for CMS (NPI Registry API is
  read-only; PECOS/NPPES-IA require login), unlike FMCSA's ask.fmcsa ticket form.
  So tiers stay: Standard=paper CMS-10114 (no login), Expedited=NPPES surrogate.

New: cms10114_pdf_filler.py fills the flat official form via text overlay
(reason checkbox + NPI + Section 2A identity + Section 4A cert name + signature
anchor); wired into npi_provider._generate_10114_for_signing for nppes-update.
Signed forms route to the NPI Enumerator via the existing daily batch.

Tests: test_cms10114.py 27/27, test_paper_batch.py 15/15, Astro build 58 pages.
This commit is contained in:
justin 2026-06-07 02:04:41 -05:00
parent f9c294e962
commit e6a630ada1
10 changed files with 540 additions and 48 deletions

View file

@ -48,8 +48,8 @@ def _is_postal_working_day(d: date) -> bool:
def _destination_for(practice_state: str, document_type: str):
"""Return the (key, name, address_lines) destination for a filing.
NPPES/CMS-10114 updates go to the NPI Enumerator (Fargo); CMS-855s go to the
provider's MAC by state.
NPPES/CMS-10114 updates go to the NPI Enumerator (Baltimore, MD); CMS-855s go
to the provider's MAC by state.
"""
try:
from scripts.workers import mac_routing as mr

View file

@ -107,9 +107,13 @@ STATE_TO_MAC: dict[str, MAC] = {
# NPI Enumerator paper address (NPPES / CMS-10114 paper path) — not a MAC, but a
# destination the daily batch groups by, same as a MAC.
# VERIFIED 2025 against CMS-10114 (Rev. 02/25), OMB 0938-0931, page 5 "Or send the
# completed signed application to:". The earlier Fargo, ND PO Box 6059 address is
# RETIRED; the current address printed on the form is the Baltimore one below.
NPI_ENUMERATOR = MAC(
"npi_enumerator", "NPI Enumerator (NPPES / CMS-10114 paper)",
("NPI Enumerator", "P.O. Box 6059", "Fargo, ND 58108-6059"),
("CMS NPI Enumerator Services", "Mail Stop DO-01-51",
"7500 Security Blvd.", "Baltimore, MD 21244"),
)

View file

@ -126,16 +126,22 @@ _SLUG_META = {
# batched mailing). The bundle's revalidation piece is handled by the dedicated
# revalidation order it spawns, so it is not listed here.
#
# nppes-update is intentionally NOT here: its Standard path is the CMS-10114 (NPPES
# update mailed to the NPI Enumerator in Fargo), handled by a separate filler when
# built. Until then, nppes-update falls to the admin todo (Expedited if surrogate
# granted, otherwise manual CMS-10114 prep).
# nppes-update is NOT a CMS-855 filing: its Standard (no-login) path is the
# CMS-10114 NPI Application/Update form, signed by the provider and mailed to the
# NPI Enumerator (CMS NPI Enumerator Services, Baltimore MD). It is handled by
# the dedicated CMS-10114 filler below (_STANDARD_10114_SLUGS).
_STANDARD_FILING_SLUGS = {
"npi-revalidation",
"npi-reactivation",
"medicare-enrollment",
}
# Slugs whose Standard path is the CMS-10114 (NPPES update/reactivation/deactivation
# mailed to the NPI Enumerator). The reason checkbox is derived from the slug.
_STANDARD_10114_SLUGS = {
"nppes-update",
}
class _BaseNPIHandler:
"""Shared review-staged behaviour for all NPI services."""
@ -178,6 +184,14 @@ class _BaseNPIHandler:
except Exception as exc: # never block the admin todo on PDF issues
LOG.error("[%s] CMS-855 generation failed: %s", order_number, exc)
filing_note = f"CMS-855 auto-generation FAILED ({exc}); prepare the form manually."
elif self.SERVICE_SLUG in _STANDARD_10114_SLUGS:
try:
filing_note = self._generate_10114_for_signing(
order_number, intake, provider, customer_email
)
except Exception as exc: # never block the admin todo on PDF issues
LOG.error("[%s] CMS-10114 generation failed: %s", order_number, exc)
filing_note = f"CMS-10114 auto-generation FAILED ({exc}); prepare the form manually."
surrogate_label = {
"yes": "YES — client can grant I&A Surrogate access -> file the EXPEDITED way (online via surrogate).",
@ -267,16 +281,78 @@ class _BaseNPIHandler:
note_lines.extend(f" - {m}" for m in missing)
return "\n".join(note_lines)
def _generate_10114_for_signing(self, order_number, intake, provider, customer_email) -> str:
"""Generate the official CMS-10114 (NPPES update path), upload it, and
request an e-signature.
Standard (no-login) path for NPPES data updates / NPI reactivation: the
provider signs the certification and the signed form is mailed to the NPI
Enumerator (CMS NPI Enumerator Services, Baltimore MD) via the daily batch.
Returns a human-readable note for the admin todo.
"""
try:
from scripts.document_gen.templates.cms10114_pdf_filler import (
fill_cms10114, normalize_reason,
)
except ImportError:
from document_gen.templates.cms10114_pdf_filler import ( # type: ignore
fill_cms10114, normalize_reason,
)
# Reason comes from the explicit intake reason if present, else the slug.
reason = normalize_reason(intake.get("filing_reason") or self.SERVICE_SLUG)
pdf_bytes, anchors, missing = fill_cms10114(intake, reason=reason, order_number=order_number)
document_key = f"compliance/{order_number}/cms10114_unsigned.pdf"
try:
import tempfile
try:
from scripts.document_gen.minio_client import MinioStorage
except ImportError:
from document_gen.minio_client import MinioStorage # type: ignore
storage = MinioStorage()
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=True) as tf:
tf.write(pdf_bytes)
tf.flush()
storage.upload(tf.name, document_key, content_type="application/pdf")
except Exception as exc:
LOG.error("[%s] CMS-10114 upload failed: %s", order_number, exc)
return f"CMS-10114 generated but upload FAILED ({exc})."
signed = self._create_855_esign_record(
order_number, intake, provider, customer_email,
form_type="10114", document_key=document_key, anchors=anchors,
document_type="cms10114",
document_title="NPI Application/Update Form (CMS-10114)",
)
note_lines = [
f"CMS-10114 generated (reason: {reason}; 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, the form joins the daily mail batch to the NPI Enumerator (standard) or file via NPPES surrogate access (expedited).")
else:
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)
return "\n".join(note_lines)
def _create_855_esign_record(self, order_number, intake, provider, customer_email,
form_type, document_key, anchors) -> bool:
form_type, document_key, anchors,
document_type=None, document_title=None) -> bool:
"""Create the esign record + email the signing link via the shared helper,
then attach the official signature anchors. Returns True on success.
Used for both CMS-855 and CMS-10114 (pass document_type/document_title to
override the defaults).
"""
if not customer_email:
return False
document_type = f"cms{form_type}"
document_title = f"Medicare Enrollment Form (CMS-{form_type.upper()})"
document_type = document_type or f"cms{form_type}"
document_title = document_title or f"Medicare Enrollment Form (CMS-{form_type.upper()})"
try:
try: