diff --git a/api/src/routes/compliance-orders.ts b/api/src/routes/compliance-orders.ts index 3afffc7..320e44c 100644 --- a/api/src/routes/compliance-orders.ts +++ b/api/src/routes/compliance-orders.ts @@ -464,6 +464,48 @@ const COMPLIANCE_SERVICES: Record< erpnext_item: "ENTITY-UPGRADE-BUNDLE", discountable: true, }, + // ── Healthcare / NPI compliance ────────────────────────────────────── + // CMS/NPPES provider compliance. Handlers are review-staged (a human + // files in PECOS/NPPES) — same safety default as the FCC auto-filing + // toggle. HIPAA is intentionally out of scope (separate specialty). + // Flagship = npi-revalidation (CMS 5-yr Medicare revalidation, dateable + // overdue signal from the free CMS revalidation list). + "npi-revalidation": { + name: "Medicare PECOS Revalidation Filing", + price_cents: 39900, + erpnext_item: "NPI-REVALIDATION", + discountable: true, + }, + "npi-reactivation": { + name: "NPI Reactivation", + price_cents: 24900, + erpnext_item: "NPI-REACTIVATION", + discountable: true, + }, + "nppes-update": { + name: "NPPES Data Update / Attestation", + price_cents: 14900, + erpnext_item: "NPPES-UPDATE", + discountable: true, + }, + "medicare-enrollment": { + name: "Medicare Enrollment (PECOS)", + price_cents: 49900, + erpnext_item: "MEDICARE-ENROLLMENT", + discountable: true, + }, + "oig-sam-screening": { + name: "OIG/SAM Exclusion Screening (Annual)", + price_cents: 9900, + erpnext_item: "OIG-SAM-SCREENING", + discountable: false, + }, + "provider-compliance-bundle": { + name: "Provider Compliance Bundle (Annual)", + price_cents: 69900, + erpnext_item: "PROVIDER-COMPLIANCE-BUNDLE", + discountable: true, + }, }; // ── Intake validation map ───────────────────────────────────────────── @@ -643,6 +685,14 @@ const REQUIRED_FIELDS: Record = { // ── Hazmat / Emissions ─────────────────────────────────────────────── "hazmat-phmsa": { required: ["dot_number", "legal_name", "email", "hazmat_classes"], soft: ["bulk_packaging", "small_business", "ein"] }, "state-emissions": { required: ["dot_number", "legal_name", "base_state", "email"], soft: ["engine_model_years", "power_units"] }, + + // ── Healthcare / NPI ───────────────────────────────────────────────── + "npi-revalidation": { required: ["npi", "provider_name", "email"], soft: ["pecos_enrollment_id", "specialty", "practice_state"] }, + "npi-reactivation": { required: ["npi", "provider_name", "email"], soft: ["deactivation_reason", "specialty"] }, + "nppes-update": { required: ["npi", "provider_name", "email"], soft: ["fields_to_update", "practice_state"] }, + "medicare-enrollment": { required: ["npi", "provider_name", "email", "practice_state"], soft: ["pecos_enrollment_id", "specialty", "entity_type"] }, + "oig-sam-screening": { required: ["npi", "provider_name", "email"], soft: ["organization_name", "staff_count"] }, + "provider-compliance-bundle": { required: ["npi", "provider_name", "email"], soft: ["pecos_enrollment_id", "specialty", "practice_state"] }, }; // ── Bundle composition + incompatibility (single source of truth) ────────── diff --git a/performancewest_erpnext/performancewest_erpnext/fixtures/item.json b/performancewest_erpnext/performancewest_erpnext/fixtures/item.json index 75bf709..b374b79 100644 --- a/performancewest_erpnext/performancewest_erpnext/fixtures/item.json +++ b/performancewest_erpnext/performancewest_erpnext/fixtures/item.json @@ -58,5 +58,77 @@ "include_item_in_manufacturing": 0, "standard_rate": 0.0, "currency": "USD" + }, + { + "doctype": "Item", + "item_code": "NPI-REVALIDATION", + "item_name": "Medicare PECOS Revalidation Filing", + "description": "Prepare and file the provider's Medicare revalidation in PECOS (5-year cycle).", + "item_group": "Services", + "stock_uom": "Nos", + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "standard_rate": 399.0, + "currency": "USD" + }, + { + "doctype": "Item", + "item_code": "NPI-REACTIVATION", + "item_name": "NPI Reactivation", + "description": "Reactivate a deactivated NPI in NPPES and re-certify the provider record.", + "item_group": "Services", + "stock_uom": "Nos", + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "standard_rate": 249.0, + "currency": "USD" + }, + { + "doctype": "Item", + "item_code": "NPPES-UPDATE", + "item_name": "NPPES Data Update / Attestation", + "description": "Update and re-attest a provider's NPPES record (CMS requires updates within 30 days of changes).", + "item_group": "Services", + "stock_uom": "Nos", + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "standard_rate": 149.0, + "currency": "USD" + }, + { + "doctype": "Item", + "item_code": "MEDICARE-ENROLLMENT", + "item_name": "Medicare Enrollment (PECOS)", + "description": "Complete a provider's Medicare enrollment via PECOS (CMS-855).", + "item_group": "Services", + "stock_uom": "Nos", + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "standard_rate": 499.0, + "currency": "USD" + }, + { + "doctype": "Item", + "item_code": "OIG-SAM-SCREENING", + "item_name": "OIG/SAM Exclusion Screening (Annual)", + "description": "Annual OIG LEIE + SAM exclusion screening for a provider and listed staff, with certificate.", + "item_group": "Services", + "stock_uom": "Nos", + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "standard_rate": 99.0, + "currency": "USD" + }, + { + "doctype": "Item", + "item_code": "PROVIDER-COMPLIANCE-BUNDLE", + "item_name": "Provider Compliance Bundle (Annual)", + "description": "Annual provider compliance bundle: revalidation watch, OIG/SAM screening, and NPPES upkeep.", + "item_group": "Services", + "stock_uom": "Nos", + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "standard_rate": 699.0, + "currency": "USD" } ] diff --git a/performancewest_erpnext/performancewest_erpnext/www/orders.py b/performancewest_erpnext/performancewest_erpnext/www/orders.py index 5510560..8be3c9f 100644 --- a/performancewest_erpnext/performancewest_erpnext/www/orders.py +++ b/performancewest_erpnext/performancewest_erpnext/www/orders.py @@ -27,6 +27,13 @@ COMPLIANCE_SERVICE_LABELS = { "stir-shaken": "STIR/SHAKEN Implementation Assistance", "dc-agent": "D.C. Registered Agent (Annual)", "bdc-filing": "BDC / Form 477 Filing", + # Healthcare / NPI + "npi-revalidation": "Medicare PECOS Revalidation Filing", + "npi-reactivation": "NPI Reactivation", + "nppes-update": "NPPES Data Update / Attestation", + "medicare-enrollment": "Medicare Enrollment (PECOS)", + "oig-sam-screening": "OIG/SAM Exclusion Screening (Annual)", + "provider-compliance-bundle": "Provider Compliance Bundle (Annual)", } # Map the per-filing timestamp columns on telecom_entities → the slugs that diff --git a/scripts/workers/services/__init__.py b/scripts/workers/services/__init__.py index c90f389..1ba8f3c 100644 --- a/scripts/workers/services/__init__.py +++ b/scripts/workers/services/__init__.py @@ -58,6 +58,15 @@ from .mailbox_setup import MailboxSetupHandler from .carrier_closeout import CarrierCloseoutHandler # PHMSA hazmat registration (admin-assisted, 49 CFR Part 107) from .hazmat_phmsa import HazmatPHMSAHandler +# Healthcare / NPI provider compliance (review-staged PECOS/NPPES filings) +from .npi_provider import ( + NPIRevalidationHandler, + NPIReactivationHandler, + NPPESUpdateHandler, + MedicareEnrollmentHandler, + OIGSAMScreeningHandler, + ProviderComplianceBundleHandler, +) SERVICE_HANDLERS: dict[str, type] = { "flsa-audit": FLSAAuditHandler, @@ -141,6 +150,13 @@ SERVICE_HANDLERS: dict[str, type] = { "hazmat-phmsa": HazmatPHMSAHandler, # ── State emissions / clean-truck (admin-assisted) ──────────────── "state-emissions": StateTruckingHandler, + # ── Healthcare / NPI (review-staged) ────────────────────────────── + "npi-revalidation": NPIRevalidationHandler, + "npi-reactivation": NPIReactivationHandler, + "nppes-update": NPPESUpdateHandler, + "medicare-enrollment": MedicareEnrollmentHandler, + "oig-sam-screening": OIGSAMScreeningHandler, + "provider-compliance-bundle": ProviderComplianceBundleHandler, } # Service slugs that operate on a telecom entity — used by job_server.py diff --git a/scripts/workers/services/npi_provider.py b/scripts/workers/services/npi_provider.py new file mode 100644 index 0000000..71f99c1 --- /dev/null +++ b/scripts/workers/services/npi_provider.py @@ -0,0 +1,184 @@ +"""Healthcare / NPI provider compliance handlers. + +Review-staged: each NPI service generates an admin todo with the provider's +NPI + intake details for a human to file in CMS PECOS / NPPES. This mirrors the +FCC auto-filing-off safety default — no automated submission to government +portals until the Playwright flows are proven. + +Covers slugs: + npi-revalidation Medicare PECOS revalidation (5-yr cycle) + npi-reactivation reactivate a deactivated NPI + nppes-update NPPES data update / attestation + medicare-enrollment new Medicare enrollment via PECOS + oig-sam-screening OIG LEIE + SAM exclusion screening (annual) + provider-compliance-bundle revalidation watch + screening + NPPES upkeep + +Intake data needed (collected by the npi-intake wizard step): + - npi provider's 10-digit NPI + - provider_name legal/provider name + - email contact email + - pecos_enrollment_id (optional) PECOS enrollment ID + - specialty (optional) taxonomy/specialty + - practice_state (optional) practice location state +""" + +from __future__ import annotations + +import json +import logging +import os + +LOG = logging.getLogger("workers.services.npi_provider") + +# Per-slug admin todo metadata: human-readable action + the portal a human uses. +_SLUG_META = { + "npi-revalidation": { + "name": "Medicare PECOS Revalidation Filing", + "portal": "https://pecos.cms.hhs.gov/", + "action": ( + "File the Medicare revalidation in PECOS for this provider. Confirm " + "the revalidation due date on the CMS revalidation list, update the " + "enrollment record, and submit. Capture the PECOS tracking ID." + ), + "priority": "high", + }, + "npi-reactivation": { + "name": "NPI Reactivation", + "portal": "https://nppes.cms.hhs.gov/", + "action": ( + "Reactivate the deactivated NPI in NPPES. Verify the deactivation " + "reason, correct any stale data, and re-certify the record." + ), + "priority": "high", + }, + "nppes-update": { + "name": "NPPES Data Update / Attestation", + "portal": "https://nppes.cms.hhs.gov/", + "action": ( + "Update + re-attest the provider's NPPES record (CMS requires " + "updates within 30 days of any change). Apply the requested field " + "changes and certify." + ), + "priority": "normal", + }, + "medicare-enrollment": { + "name": "Medicare Enrollment (PECOS)", + "portal": "https://pecos.cms.hhs.gov/", + "action": ( + "Complete the provider's Medicare enrollment in PECOS (CMS-855). " + "Confirm taxonomy, practice location, and authorized official." + ), + "priority": "high", + }, + "oig-sam-screening": { + "name": "OIG/SAM Exclusion Screening (Annual)", + "portal": "https://oig.hhs.gov/exclusions/ + https://sam.gov/", + "action": ( + "Run the provider (and any listed staff) against the OIG LEIE and " + "SAM exclusion lists. Produce the screening certificate and flag any " + "matches for escalation." + ), + "priority": "normal", + }, + "provider-compliance-bundle": { + "name": "Provider Compliance Bundle (Annual)", + "portal": "https://pecos.cms.hhs.gov/ + https://nppes.cms.hhs.gov/", + "action": ( + "Onboard the provider into the annual compliance bundle: enroll in " + "revalidation watch, run OIG/SAM screening, and refresh the NPPES " + "record. Set the next revalidation reminder." + ), + "priority": "high", + }, +} + + +class _BaseNPIHandler: + """Shared review-staged behaviour for all NPI services.""" + + SERVICE_SLUG = "" + + async def process(self, order_data: dict) -> list[str]: + order_number = order_data.get("order_number", order_data.get("name", "")) + return await self.handle(order_data, order_number) + + async def handle(self, order_data: dict, order_number: str) -> list[str]: + meta = _SLUG_META[self.SERVICE_SLUG] + LOG.info("[%s] Processing %s", order_number, self.SERVICE_SLUG) + + intake = order_data.get("intake_data") or {} + if isinstance(intake, str): + intake = json.loads(intake) + + npi = intake.get("npi", "N/A") + provider = intake.get("provider_name", order_data.get("customer_name", "Unknown")) + specialty = intake.get("specialty", "") + practice_state = intake.get("practice_state", "") + pecos_id = intake.get("pecos_enrollment_id", "") + + description = ( + f"{meta['action']}\n\n" + f"Provider: {provider}\n" + f"NPI: {npi}\n" + f"PECOS Enrollment ID: {pecos_id or 'not provided'}\n" + f"Specialty: {specialty or 'not provided'}\n" + f"Practice state: {practice_state or 'not provided'}\n" + f"Portal: {meta['portal']}\n\n" + f"Review-staged: file manually, then mark this order complete." + ) + + self._create_todo( + order_number, + intake, + title=f"{meta['name']} — {provider} (NPI {npi})", + description=description, + priority=meta["priority"], + ) + return [] + + def _create_todo(self, order_number, intake, title, description, priority="normal"): + try: + import psycopg2 + + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + with conn.cursor() as cur: + cur.execute( + """ + INSERT INTO admin_todos ( + title, category, priority, order_number, service_slug, + description, data, status + ) VALUES (%s, %s, %s, %s, %s, %s, %s, 'pending') + """, + ( + title, "filing", priority, order_number, + self.SERVICE_SLUG, description, json.dumps(intake), + ), + ) + conn.commit() + conn.close() + except Exception as exc: + LOG.error("[%s] Failed to create NPI todo: %s", order_number, exc) + + +class NPIRevalidationHandler(_BaseNPIHandler): + SERVICE_SLUG = "npi-revalidation" + + +class NPIReactivationHandler(_BaseNPIHandler): + SERVICE_SLUG = "npi-reactivation" + + +class NPPESUpdateHandler(_BaseNPIHandler): + SERVICE_SLUG = "nppes-update" + + +class MedicareEnrollmentHandler(_BaseNPIHandler): + SERVICE_SLUG = "medicare-enrollment" + + +class OIGSAMScreeningHandler(_BaseNPIHandler): + SERVICE_SLUG = "oig-sam-screening" + + +class ProviderComplianceBundleHandler(_BaseNPIHandler): + SERVICE_SLUG = "provider-compliance-bundle" diff --git a/site/src/lib/intake_manifest.ts b/site/src/lib/intake_manifest.ts index b04040e..2cb7a08 100644 --- a/site/src/lib/intake_manifest.ts +++ b/site/src/lib/intake_manifest.ts @@ -38,6 +38,7 @@ export type IntakeStep = | "mcs150" // MCS-150 biennial update form fields (standalone) | "dot-intake" // Unified DOT intake — shows sections based on services ordered | "state-trucking" // State-level trucking + hazmat/emissions intake (slug-gated sections) + | "npi-intake" // Healthcare / NPI provider intake (NPI, PECOS, practice) | "review" | "payment"; @@ -143,6 +144,14 @@ export const INTAKE_MANIFEST: Record = { // ── Entity / Corporate Upgrade ───────────────────────────────────── "entity-upgrade-bundle": ["dot-intake", "review", "payment"], + + // ── Healthcare / NPI ─────────────────────────────────────────────── + "npi-revalidation": ["npi-intake", "review", "payment"], + "npi-reactivation": ["npi-intake", "review", "payment"], + "nppes-update": ["npi-intake", "review", "payment"], + "medicare-enrollment": ["npi-intake", "review", "payment"], + "oig-sam-screening": ["npi-intake", "review", "payment"], + "provider-compliance-bundle": ["npi-intake", "review", "payment"], }; // Category-gated dynamic steps. The Wizard inserts these after the `category` @@ -204,6 +213,13 @@ export const SERVICE_META: Record