Skip to main content

Payer contract registry

Outcome

Every contract across every payer is visible in one place; expiring contracts surface ahead of deadlines via daily-email subscriptions.

Prerequisites

What /admin/contracts shows

CapabilityNotes
List every billing.payer_contract row in the active tenantAcross every payer in one place
FilterBy payer / billing entity / status / 30/60/90-day expiring-soon window
Side drawerLinked fee schedules (fee_schedule.contract_id)
Edit in placePATCH on /payers/:id/contracts/:contractId

Routes

  • GET /api/v1/admin/contracts — joined list with filters.
  • GET /api/v1/admin/contracts/:contractId — single enriched row.
  • GET /api/v1/admin/contracts/:contractId/fee-schedules — linked schedules.

Expiry alerts

Tenants can subscribe to a daily email when contracts in a configured scope are within a 30/60/90-day lead window. Each subscription is a row in billing.contract_expiry_alert (tenant migration 114).

The dispatcher fires once per CONTRACT_EXPIRY_INTERVAL_MS (default 60s, range 15s–30min). Each tick:

  1. Pulls active tenants from master.
  2. Resolves each tenant's Knex via TenantConnectionResolver.
  3. Calls listDue(now) — excludes alerts that already fired today (calendar-day idempotency).
  4. For each due alert, resolves matching contracts. Empty match-set stamps last_status='NO_CONTRACTS' and does not email; non-empty sends via SMTP.

Operator playbook — "a contract is expiring soon"

  1. Open /admin/contracts. The header banner shows how many active contracts expire within 30 days. Click Show expiring to filter.

  2. Open the row drawer. Click Edit contract to open the same dialog as payer + program config (now in edit mode); update effective_to and save.

  3. (Optional) Set up an automated email subscription. Switch to the Expiry alerts tab → New alert → choose scope + lead time + recipients (max 10).

Operator playbook — "an expiry alert is failing"

  1. Open /admin/contracts/expiry-alerts. The Status column shows the most recent attempt:

    StatusMeaning
    Sent (SUCCESS)Last fire delivered the email.
    Failed (FAILED)See last_error in DB; SMTP outage or mis-typed recipient.
    No matches (NO_CONTRACTS)Alert ran but the scope had no expiring contracts. Normal — alerts stay quiet during normal operation.
  2. To replay an alert immediately without waiting for the next tick, open the row, click Edit, then Send test. The test path does not stamp last_fired_at, so the next scheduled tick still fires.

  3. To pause an alert without losing its scope, edit and uncheck Active.

Break-glass — disable the scheduler

Set CONTRACT_EXPIRY_ALERTS_ENABLED=false and restart rcm-core. The CRUD endpoints stay available (operators can still edit subscriptions); only the dispatcher loop is suspended.

Schema reference

Tenant migration 114 creates billing.contract_expiry_alert:

ColumnTypeNotes
alert_iduuid PK
payer_iduuid NULLNULL = all payers
billing_entity_iduuid NULLNULL = all billing entities
lead_daysintCHECK IN (30, 60, 90)
recipientsjsonbstring[], 1–10 entries
subjectvarchar(200)
activeboolean
last_fired_attimestamptz NULLlast calendar day the loop sent
last_statusvarchar(20) NULLSUCCESS / FAILED / NO_CONTRACTS
last_errortext NULL

A partial-unique index keeps the (scope, lead_days) tuple unique even when scope columns are NULL.

Validation

CheckExpected
Expiring banner reflects real contract countYes
Send test from alert rowEmail arrives; last_fired_at not stamped
Active togglePauses without delete

Troubleshooting

SymptomCauseFix
Status Failed repeatedlySMTP host/auth wrongUpdate scheduled email env vars.
Status No matches alwaysScope too narrowWiden scope (payer_id=NULL, billing_entity_id=NULL) or extend lead_days.
Alert never fires after subscriptionCONTRACT_EXPIRY_ALERTS_ENABLED=falseRe-enable + restart.

Cross-references

Next

4.10 — State rate code reference