Skip to content

Commit

Permalink
remit processing corrections
Browse files Browse the repository at this point in the history
  • Loading branch information
sunsetsystems committed Sep 7, 2006
1 parent 8ad119e commit 326a485
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 66 deletions.
73 changes: 29 additions & 44 deletions interface/billing/sl_eob_process.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@

// This processes X12 835 remittances and produces a report.

// Caveats:
// Currently we assume that all 835's come from primary insurance, and
// that secondary claims always go out on paper. This should be made
// more general at some point. So far we have only tested with a
// family practice clinic using Zirmed.

// Buffer all output so we can archive it to a file.
ob_start();

Expand Down Expand Up @@ -153,6 +147,7 @@ function era_callback(&$out) {
if ($csc == '1' || $csc == '19') $inslabel = 'Ins1';
if ($csc == '2' || $csc == '20') $inslabel = 'Ins2';
if ($csc == '3' || $csc == '21') $inslabel = 'Ins3';
$primary = ($inslabel == 'Ins1');
writeMessageLine($bgcolor, 'infdetail',
"Claim status $csc: " . $claim_status_codes[$csc]);

Expand Down Expand Up @@ -200,29 +195,12 @@ function era_callback(&$out) {
$error = true;
}

// Check for duplicate payment. Should not happen.
//
// This is not right. What we want to do is check if we have
// any payments or adjustments from this payer for this service item,
// and produce an error if so. The point is that a duplicated claim
// submission may not give the same results as the original.
/****
foreach ($prev['dtl'] as $dkey => $ddata) {
if (! $ddata['pmt']) continue;
$ddate = parse_date($dkey);
if ($ddate == $check_date && $ddata['pmt'] == $svc['paid']) {
writeMessageLine($bgcolor, 'errdetail',
"This payment dated $check_date seems to be already posted!");
$error = true;
}
}
****/
// The following replaces the above.
// Check for already-existing primary remittance activity.
if ((sprintf("%.2f",$prev['chg']) != sprintf("%.2f",$prev['bal']) ||
$prev['adj'] != 0) && $inslabel == 'Ins1')
$prev['adj'] != 0) && $primary)
{
writeMessageLine($bgcolor, 'errdetail',
"This service item already has payments and/or adjustments!");
"This service item already has primary payments and/or adjustments!");
$error = true;
}

Expand All @@ -232,14 +210,7 @@ function era_callback(&$out) {
// If the service item is not in our database...
else {

/****
writeDetailLine($bgcolor, 'errdetail', $patient_name, $invnumber,
$svc['code'], $service_date, '*** UNMATCHED SERVICE ITEM ***',
$svc['chg'], '');
$error = true;
****/

// No, this is not an error. Instead, if we are not in error mode
// This is not an error. If we are not in error mode and not debugging,
// insert the service item into SL. Then display it (in green if it
// was inserted, or in red if we are in error mode).
$description = 'CPT4:' . $svc['code'] . " Added by $inslabel $production_date";
Expand Down Expand Up @@ -284,29 +255,43 @@ function era_callback(&$out) {
}

// Post and report the payment for this service item from the ERA.
// By the way a 'Claim' level payment is probably going to be negative,
// i.e. a payment reversal.
if ($svc['paid']) {
if (!$error && !$debug) {
slPostPayment($arrow['id'], $svc['paid'], $check_date,
"$inslabel/" . $out['check_number'], $svc['code'], $insurance_id, $debug);
$invoice_total -= $svc['paid'];
}
$description = "$inslabel/" . $out['check_number'] . ' payment';
if ($svc['paid'] < 0) $description .= ' reversal';
writeDetailLine($bgcolor, $class, $patient_name, $invnumber,
$svc['code'], $check_date, "$inslabel/" . $out['check_number'] . ' payment',
$svc['code'], $check_date, $description,
0 - $svc['paid'], ($error ? '' : $invoice_total));
}

// Post and report adjustments from this ERA. Posted adjustment reasons
// must be 25 characters or less in order to fit on patient statements.
foreach ($svc['adj'] as $adj) {
$description = $adj['reason_code'] . ': ' . $adjustment_reasons[$adj['reason_code']];
// Group code PR is Patient Responsibility. Enter these as zero
// adjustments to retain the note without crediting the claim.
if ($adj['group_code'] == 'PR') {
$reason = 'Pt resp: '; // Reasons should be 25 chars or less.
if ($adj['reason_code'] == '1') $reason = 'To deductible: ';
else if ($adj['reason_code'] == '2') $reason = 'Coinsurance: ';
else if ($adj['reason_code'] == '3') $reason = 'Co-pay: ';
if ($adj['group_code'] == 'PR' || !$primary) {
// Group code PR is Patient Responsibility. Enter these as zero
// adjustments to retain the note without crediting the claim.
if ($primary) {
$reason = 'Pt resp: '; // Reasons should be 25 chars or less.
if ($adj['reason_code'] == '1') $reason = 'To deductible: ';
else if ($adj['reason_code'] == '2') $reason = 'Coinsurance: ';
else if ($adj['reason_code'] == '3') $reason = 'Co-pay: ';
}
// Non-primary insurance adjustments are garbage, either repeating
// the primary or are not adjustments at all. Report them as notes
// but do not post any amounts.
else {
$reason = "$inslabel note " . $adj['reason_code'] . ': ';
$reason .= sprintf("%.2f", $adj['amount']);
}
$reason .= sprintf("%.2f", $adj['amount']);
// Post a zero-dollar adjustment just to save it as a comment.
if (!$error && !$debug) {
slPostAdjustment($arrow['id'], 0, $production_date,
$out['check_number'], $svc['code'], $insurance_id,
Expand All @@ -315,7 +300,7 @@ function era_callback(&$out) {
writeMessageLine($bgcolor, $class, $description . ' ' .
sprintf("%.2f", $adj['amount']));
}
// Other group codes are real adjustments.
// Other group codes for primary insurance are real adjustments.
else {
if (!$error && !$debug) {
slPostAdjustment($arrow['id'], $adj['amount'], $production_date,
Expand Down Expand Up @@ -346,7 +331,7 @@ function era_callback(&$out) {
if ($sl_err) die($sl_err);
// Check for secondary insurance.
$insgot = strtolower($arrow['notes']);
if ($inslabel == 'Ins1' && strpos($insgot, 'ins2') !== false) {
if ($primary && strpos($insgot, 'ins2') !== false) {
slSetupSecondary($arrow['id'], $debug);
writeMessageLine($bgcolor, 'infdetail',
'This claim is now re-queued for secondary paper billing');
Expand Down
45 changes: 23 additions & 22 deletions library/parse_era.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@

function parse_era_2100(&$out, $cb) {
if ($out['loopid'] == '2110') {

// Force the sum of service payments to equal the claim payment
// amount. Whenever this is an issue it should result from
// claim-level adjustments, and in this case the first SVC item
// that we stored was a 'Claim' type.
$paytotal = $out['amount_approved'];
foreach ($out['svc'] as $svc) $paytotal -= $svc['paid'];
$paytotal = round($paytotal, 2);
if ($paytotal != 0) {
$out['svc'][0]['paid'] += $paytotal;
if ($out['svc'][0]['code'] != 'Claim') {
$out['warnings'] .= "First service item payment amount " .
"adjusted by $paytotal due to payment imbalance. " .
"This should not happen!\n";
}
}

$cb($out);
}
}
Expand All @@ -30,8 +47,6 @@ function parse_era($filename, $cb) {
$inline = substr($buffer, 0, $tpos);
$buffer = substr($buffer, $tpos + 1);

// echo $inline . "\n"; // debugging

$seg = explode('|', $inline);
$segid = $seg[0];

Expand Down Expand Up @@ -177,8 +192,10 @@ function parse_era($filename, $cb) {
}
else if ($segid == 'CAS' && $out['loopid'] == '2100') {
// This is a claim-level adjustment and should be unusual.
// Handle it by creating a dummy zero-charge service item and then
// populating the adjustments into it.
// Handle it by creating a dummy zero-charge service item and
// then populating the adjustments into it. See also code in
// parse_era_2100() which will later plug in a payment reversal
// amount that offsets these adjustments.
$i = 0; // if present, the dummy service item will be first.
if (!$out['svc'][$i]) {
$out['svc'][$i] = array();
Expand Down Expand Up @@ -227,21 +244,8 @@ function parse_era($filename, $cb) {
else if ($segid == 'MOA' && $out['loopid'] == '2100') {
$out['warnings'] .= "MOA segment at claim level ignored.\n";
}
// REF segments may provide various identifying numbers. REF02 is:
// 1L = Group or Policy Number
// 1W = Member Identification Number
// 9A = Repriced Claim Reference Number
// 9C = Adjusted Repriced Claim Reference Number
// A6 = Employee Identification Number
// BB = Authorization Number
// CE = Class of Contract Code
// EA = Medical Record Identification Number
// F8 = Original Reference Number
// G1 = Prior Authorization Number
// G3 = Predetermination of Benefits Identification Number
// IG = Insurance Policy Number
// SY = Social Security Number
// 1A, 1B, 1C, 1D, 1G, 1H, D3, G2 = various rendering provider numbers
// REF segments may provide various identifying numbers, where REF02
// indicates the type of number.
else if ($segid == 'REF' && $seg[1] == '1W' && $out['loopid'] == '2100') {
$out['claim_comment'] = trim($seg[2]);
}
Expand Down Expand Up @@ -291,16 +295,13 @@ function parse_era($filename, $cb) {
$out['svc'][$i]['chg'] = $seg[2];
$out['svc'][$i]['paid'] = $seg[3];
$out['svc'][$i]['adj'] = array();

// Note: SVC05, if present, indicates the paid units of service.
// It defaults to 1.

// Note: In the case of bundling, SVC06 reports the original procedure
// code, there are adjustments of the old procedure codes that zero out
// the original charge amounts, a negative adjustment of the new
// procedure code(s) reflecting the old charges, and other "normal"
// adjustments to these charges.

}
// DTM01 identifies the type of service date:
// 472 = a single date of service
Expand Down

0 comments on commit 326a485

Please sign in to comment.