claimrevolution / oe-module-claimrev-connect
OpenEMR Custom Module Claim Revolution, LLC Connector
Package info
github.com/claimrevolution/oe-module-claimrev-connect
Type:openemr-module
pkg:composer/claimrevolution/oe-module-claimrev-connect
Requires
- php: >=8.2
- guzzlehttp/guzzle: ^7.5
- nyholm/psr7: ^1.4
- openemr/oe-module-installer-plugin: ^0.1.0
- symfony/event-dispatcher: ^4.4 || ^5.0 || ^6.0 || ^7.0
Requires (Dev)
- phpunit/phpunit: ^11.0
- psr/log: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-26 21:21:15 UTC
README
Connects OpenEMR to the Claim Revolution, LLC clearinghouse and adds revenue-cycle dashboards on top of the data the clearinghouse returns. Claim and ERA exchange runs over the ClaimRev REST API; eligibility, reconciliation, denial analytics, AR aging, and a claim status dashboard run on the local copy of that data plus OpenEMR's billing tables.
Features
Billing flow
| Page | What it does |
|---|---|
public/x12Tracker.php |
Tracks claim files sent to ClaimRev (status, retry). |
public/era.php |
Lists ERA / 835 files received from ClaimRev with download. |
public/payment_advice.php |
ERA-driven payment advice search + posting to OpenEMR's ar_session / ar_activity. Single-row + batch post supported. |
public/claims.php |
Claim search against the ClaimRev portal with detail expansion, sort, CSV export, requeue, mark-worked, and editor links. |
public/claim_status.php |
Work queue: claims needing attention, with timeline of every status event we've seen. |
public/reconciliation.php |
Side-by-side OpenEMR claim status vs ClaimRev status with discrepancy classification. |
Eligibility
| Page | What it does |
|---|---|
public/appointments.php |
Upcoming appointments with their primary-insurance eligibility status and "check now" / queue actions. |
| Eligibility card on patient chart | Per-product (Eligibility / Coverage Discovery / Demographics / MBI Finder) results with AI chat over the saved response. |
| Calendar indicators | Color codes events on the OpenEMR calendar by eligibility status — green / red / yellow / grey. Off by default. |
| Eligibility sweep | Background service that proactively queues eligibility checks for the next N days of appointments on configured weekdays. |
Reporting / dashboards
| Page | What it does |
|---|---|
public/aging_report.php |
AR aging by payer with current / 30 / 60 / 90 / 120 / 120+ buckets and CSV export. |
public/denial_analytics.php |
Denial rollups by reason code, by payer, by month with date filters. |
public/recoupment_report.php |
Identifies negative ERA adjustments (recoupments / takebacks). |
public/patient_balance.php |
Patient-responsibility queue: encounters with outstanding balance after insurance closed, with statement tracking. |
| Dashboard KPIs | DashboardService exposes claim throughput, AR, denial, collection, and patient-AR metrics for the home tile. |
Configuration
All settings live under Admin → Globals → ClaimRev Connect. Names are
the oe_claimrev_* keys defined in src/GlobalConfig.php.
Required to function
| Setting | Notes |
|---|---|
oe_claimrev_config_environment |
P for production (default), D for a custom identity provider (Entra ID External, Zitadel, …). |
oe_claimrev_config_clientid |
OAuth client ID. Available in the ClaimRev Portal under Client Connect. |
oe_claimrev_config_clientsecret |
OAuth client secret. Encrypted at rest. Contact ClaimRev support for this value. |
Without these three set, GlobalConfig::isConfigured() returns false and
the module skips event registration entirely (no menu item, no eligibility
events, no posting endpoints) — pages that try to load show
ModuleNotConfiguredException.
Optional
| Setting | What it does |
|---|---|
oe_claimrev_x12_partner_name |
X12 partner record name. Default ClaimRev. Used by ClaimRevModuleSetup::createPartnerRecord. |
oe_claimrev_config_service_type_codes |
Comma-separated 270 service-type codes. Empty asks for all benefits. |
oe_claimrev_benefit_code_filter |
Comma-separated benefit codes (1, 6, A, B, C, …). Filters the eligibility detail view locally; not sent to ClaimRev. |
oe_claimrev_config_auto_send_claim_files |
Auto-send X12 claim files. When off, files queue but are sent manually. |
oe_claimrev_config_add_menu_button |
Add the module's top-nav menu item (re-login required). |
oe_claimrev_config_add_eligibility_card |
Add the eligibility card to the patient dashboard. |
oe_claimrev_config_use_facility_for_eligibility |
Use the facility (rather than the appointment provider) as the 270 information receiver. |
oe_claimrev_enable_rte |
Real-time eligibility — kick off a check when an appointment is created. |
oe_claimrev_eligibility_results_age |
Days before a stored eligibility result is considered stale. Used by the sweep + calendar indicators. |
oe_claimrev_send_eligibility |
Master switch for the background eligibility send service. |
oe_claimrev_enable_watchdog |
Auto-resets ClaimRev background services that stay in running=1 for >10 minutes (PHP crash, OOM kill). Default on. |
oe_claimrev_enable_notifications |
Poll ClaimRev for portal notifications and deliver them as OpenEMR pnotes. Default on. |
oe_claimrev_notification_recipient |
Semicolon-separated OpenEMR usernames to receive notifications. Default admin. |
oe_claimrev_enable_test_mode |
Switches every test-mode-capable page (Payment Advice, ERA, Reconciliation, Eligibility) to mock data generated from local billing rows. ERA Download returns a 0-byte placeholder; Reconciliation row actions short-circuit to fake-success. |
oe_claimrev_enable_sweep |
Master switch for the eligibility sweep background service. |
oe_claimrev_sweep_days |
Comma-separated day-of-week numbers (0 = Sunday … 6 = Saturday). Default 1,4 (Mon + Thu). |
oe_claimrev_sweep_lookahead |
Days ahead to sweep. Default 7. |
oe_claimrev_enable_calendar_indicators |
Color-code OpenEMR calendar events by eligibility status. May impact calendar performance on busy schedules. |
Identity-provider overrides
Auto-configured for production. Only override when pointing at a custom identity provider:
oe_claimrev_config_portal_url, oe_claimrev_config_dev_api_url,
oe_claimrev_config_dev_scope, oe_claimrev_config_dev_authority.
Background services
Registered in background_services by table.sql and re-enabled on
module enable via ModuleManagerListener::enable. Each one has a
@phpstan-ignore openemr.noGlobalNsFunctions shim that delegates to
a namespaced service class so the cron table can address it by name.
| Service | Function | Cadence | Delegate |
|---|---|---|---|
ClaimRev_Send |
start_X12_Claimrev_send_files |
1 minute | ClaimUpload::sendWaitingFiles |
ClaimRev_Receive |
start_X12_Claimrev_get_reports |
240 minutes | ReportDownload::getWaitingFiles |
ClaimRev_Elig_Send_Receive |
start_send_eligibility |
1 minute | EligibilityTransfer::sendWaitingEligibility |
ClaimRev_Notifications |
start_claimrev_notifications |
60 minutes | NotificationPollService::run |
ClaimRev_Watchdog |
start_claimrev_watchdog |
20 minutes | ClaimRevModuleSetup::resetStuckServices |
ClaimRev_Elig_Sweep |
start_eligibility_sweep |
1440 minutes (daily) | EligibilitySweepService::run |
The watchdog only resets services other than itself; otherwise a watchdog run that exceeds 10 minutes would clear its own running flag mid-execution.
Database tables
All created by table.sql. Migrations run once on enable via
ClaimRevModuleSetup::runMigrations, which understands the standard
OpenEMR #IfNotRow / #IfNotColumnType / #IfNotTable / #EndIf directives.
| Table | Purpose |
|---|---|
mod_claimrev_eligibility |
One row per (patient, payer responsibility, request) eligibility check + JSON result. |
mod_claimrev_notifications |
Tracks which portal notifications have already been delivered as pnotes. |
mod_claimrev_claims |
Per-claim mirror of ClaimRev status (object id, status name/id, ERA classification, paid amount, ar_session_id, last sync). |
mod_claimrev_claim_events |
Append-only event log per claim (submitted, rejected, accepted, denied, status_check_276, era_received, payment_posted, requeued, corrected, manual_note, claimrev_sync). |
mod_claimrev_patient_statements |
Statement history for the patient balance queue (date, method, amount, status, notes). |
Development
Code conventions
This module follows the OpenEMR developer conventions documented in the
top-level CLAUDE.md. In particular:
- Every PHP file starts with
declare(strict_types=1). - Superglobal access goes through
ModuleInput(which routes throughfilter_inputso theopenemr.forbiddenRequestGlobalsPHPStan rule passes). - Mixed cells from
QueryUtils::fetchRecordsare narrowed withTypeCoerce::asString / asInt / asFloat / asBool / asNullableIntrather than bare casts ((int) $mixedis rejected at PHPStan level 10). catch (\Throwable)andcatch (\Exception)are forbidden byopenemr.forbiddenCatchType. Usecatch (\RuntimeException | \LogicException)instead.ClaimRevExceptionextendsRuntimeExceptionso this still catches our own throws.- Database calls go through
QueryUtils; legacysqlStatement/sqlInsert/sqlQuery/sqlStatementNoLogare blocked byopenemr.deprecatedSqlFunction. - Error reporting uses the PSR-3 logger via
ServiceContainer::getLogger, noterror_log().
Tests
Pure helpers have isolated test coverage in
tests/Tests/Isolated/Modules/ClaimRevConnector/:
TypeCoerceTest asString / asInt / asFloat / asBool / asNullableInt
ValueMappingTest mapPayerResponsibility (case-insensitive primary/secondary/tertiary → p/s/t)
AgingReportServiceTest toCsv with RFC 4180 escape
ClaimTrackingServiceTest parsePcn (mirrors PaymentAdvicePostingService)
PaymentAdvicePostingServiceTest buildIdempotencyReference, parsePatientControlNumber, getClaimStatusLabel, sumServiceAmounts
ReconciliationServiceTest computeDiscrepancy classifier
Run them from the repo root:
composer phpunit-isolated -- --filter ClaimRevConnector
DB-bound and API-bound code paths (the reconcile() / searchClaims() /
post() entry points) need a real OpenEMR install; they are exercised
by manual QA through the UI.
Static analysis
composer phpstan runs at level 10. Custom rules in tests/PHPStan/Rules/
enforce the conventions above. Avoid adding new baseline entries — fix
the underlying type error.
composer rector-check keeps the module on modern PHP idioms; run
composer rector-fix to apply suggestions.
Branches and version support
There is one maintained line, main. A single build covers OpenEMR 7.x
and 8.x: the src/Compat/ shim layer activates per host, and CsrfHelper
detects the 7.x versus 8.x CsrfUtils::collectCsrfToken signature at
runtime, so the same binary works on both.
Older cores are supported by pinning a release, not by a branch:
composer require claimrevolution/oe-module-claimrev-connect:2.1.7
Every tag stays on Packagist, so a pin costs nothing to maintain — unlike a parallel branch, which has to be kept in sync by hand and drifts the moment someone forgets.
Which version to pin is documented in the compatibility matrix in the OpenEMR manual. The short version: use the latest release on any OpenEMR 8.x, because v2.1.6 and earlier fatal on the 8.0.x patch line.
Troubleshooting
ModuleNotConfiguredException on every module page
The required globals (oe_claimrev_config_clientid,
oe_claimrev_config_clientsecret, oe_claimrev_config_environment)
aren't set. Go to Admin → Globals → ClaimRev Connect, fill the
three values, save. Re-login (the module's menu item only renders
after a fresh login).
Background services stuck in running=1
The watchdog should reset stuck services every 20 minutes. To reset manually:
UPDATE background_services SET running = 0 WHERE running = 1 AND name LIKE '%ClaimRev%' AND name != 'ClaimRev_Watchdog';
The watchdog excludes itself for the reason noted above.
Wrong module version for the OpenEMR version
There is no separate build per core — one release covers 7.x and 8.x — but
the version still matters. On the OpenEMR 8.0.x patch line, v2.1.6 and
earlier fatal with Call to undefined method OEGlobalsBag::getKernel(),
because 8.0.x ships an OEGlobalsBag without that method while the
flex/master line has it. The visible symptoms are blank module pages, or
the Patient Portal / API panel vanishing from the patient dashboard.
Upgrading to v2.1.7 or later resolves it. See the compatibility matrix in the OpenEMR manual.
"Already posted" warnings on a payment advice that wasn't
PaymentAdvicePostingService::isAlreadyPosted keys off the
ar_session.reference value, which uses a fixed
ClaimRev-{paymentAdviceId} prefix
(PaymentAdvicePostingService::REFERENCE_PREFIX). If the prefix or the
paymentAdviceId changes between runs, the dedup check stops matching
and the same advice can post twice. The shape is regression-tested
in PaymentAdvicePostingServiceTest::testReferencePrefixHasExpectedShape.
License
GPL v3 — see LICENSE and the
OpenEMR project license.
Contributing
Open an issue or send a pull request against
claimrevolution/openemr_fork
or the upstream module path
interface/modules/custom_modules/oe-module-claimrev-connect
in the main OpenEMR repo.