feat(healthcare): OIG/SAM exclusion screening as $79/mo Stripe Subscription
Convert OIG/SAM from one-time $299/yr to recurring $79/month (card+ACH only) - the first real recurring-billing product in the system. Exclusion screening is a *monthly* federal obligation, so recurring monitoring fits the requirement and is the biggest valuation lever (vs a one-time annual run). Catalog (single source of truth): - service-catalog.ts: add billing_interval + allowed_methods to ComplianceService; oig-sam-screening -> 7900c, billing_interval:"month", allowed_methods:[card,ach], name "(Monthly Monitoring)". - gen-service-catalog.py + check-service-catalog-drift.py: carry/guard the two new fields; regenerate site catalog. Checkout (api/src/routes/checkout.ts): - mode:"subscription" with recurring price_data when billing_interval is set; surcharge absorbed for recurring (clean $79/mo); server-side METHOD_NOT_ALLOWED re-validation against allowed_methods. - ensureColumns + migration 100: compliance_orders.stripe_subscription_id, bundle_upsell_sent_at (+ subscription index). Webhooks (api/src/routes/webhooks.ts): - record stripe_subscription_id on checkout.session.completed (subscription mode). - invoice.paid (subscription_cycle only) -> re-dispatch screening for the cycle; invoice.payment_failed -> admin alert + first-failure customer nudge; customer.subscription.deleted -> mark order cancelled. (API 2026-03-25 moved the subscription link to invoice.parent.subscription_details.subscription.) Fulfillment: - job_server.py: pass recurring_cycle/invoice_id into the order. - npi_provider.py: OIG handler labels renewal cycles "[Monthly cycle]" + re-screen note; bundle action runs only the FIRST screening + flags the $79/mo upsell. Bundle land-and-expand: - Provider Compliance Bundle now includes only the first OIG/SAM screening (was giving away $948/yr of monitoring inside an $899 bundle). - new worker scripts/workers/bundle_upsell.py (+ pw-bundle-upsell timer): ~3 weeks after a paid bundle, emails the customer to continue $79/mo monitoring; dedup via bundle_upsell_sent_at; skips customers who already have an OIG/SAM order. Surfaces updated to $79/mo: PaymentStep (filters methods, "Billed every month, cancel anytime"), order pages, healthcare index, npi-compliance-check tool (also fixed stale $699 bundle drift -> $899), hc_oig_screening + hc_compliance_bundle emails. Docs: billing.md gains a "Stripe-native Subscriptions" section + a reality-check banner (Adyen/ERPNext-gateway model documented there is NOT live; Stripe is the real rail). Fixed run-migrations.yml container name bug (performancewest-postgres-1 -> performancewest-api-postgres-1, overridable). Tests: api/tests/recurring-subscription.test.ts (28 assertions) covers catalog gating, method validation, surcharge suppression, recurring line-item build, invoiceSubscriptionId extraction, renewal-cycle gating. tsc clean; site build clean; catalog drift OK. Manual deploy step: enable invoice.paid, invoice.payment_failed, customer.subscription.deleted on the Stripe webhook endpoint.
This commit is contained in:
parent
f481a1d13c
commit
cf021e2f91
21 changed files with 820 additions and 69 deletions
|
|
@ -35,9 +35,15 @@ def parse_generated(ts: str) -> dict:
|
|||
name_m = re.search(r'name:\s*"((?:[^"\\]|\\.)*)"', inner)
|
||||
price_m = re.search(r"price_cents:\s*(\d+)", inner)
|
||||
gov_m = re.search(r'gov_fee_label:\s*"((?:[^"\\]|\\.)*)"', inner)
|
||||
interval_m = re.search(r'billing_interval:\s*"(month|year)"', inner)
|
||||
methods_m = re.search(r"allowed_methods:\s*\[([^\]]*)\]", inner)
|
||||
entry = {"name": gen._unescape(name_m.group(1)), "price_cents": int(price_m.group(1))}
|
||||
if gov_m:
|
||||
entry["gov_fee_label"] = gen._unescape(gov_m.group(1))
|
||||
if interval_m:
|
||||
entry["billing_interval"] = interval_m.group(1)
|
||||
if methods_m:
|
||||
entry["allowed_methods"] = re.findall(r'"([a-z]+)"', methods_m.group(1))
|
||||
out[slug] = entry
|
||||
return out
|
||||
|
||||
|
|
@ -57,6 +63,10 @@ def main() -> int:
|
|||
problems.append(f"{slug}: name mismatch")
|
||||
if a.get("gov_fee_label") != g.get("gov_fee_label"):
|
||||
problems.append(f"{slug}: gov_fee_label mismatch")
|
||||
if a.get("billing_interval") != g.get("billing_interval"):
|
||||
problems.append(f"{slug}: billing_interval API={a.get('billing_interval')} generated={g.get('billing_interval')}")
|
||||
if a.get("allowed_methods") != g.get("allowed_methods"):
|
||||
problems.append(f"{slug}: allowed_methods API={a.get('allowed_methods')} generated={g.get('allowed_methods')}")
|
||||
for slug in have:
|
||||
if slug not in api:
|
||||
problems.append(f"{slug}: in generated file but not in API")
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ def parse_catalog(ts: str) -> dict:
|
|||
name_m = re.search(r'name:\s*"((?:[^"\\]|\\.)*)"', inner)
|
||||
price_m = re.search(r"price_cents:\s*(\d+)", inner)
|
||||
gov_m = re.search(r'gov_fee_label:\s*"((?:[^"\\]|\\.)*)"', inner)
|
||||
interval_m = re.search(r'billing_interval:\s*"(month|year)"', inner)
|
||||
methods_m = re.search(r"allowed_methods:\s*\[([^\]]*)\]", inner)
|
||||
if not name_m or not price_m:
|
||||
continue
|
||||
entry = {
|
||||
|
|
@ -48,6 +50,10 @@ def parse_catalog(ts: str) -> dict:
|
|||
}
|
||||
if gov_m:
|
||||
entry["gov_fee_label"] = _unescape(gov_m.group(1))
|
||||
if interval_m:
|
||||
entry["billing_interval"] = interval_m.group(1)
|
||||
if methods_m:
|
||||
entry["allowed_methods"] = re.findall(r'"([a-z]+)"', methods_m.group(1))
|
||||
out[slug] = entry
|
||||
return out
|
||||
|
||||
|
|
@ -59,6 +65,10 @@ def render(catalog: dict) -> str:
|
|||
parts = [f"name: {json.dumps(s["name"], ensure_ascii=False)}", f"price_cents: {s['price_cents']}"]
|
||||
if s.get("gov_fee_label"):
|
||||
parts.append(f"gov_fee_label: {json.dumps(s["gov_fee_label"], ensure_ascii=False)}")
|
||||
if s.get("billing_interval"):
|
||||
parts.append(f"billing_interval: {json.dumps(s['billing_interval'], ensure_ascii=False)}")
|
||||
if s.get("allowed_methods"):
|
||||
parts.append(f"allowed_methods: {json.dumps(s['allowed_methods'], ensure_ascii=False)}")
|
||||
lines.append(f" {json.dumps(slug, ensure_ascii=False)}: {{ {', '.join(parts)} }},")
|
||||
return (
|
||||
"/**\n"
|
||||
|
|
@ -74,6 +84,8 @@ def render(catalog: dict) -> str:
|
|||
" name: string;\n"
|
||||
" price_cents: number;\n"
|
||||
" gov_fee_label?: string;\n"
|
||||
' billing_interval?: "month" | "year";\n'
|
||||
' allowed_methods?: ("card" | "ach" | "paypal" | "klarna" | "crypto")[];\n'
|
||||
"}\n\n"
|
||||
"export const SERVICE_META: Record<string, ServiceMeta> = {\n"
|
||||
+ "\n".join(lines)
|
||||
|
|
|
|||
206
scripts/workers/bundle_upsell.py
Normal file
206
scripts/workers/bundle_upsell.py
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
"""
|
||||
Bundle -> monthly-monitoring upsell worker.
|
||||
|
||||
The Provider Compliance Bundle ($899/yr) includes the customer's FIRST OIG/SAM
|
||||
exclusion screening. Federal exclusion screening is a *monthly* obligation, so
|
||||
after that first screening we invite the customer to continue with standalone
|
||||
OIG/SAM monitoring ($79/month) -- the recurring product. This worker finds paid
|
||||
bundle orders whose first cycle is done and sends a one-time upsell email with a
|
||||
direct link to the recurring checkout.
|
||||
|
||||
Schedule: daily (systemd timer pw-bundle-upsell). Each order is emailed at most
|
||||
once (tracked by compliance_orders.bundle_upsell_sent_at).
|
||||
|
||||
Window: send roughly 3-4 weeks after the bundle was paid -- long enough that the
|
||||
first screening/certificate has been delivered, soon enough to convert before
|
||||
the next monthly obligation lapses.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
import psycopg2
|
||||
|
||||
LOG = logging.getLogger("workers.bundle_upsell")
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s %(message)s")
|
||||
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://pw:pw@localhost:5432/performancewest")
|
||||
DOMAIN = os.getenv("DOMAIN", "performancewest.net")
|
||||
|
||||
SMTP_HOST = os.getenv("SMTP_HOST", "co.carrierone.com")
|
||||
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
|
||||
SMTP_USER = os.getenv("SMTP_USER", "noreply@performancewest.net")
|
||||
SMTP_PASS = os.getenv("SMTP_PASS", "")
|
||||
SMTP_FROM = os.getenv("SMTP_FROM", "Performance West <noreply@performancewest.net>")
|
||||
|
||||
BUNDLE_SLUG = "provider-compliance-bundle"
|
||||
|
||||
# How long after the bundle is paid before we send the monitoring upsell. Lower
|
||||
# bound gives the first screening + certificate time to be delivered; upper bound
|
||||
# stops us emailing ancient orders when the worker is first switched on.
|
||||
UPSELL_AFTER = timedelta(days=21)
|
||||
UPSELL_BEFORE = timedelta(days=120)
|
||||
|
||||
# Override knob for testing / disabling.
|
||||
UPSELL_ENABLED = os.getenv("BUNDLE_UPSELL_ENABLED", "1") == "1"
|
||||
|
||||
|
||||
def build_email_html(customer_name: str, npi: str, order_url: str) -> str:
|
||||
npi_line = (
|
||||
f'<p style="margin:0 0 14px;font-size:13px;color:#475569;">For NPI '
|
||||
f'<strong>{npi}</strong>.</p>'
|
||||
if npi else ""
|
||||
)
|
||||
return f"""<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
|
||||
<body style="margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#eef0f3;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#eef0f3;padding:32px 16px;"><tr><td align="center">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="max-width:600px;background:#fff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
||||
<tr><td style="background:linear-gradient(135deg,#0f766e 0%,#14b8a6 100%);padding:26px 28px;">
|
||||
<h1 style="color:#fff;margin:0;font-size:21px;font-weight:700;">Keep your exclusion screening current</h1>
|
||||
<p style="color:#ccfbf1;margin:6px 0 0;font-size:13px;">Continue monthly OIG/SAM monitoring</p>
|
||||
</td></tr>
|
||||
<tr><td style="padding:28px;">
|
||||
<p style="margin:0 0 16px;font-size:15px;color:#1f2937;">Hi {customer_name},</p>
|
||||
<p style="margin:0 0 16px;font-size:14px;color:#475569;line-height:1.7;">
|
||||
Your Provider Compliance Bundle included your <strong>first</strong> OIG LEIE
|
||||
and SAM exclusion screening. But exclusion screening is a <strong>monthly</strong>
|
||||
obligation under federal guidance — a one-time check doesn't keep you
|
||||
covered the rest of the year.
|
||||
</p>
|
||||
{npi_line}
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#ecfdf5;border:2px solid #6ee7b7;border-radius:10px;margin:0 0 22px;">
|
||||
<tr><td style="padding:18px;">
|
||||
<p style="margin:0 0 6px;font-size:14px;color:#065f46;font-weight:700;">Ongoing OIG/SAM Exclusion Monitoring</p>
|
||||
<p style="margin:0 0 4px;font-size:13px;color:#065f46;line-height:1.6;">
|
||||
We re-screen you (and listed staff) every month against the current OIG
|
||||
LEIE and SAM lists, and issue a fresh audit-ready certificate each cycle.
|
||||
</p>
|
||||
<p style="margin:8px 0 0;font-size:15px;color:#047857;font-weight:700;">$79 / month — cancel anytime</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
<table width="100%" cellpadding="0" cellspacing="0"><tr><td align="center">
|
||||
<a href="{order_url}" style="display:inline-block;background:#10b981;color:#fff;font-size:15px;font-weight:700;padding:14px 38px;border-radius:8px;text-decoration:none;">
|
||||
Set up monthly monitoring →
|
||||
</a>
|
||||
</td></tr></table>
|
||||
<p style="margin:22px 0 0;font-size:12px;color:#94a3b8;text-align:center;">
|
||||
Questions? Reply to this email or call (888) 411-0383.
|
||||
</p>
|
||||
</td></tr>
|
||||
<tr><td style="padding:16px 28px;background:#f8fafc;border-top:1px solid #e5e7eb;text-align:center;">
|
||||
<p style="margin:0;font-size:11px;color:#9ca3af;">Performance West — healthcare compliance.</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr></table>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
def build_email_text(customer_name: str, npi: str, order_url: str) -> str:
|
||||
npi_line = f"NPI: {npi}\n\n" if npi else ""
|
||||
return (
|
||||
f"Hi {customer_name},\n\n"
|
||||
"Your Provider Compliance Bundle included your FIRST OIG/SAM exclusion "
|
||||
"screening. Exclusion screening is a monthly obligation under federal "
|
||||
"guidance, so a one-time check doesn't keep you covered all year.\n\n"
|
||||
f"{npi_line}"
|
||||
"Continue with ongoing OIG/SAM Exclusion Monitoring: we re-screen you (and "
|
||||
"listed staff) every month and issue a fresh audit-ready certificate each "
|
||||
"cycle. $79/month, cancel anytime.\n\n"
|
||||
f"Set up monthly monitoring: {order_url}\n\n"
|
||||
"Questions? Reply to this email or call (888) 411-0383.\n\n"
|
||||
"Performance West\n"
|
||||
)
|
||||
|
||||
|
||||
def send_upsell_email(to_email: str, customer_name: str, npi: str) -> bool:
|
||||
order_url = f"https://{DOMAIN}/order/oig-sam-screening"
|
||||
if npi:
|
||||
order_url += f"?npi={npi}"
|
||||
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["From"] = SMTP_FROM
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = "Keep your OIG/SAM exclusion screening current ($79/mo)"
|
||||
msg["Reply-To"] = f"support@{DOMAIN}"
|
||||
msg.attach(MIMEText(build_email_text(customer_name, npi, order_url), "plain"))
|
||||
msg.attach(MIMEText(build_email_html(customer_name, npi, order_url), "html"))
|
||||
|
||||
try:
|
||||
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as server:
|
||||
server.ehlo()
|
||||
server.starttls()
|
||||
server.ehlo()
|
||||
server.login(SMTP_USER, SMTP_PASS)
|
||||
server.sendmail(SMTP_USER, [to_email], msg.as_string())
|
||||
LOG.info("Sent bundle->monitoring upsell to %s", to_email)
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.error("Failed to send bundle upsell to %s: %s", to_email, e)
|
||||
return False
|
||||
|
||||
|
||||
def process_upsells():
|
||||
if not UPSELL_ENABLED:
|
||||
LOG.info("Bundle upsell disabled (BUNDLE_UPSELL_ENABLED=0)")
|
||||
return
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
window_start = now - UPSELL_BEFORE
|
||||
window_end = now - UPSELL_AFTER
|
||||
sent = 0
|
||||
|
||||
conn = psycopg2.connect(DATABASE_URL)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
# Paid bundle orders in the upsell window that haven't been upsold yet,
|
||||
# and where the customer doesn't ALREADY have an OIG/SAM order (don't
|
||||
# pitch monitoring to someone who already bought it).
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT b.order_number, b.customer_email, b.customer_name, b.intake_data
|
||||
FROM compliance_orders b
|
||||
WHERE b.service_slug = %s
|
||||
AND b.payment_status = 'paid'
|
||||
AND b.paid_at BETWEEN %s AND %s
|
||||
AND b.bundle_upsell_sent_at IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM compliance_orders o
|
||||
WHERE o.customer_email = b.customer_email
|
||||
AND o.service_slug = 'oig-sam-screening'
|
||||
)
|
||||
LIMIT 100
|
||||
""",
|
||||
(BUNDLE_SLUG, window_start, window_end),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
LOG.info("Bundle upsell: %d candidate(s) in window", len(rows))
|
||||
for order_number, email, name, intake in rows:
|
||||
if not email:
|
||||
continue
|
||||
npi = ""
|
||||
if isinstance(intake, dict):
|
||||
npi = str(intake.get("npi") or "")
|
||||
ok = send_upsell_email(email, name or "there", npi)
|
||||
if ok:
|
||||
cur.execute(
|
||||
"UPDATE compliance_orders SET bundle_upsell_sent_at = %s WHERE order_number = %s",
|
||||
(now, order_number),
|
||||
)
|
||||
conn.commit()
|
||||
sent += 1
|
||||
LOG.info("Bundle upsell run complete: %d email(s) sent", sent)
|
||||
except Exception as e:
|
||||
LOG.error("Bundle upsell error: %s", e)
|
||||
conn.rollback()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
process_upsells()
|
||||
|
|
@ -1255,6 +1255,14 @@ def handle_process_compliance_service(payload: dict) -> dict:
|
|||
# verification gate and re-dispatches the order for submission).
|
||||
if payload.get("admin_approved"):
|
||||
order["admin_approved"] = True
|
||||
# Recurring subscription renewal cycle (e.g. OIG/SAM monthly monitoring):
|
||||
# the webhook re-dispatches each cycle to re-run the screening and deliver a
|
||||
# fresh certificate. Flag it so the handler can date/label the new cycle and
|
||||
# delivery treats it as a recurring report rather than a first fulfillment.
|
||||
if payload.get("recurring_cycle"):
|
||||
order["recurring_cycle"] = True
|
||||
if payload.get("invoice_id"):
|
||||
order["recurring_invoice_id"] = payload["invoice_id"]
|
||||
|
||||
# Final entity check before dispatch
|
||||
ent = order.get("entity", {})
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ Covers slugs:
|
|||
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
|
||||
oig-sam-screening OIG LEIE + SAM exclusion screening (monthly)
|
||||
provider-compliance-bundle revalidation watch + first screening + NPPES upkeep
|
||||
|
||||
Intake data needed (collected by the npi-intake wizard step):
|
||||
- npi provider's 10-digit NPI
|
||||
|
|
@ -95,12 +95,13 @@ _SLUG_META = {
|
|||
"priority": "high",
|
||||
},
|
||||
"oig-sam-screening": {
|
||||
"name": "OIG/SAM Exclusion Screening (Annual)",
|
||||
"name": "OIG/SAM Exclusion Screening (Monthly Monitoring)",
|
||||
"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."
|
||||
"matches for escalation. Recurring monthly subscription: each renewal "
|
||||
"cycle re-runs the screening against current data and emails the report."
|
||||
),
|
||||
"access": (
|
||||
"No client access needed - OIG LEIE + SAM.gov are public. Screen by NPI/name, issue certificate."
|
||||
|
|
@ -112,8 +113,9 @@ _SLUG_META = {
|
|||
"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."
|
||||
"revalidation watch, run the FIRST OIG/SAM screening (included), and "
|
||||
"refresh the NPPES record. Set the next revalidation reminder, and flag "
|
||||
"for the $79/month exclusion-monitoring upsell after the first screening."
|
||||
),
|
||||
"access": (
|
||||
"Standard (default): CMS-855 paper filing for the enrollment/revalidation piece, mailed to MAC (daily batch); screening is public (no client action). "
|
||||
|
|
@ -200,8 +202,26 @@ class _BaseNPIHandler:
|
|||
"no": "NO — client declined surrogate -> use the STANDARD path (prepare form, e-sign, daily mail batch).",
|
||||
}.get(surrogate, "UNDECIDED — confirm with client; default to STANDARD path if not granted.")
|
||||
|
||||
# Recurring monthly cycle (e.g. OIG/SAM monitoring renewal): the webhook
|
||||
# re-dispatched this order after a renewal charge cleared. Surface it so
|
||||
# the admin re-runs the screening for the new cycle and issues a fresh
|
||||
# dated certificate, rather than treating it as a first fulfillment.
|
||||
recurring = bool(order_data.get("recurring_cycle"))
|
||||
cycle_note = ""
|
||||
title_prefix = ""
|
||||
if recurring:
|
||||
inv = order_data.get("recurring_invoice_id", "")
|
||||
cycle_note = (
|
||||
"\n*** RECURRING MONTHLY CYCLE *** — renewal charge cleared"
|
||||
+ (f" (invoice {inv})" if inv else "")
|
||||
+ ". Re-run the screening against CURRENT OIG LEIE + SAM data and "
|
||||
"issue a NEW dated certificate for this cycle.\n"
|
||||
)
|
||||
title_prefix = "[Monthly cycle] "
|
||||
|
||||
description = (
|
||||
f"{meta['action']}\n\n"
|
||||
cycle_note
|
||||
+ f"{meta['action']}\n\n"
|
||||
f"Provider: {provider}\n"
|
||||
f"NPI: {npi}\n"
|
||||
f"PECOS Enrollment ID: {pecos_id or 'not provided'}\n"
|
||||
|
|
@ -220,7 +240,7 @@ class _BaseNPIHandler:
|
|||
self._create_todo(
|
||||
order_number,
|
||||
intake,
|
||||
title=f"{meta['name']} — {provider} (NPI {npi})",
|
||||
title=f"{title_prefix}{meta['name']} — {provider} (NPI {npi})",
|
||||
description=description,
|
||||
priority=meta["priority"],
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue