2 // Copyright (C) 2008-2009 Rod Roark <rod@sunsetsystems.com>
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
9 require_once("Claim.class.php");
16 function put_hcfa($line, $col, $maxlen, $data) {
17 global $hcfa_curr_line, $hcfa_curr_col, $hcfa_data;
18 if ($line < $hcfa_curr_line)
19 die("Data item at ($line, $col) precedes current line.");
20 while ($hcfa_curr_line < $line) {
25 if ($col < $hcfa_curr_col)
26 die("Data item at ($line, $col) precedes current column.");
27 while ($hcfa_curr_col < $col) {
31 $data = preg_replace('/[.#]/', '', strtoupper($data));
32 $len = min(strlen($data), $maxlen);
33 $hcfa_data .= substr($data, 0, $len);
34 $hcfa_curr_col +
= $len;
37 function gen_hcfa_1500($pid, $encounter, &$log) {
38 global $hcfa_data, $hcfa_proc_index;
44 $claim = new Claim($pid, $encounter);
46 $log .= "Generating HCFA claim $pid-$encounter for " .
47 $claim->patientFirstName() . ' ' .
48 $claim->patientMiddleName() . ' ' .
49 $claim->patientLastName() . ' on ' .
50 date('Y-m-d H:i', $today) . ".\n";
52 while ($hcfa_proc_index < $claim->procCount()) {
53 if ($hcfa_proc_index) $hcfa_data .= "\014"; // append form feed for new page
54 gen_hcfa_1500_page($pid, $encounter, &$log, &$claim);
61 function gen_hcfa_1500_page($pid, $encounter, &$log, &$claim) {
62 global $hcfa_curr_line, $hcfa_curr_col, $hcfa_data, $hcfa_proc_index;
68 // http://www.ngsmedicare.com/NGSMedicare/PartB/EducationandSupport/ToolsandMaterials/CMS_ClaimFormInst.aspx
69 // Medicare interprets sections 9 and 11 of the claim form in its own
70 // special way. This flag tells us to do that. However I'm not 100%
71 // sure that it applies nationwide, and if you find that it is not right
72 // for you then set it to false. -- Rod 2009-03-26
73 $new_medicare_logic = $claim->claimType() == 'MB';
75 // Payer name, attn, street.
76 put_hcfa(2, 41, 31, $claim->payerName());
77 put_hcfa(3, 41, 31, $claim->payerAttn());
78 put_hcfa(4, 41, 31, $claim->payerStreet());
80 // Payer city, state, zip.
81 $tmp = $claim->payerCity() ?
($claim->payerCity() . ', ') : '';
82 put_hcfa(5, 41, 31, $tmp . $claim->payerState() . ' ' . $claim->payerZip());
84 // Box 1. Insurance Type
85 $ct = $claim->claimType();
86 $tmpcol = 45; // Other
87 if ($ct === 'MB') $tmpcol = 1; // Medicare
88 else if ($ct === 'MC') $tmpcol = 8; // Medicaid
89 else if ($ct === 'CH') $tmpcol = 15; // Champus
90 else if ($ct === 'CH') $tmpcol = 24; // Champus VA (why same code?)
91 else if ($ct === 'BL') $tmpcol = 31; // Group Health Plan (only BCBS?)
92 else if ($ct === '16') $tmpcol = 39; // FECA
93 put_hcfa(8, $tmpcol, 1, 'X');
95 // Box 1a. Insured's ID Number
96 put_hcfa(8, 50, 17, $claim->policyNumber());
98 // Box 2. Patient's Name
99 $tmp = $claim->patientLastName() . ', ' . $claim->patientFirstName();
100 if ($claim->patientMiddleName())
101 $tmp .= ', ' . substr($claim->patientMiddleName(),0,1);
102 put_hcfa(10, 1, 28, $tmp);
104 // Box 3. Patient's Birth Date and Sex
105 $tmp = $claim->patientDOB();
106 put_hcfa(10, 31, 2, substr($tmp,4,2));
107 put_hcfa(10, 34, 2, substr($tmp,6,2));
108 put_hcfa(10, 37, 4, substr($tmp,0,4));
109 put_hcfa(10, $claim->patientSex() == 'M' ?
42 : 47, 1, 'X');
111 // Box 4. Insured's Name
112 $tmp = $claim->insuredLastName() . ', ' . $claim->insuredFirstName();
113 if ($claim->insuredMiddleName())
114 $tmp .= ', ' . substr($claim->insuredMiddleName(),0,1);
115 put_hcfa(10, 50, 28, $tmp);
117 // Box 5. Patient's Address
118 put_hcfa(12, 1, 28, $claim->patientStreet());
120 // Box 6. Patient Relationship to Insured
121 $tmp = $claim->insuredRelationship();
122 $tmpcol = 47; // Other
123 if ($tmp === '18') $tmpcol = 33; // self
124 else if ($tmp === '01') $tmpcol = 38; // spouse
125 else if ($tmp === '19') $tmpcol = 42; // child
126 put_hcfa(12, $tmpcol, 1, 'X');
128 // Box 7. Insured's Address
129 put_hcfa(12, 50, 28, $claim->insuredStreet());
131 // Box 5 continued. Patient's City and State
132 put_hcfa(14, 1, 20, $claim->patientCity());
133 put_hcfa(14, 26, 2, $claim->patientState());
135 // Box 8. Patient (Marital) Status
136 $tmp = $claim->patientStatus();
137 $tmpcol = 47; // Other
138 if ($tmp === 'S') $tmpcol = 35; // Single
139 else if ($tmp === 'M') $tmpcol = 41; // Married
140 put_hcfa(14, $tmpcol, 1, 'X');
142 // Box 7 continued. Insured's City and State
143 put_hcfa(14, 50, 20, $claim->insuredCity());
144 put_hcfa(14, 74, 2, $claim->insuredState());
146 // Box 5 continued. Patient's Zip Code and Telephone
147 put_hcfa(16, 1, 10, $claim->patientZip());
148 $tmp = $claim->patientPhone();
149 put_hcfa(16, 15, 3, substr($tmp,0,3));
150 put_hcfa(16, 19, 7, substr($tmp,3));
152 // Box 8 continued. Patient (Employment) Status
153 $tmp = $claim->patientOccupation();
154 if ($tmp === 'STUDENT' ) put_hcfa(16, 41, 1, 'X');
155 else if ($tmp === 'PT STUDENT') put_hcfa(16, 47, 1, 'X');
156 else if ($tmp !== 'UNEMPLOYED') put_hcfa(16, 35, 1, 'X');
158 // Box 7 continued. Insured's Zip Code and Telephone
159 put_hcfa(16, 50, 10, $claim->insuredZip());
160 $tmp = $claim->insuredPhone();
161 put_hcfa(16, 65, 3, substr($tmp,0,3));
162 put_hcfa(16, 69, 7, substr($tmp,3));
164 // Box 9. Other Insured's Name
165 if ($new_medicare_logic) {
166 // TBD: Medigap stuff? How do we know if this is a Medigap transfer?
169 if ($claim->payerCount() > 1) {
170 $tmp = $claim->insuredLastName(1) . ', ' . $claim->insuredFirstName(1);
171 if ($claim->insuredMiddleName(1))
172 $tmp .= ', ' . substr($claim->insuredMiddleName(1),0,1);
173 put_hcfa(18, 1, 28, $tmp);
177 // Box 11. Insured's Group Number
178 if ($new_medicare_logic) {
179 // If this is Medicare secondary then we need the primary's policy number
180 // here, otherwise the word "NONE".
181 $tmp = $claim->payerSequence() == 'P' ?
'NONE' : $claim->policyNumber(1);
184 $tmp = $claim->groupNumber();
186 put_hcfa(18, 50, 30, $tmp);
188 // Box 9a. Other Insured's Policy or Group Number
189 if ($new_medicare_logic) {
190 // TBD: Medigap stuff?
193 if ($claim->payerCount() > 1) {
194 put_hcfa(20, 1, 28, $claim->policyNumber(1));
198 // Box 10a. Employment Related
199 put_hcfa(20, $claim->isRelatedEmployment() ?
35 : 41, 1, 'X');
201 // Box 11a. Insured's Birth Date and Sex
202 if ($new_medicare_logic) {
203 $tmpdob = $tmpsex = '';
204 if ($claim->payerSequence() != 'P') {
205 $tmpdob = $claim->insuredDOB(1);
206 $tmpsex = $claim->insuredSex(1);
210 $tmpdob = $claim->insuredDOB();
211 $tmpsex = $claim->insuredSex();
214 put_hcfa(20, 53, 2, substr($tmpdob,4,2));
215 put_hcfa(20, 56, 2, substr($tmpdob,6,2));
216 put_hcfa(20, 59, 4, substr($tmpdob,0,4));
219 put_hcfa(20, $tmpsex == 'M' ?
68 : 75, 1, 'X');
222 // Box 9b. Other Insured's Birth Date and Sex
223 if ($new_medicare_logic) {
224 // TBD: Medigap stuff?
227 if ($claim->payerCount() > 1) {
228 $tmp = $claim->insuredDOB(1);
229 put_hcfa(22, 2, 2, substr($tmp,4,2));
230 put_hcfa(22, 5, 2, substr($tmp,6,2));
231 put_hcfa(22, 8, 4, substr($tmp,0,4));
232 put_hcfa(22, $claim->insuredSex(1) == 'M' ?
18 : 24, 1, 'X');
236 // Box 10b. Auto Accident
237 put_hcfa(22, $claim->isRelatedAuto() ?
35 : 41, 1, 'X');
238 if ($claim->isRelatedAuto())
239 put_hcfa(22, 45, 2, $claim->autoAccidentState());
241 // Box 11b. Insured's Employer/School Name
242 if ($new_medicare_logic) {
243 $tmp = $claim->payerSequence() == 'P' ?
'' : $claim->groupName(1);
246 $tmp = $claim->groupName();
248 put_hcfa(22, 50, 30, $tmp);
250 // Box 9c. Other Insured's Employer/School Name
251 if ($new_medicare_logic) {
252 // TBD: Medigap stuff?
255 if ($claim->payerCount() > 1) {
256 put_hcfa(24, 1, 28, $claim->groupName(1));
260 // Box 10c. Other Accident
261 put_hcfa(24, $claim->isRelatedOther() ?
35 : 41, 1, 'X');
263 // Box 11c. Insurance Plan Name or Program Name
264 if ($new_medicare_logic) {
266 if ($claim->payerSequence() != 'P') {
267 $tmp = $claim->planName(1);
268 if (!$tmp) $tmp = $claim->payerName(1);
272 $tmp = $claim->planName();
274 put_hcfa(24, 50, 30, $tmp);
276 // Box 9d. Other Insurance Plan Name or Program Name
277 if ($new_medicare_logic) {
278 // TBD: Medigap stuff?
281 if ($claim->payerCount() > 1) {
282 put_hcfa(26, 1, 28, $claim->planName(1));
286 // Box 11d. Is There Another Health Benefit Plan
287 if (!$new_medicare_logic) {
288 put_hcfa(26, $claim->payerCount() > 1 ?
52 : 57, 1, 'X');
291 // Box 12. Patient's or Authorized Person's Signature
292 put_hcfa(29, 7, 17, 'Signature on File');
293 // Note: Date does not apply unless the person physically signs the form.
295 // Box 13. Insured's or Authorized Person's Signature
296 put_hcfa(29, 55, 17, 'Signature on File');
298 // Box 14. Date of Current Illness/Injury/Pregnancy
299 $tmp = $claim->onsetDate();
300 put_hcfa(32, 2, 2, substr($tmp,4,2));
301 put_hcfa(32, 5, 2, substr($tmp,6,2));
302 put_hcfa(32, 8, 4, substr($tmp,0,4));
304 // Box 15. Date of First Occurrence
305 // Not currently supported.
307 // Box 16. Dates Patient Unable to Work in Current Occupation
308 if ($claim->isUnableToWork()) {
309 $tmp = $claim->offWorkFrom();
310 put_hcfa(32, 54, 2, substr($tmp,4,2));
311 put_hcfa(32, 57, 2, substr($tmp,6,2));
312 put_hcfa(32, 60, 4, substr($tmp,0,4));
313 $tmp = $claim->offWorkTo();
314 put_hcfa(32, 68, 2, substr($tmp,4,2));
315 put_hcfa(32, 71, 2, substr($tmp,6,2));
316 put_hcfa(32, 74, 4, substr($tmp,0,4));
319 // Referring provider stuff. Reports are that for primary care doctors,
320 // Medicare forbids an entry here and other payers require one.
321 // There is still confusion over this.
323 if ($claim->referrerLastName() &&
324 (empty($GLOBALS['MedicareReferrerIsRenderer']) ||
$claim->claimType() != 'MB'))
326 // Box 17a. Referring Provider Alternate Identifier
327 if ($claim->referrerUPIN() && $claim->claimType() != 'MB') {
328 put_hcfa(33, 30, 2, '1G');
329 put_hcfa(33, 33, 15, $claim->referrerUPIN());
332 // Box 17. Name of Referring Provider or Other Source
333 $tmp = $claim->referrerLastName() . ', ' . $claim->referrerFirstName();
334 if ($claim->referrerMiddleName())
335 $tmp .= ', ' . substr($claim->referrerMiddleName(),0,1);
336 put_hcfa(34, 1, 25, $tmp);
338 // Box 17b. Referring Provider NPI
339 if ($claim->referrerNPI()) {
340 put_hcfa(34, 33, 15, $claim->referrerNPI());
344 // Box 18. Hospitalization Dates Related to Current Services
345 if ($claim->isHospitalized()) {
346 $tmp = $claim->hospitalizedFrom();
347 put_hcfa(34, 54, 2, substr($tmp,4,2));
348 put_hcfa(34, 57, 2, substr($tmp,6,2));
349 put_hcfa(34, 60, 4, substr($tmp,0,4));
350 $tmp = $claim->hospitalizedTo();
351 put_hcfa(34, 68, 2, substr($tmp,4,2));
352 put_hcfa(34, 71, 2, substr($tmp,6,2));
353 put_hcfa(34, 74, 4, substr($tmp,0,4));
356 // Box 19. Reserved for Local Use
357 put_hcfa(36, 1, 48, $claim->additionalNotes());
359 // Box 20. Outside Lab
360 put_hcfa(36, $claim->isOutsideLab() ?
52 : 57, 1, 'X');
361 if ($claim->isOutsideLab()) {
362 // Note here that put_hcfa strips the decimal point, as required.
363 // We right-justify this amount (ending in col. 69).
364 put_hcfa(36, 63, 8, sprintf('%8s', $claim->outsideLabAmount()));
368 $tmp = $claim->diagArray();
370 foreach ($tmp as $diag) $diags[] = $diag;
371 if (!empty($diags[0])) {
372 put_hcfa(38, 3, 3, substr($diags[0], 0, 3));
373 put_hcfa(38, 7, 2, substr($diags[0], 3));
375 if (!empty($diags[2])) {
376 put_hcfa(38, 30, 3, substr($diags[2], 0, 3));
377 put_hcfa(38, 34, 2, substr($diags[2], 3));
380 // Box 22. Medicaid Resubmission Code and Original Ref. No.
381 put_hcfa(38, 50, 10, $claim->medicaidResubmissionCode());
382 put_hcfa(38, 62, 10, $claim->medicaidOriginalReference());
384 // Box 21 continued. Diagnoses
385 if (!empty($diags[1])) {
386 put_hcfa(40, 3, 3, substr($diags[1], 0, 3));
387 put_hcfa(40, 7, 2, substr($diags[1], 3));
389 if (!empty($diags[3])) {
390 put_hcfa(40, 30, 3, substr($diags[3], 0, 3));
391 put_hcfa(40, 34, 2, substr($diags[3], 3));
394 // Box 23. Prior Authorization Number
395 put_hcfa(40, 50, 28, $claim->priorAuth());
397 $proccount = $claim->procCount(); // number of procedures
399 // Charges, adjustments and payments are accumulated by line item so that
400 // each page of a multi-page claim will stand alone. Payments include the
401 // co-pay for the first page only.
402 $clm_total_charges = 0;
403 $clm_amount_adjusted = 0;
404 $clm_amount_paid = $hcfa_proc_index ?
0 : $claim->patientPaidAmount();
406 // Procedure loop starts here.
408 for ($svccount = 0; $svccount < 6 && $hcfa_proc_index < $proccount; ++
$hcfa_proc_index) {
409 $dia = $claim->diagIndexArray($hcfa_proc_index);
411 if (!$claim->cptCharges($hcfa_proc_index)) {
412 $log .= "*** Procedure '" . $claim->cptKey($hcfa_proc_index) .
413 "' has no charges!\n";
417 $log .= "*** Procedure '" . $claim->cptKey($hcfa_proc_index) .
418 "' is not justified!\n";
421 $clm_total_charges +
= $claim->cptCharges($hcfa_proc_index);
423 // Compute prior payments and "hard" adjustments.
424 for ($ins = 1; $ins < $claim->payerCount(); ++
$ins) {
425 if ($claim->payerSequence($ins) > $claim->payerSequence())
426 continue; // skip future payers
427 $payerpaid = $claim->payerTotals($ins, $claim->cptKey($hcfa_proc_index));
428 $clm_amount_paid +
= $payerpaid[1];
429 $clm_amount_adjusted +
= $payerpaid[2];
433 $lino = $svccount * 2 +
41;
435 // Drug Information. Medicaid insurers want this with HCPCS codes.
437 $ndc = $claim->cptNDCID($hcfa_proc_index);
439 if (preg_match('/^\d\d\d\d\d-\d\d\d\d-\d\d$/', $ndc, $tmp)) {
440 $ndc = $tmp[1] . $tmp[2] . $tmp[3];
442 $log .= "*** NDC code '$ndc' has invalid format!\n";
444 put_hcfa($lino, 1, 50, "N4$ndc " . $claim->cptNDCUOM($hcfa_proc_index) .
445 $claim->cptNDCQuantity($hcfa_proc_index));
448 // 24i and 24j Top. ID Qualifier and Rendering Provider ID
449 if ($claim->supervisorNumber()) {
450 // If there is a supervising provider and that person has a
451 // payer-specific provider number, then we assume that the SP
452 // must be identified on the claim and this is how we do it
453 // (but the NPI of the actual rendering provider appears below).
454 // BCBS of TN indicated they want it this way. YMMV. -- Rod
455 put_hcfa($lino, 65, 2, $claim->supervisorNumberType());
456 put_hcfa($lino, 68, 10, $claim->supervisorNumber());
458 else if ($claim->providerNumber($hcfa_proc_index)) {
459 put_hcfa($lino, 65, 2, $claim->providerNumberType($hcfa_proc_index));
460 put_hcfa($lino, 68, 10, $claim->providerNumber($hcfa_proc_index));
465 // 24a. Date of Service
466 $tmp = $claim->serviceDate();
467 put_hcfa($lino, 1, 2, substr($tmp,4,2));
468 put_hcfa($lino, 4, 2, substr($tmp,6,2));
469 put_hcfa($lino, 7, 2, substr($tmp,2,2));
470 put_hcfa($lino,10, 2, substr($tmp,4,2));
471 put_hcfa($lino,13, 2, substr($tmp,6,2));
472 put_hcfa($lino,16, 2, substr($tmp,2,2));
474 // 24b. Place of Service
475 put_hcfa($lino, 19, 2, $claim->facilityPOS());
478 // Not currently supported.
480 // 24d. Procedures, Services or Supplies
481 put_hcfa($lino, 25, 7, $claim->cptCode($hcfa_proc_index));
482 put_hcfa($lino, 33, 6, $claim->cptModifier($hcfa_proc_index));
484 // 24e. Diagnosis Pointer
486 foreach ($claim->diagIndexArray($hcfa_proc_index) as $value) $tmp .= $value;
487 put_hcfa($lino, 45, 4, $tmp);
490 put_hcfa($lino, 50, 8, str_replace('.', ' ',
491 sprintf('%8.2f', $claim->cptCharges($hcfa_proc_index))));
493 // 24g. Days or Units
494 put_hcfa($lino, 59, 3, $claim->cptUnits($hcfa_proc_index));
496 // 24h. EPSDT Family Plan
497 // Not currently supported.
499 // 24j. Rendering Provider NPI
500 put_hcfa($lino, 68, 10, $claim->providerNPI($hcfa_proc_index));
503 // 25. Federal Tax ID Number
504 // FrreB hard coded EIN. Changed it to included SSN as well.
505 put_hcfa(56, 1, 15, $claim->billingFacilityETIN());
506 if($claim->federalIdType()=='SY'){
507 put_hcfa(56, 16, 1, 'X'); // The SSN checkbox
510 put_hcfa(56, 19, 1, 'X'); // The EIN checkbox
513 // 26. Patient's Account No.
514 // Instructions say hyphens are not allowed, but freeb used them.
515 put_hcfa(56, 23, 15, "$pid-$encounter");
517 // 27. Accept Assignment
518 put_hcfa(56, $claim->billingFacilityAssignment() ?
38 : 43, 1, 'X');
521 put_hcfa(56, 52, 8, str_replace('.',' ',sprintf('%8.2f',$clm_total_charges)));
522 if (!$clm_total_charges) {
523 $log .= "*** This claim has no charges!\n";
527 put_hcfa(56, 62, 8, str_replace('.',' ',sprintf('%8.2f',$clm_amount_paid)));
530 // For secondary payers this reflects primary "contracted rate" adjustments,
531 // so in general box 30 will not equal box 28 minus box 29.
532 put_hcfa(56, 71, 8, str_replace('.',' ',sprintf('%8.2f',
533 $clm_total_charges - $clm_amount_paid - $clm_amount_adjusted)));
535 // 33. Billing Provider: Phone Number
536 $tmp = $claim->billingContactPhone();
537 put_hcfa(57, 66, 3, substr($tmp,0,3));
538 put_hcfa(57, 70, 7, substr($tmp,3));
540 // 32. Service Facility Location Information: Name
541 put_hcfa(58, 23, 25, $claim->facilityName());
543 // 33. Billing Provider: Name
544 put_hcfa(58, 50, 25, $claim->billingFacilityName());
546 // 32. Service Facility Location Information: Street
547 put_hcfa(59, 23, 25, $claim->facilityStreet());
549 // 33. Billing Provider: Name
550 put_hcfa(59, 50, 25, $claim->billingFacilityStreet());
552 // 31. Signature of Physician or Supplier
553 // FreeB printed the rendering provider's name and the current date here,
554 // but according to my instructions it must be a real signature and date,
555 // or else "Signature on File" or "SOF".
556 put_hcfa(60, 1, 20, 'Signature on File');
558 // $tmp = $claim->providerFirstName();
559 // if ($claim->providerMiddleName()) $tmp .= ' ' . substr($claim->providerMiddleName(),0,1);
560 // put_hcfa(60, 1, 20, $tmp . ' ' . $claim->providerLastName());
562 // 32. Service Facility Location Information: City State Zip
563 $tmp = $claim->facilityCity() ?
($claim->facilityCity() . ' ') : '';
564 put_hcfa(60, 23, 25, $tmp . $claim->facilityState() . ' ' .
565 $claim->facilityZip());
567 // 32. Billing Provider: City State Zip
568 $tmp = $claim->billingFacilityCity() ?
($claim->billingFacilityCity() . ' ') : '';
569 put_hcfa(60, 50, 25, $tmp . $claim->billingFacilityState() . ' ' .
570 $claim->billingFacilityZip());
572 // 32a. Service Facility NPI
573 put_hcfa(61, 24, 10, $claim->facilityNPI());
575 // 32b. Service Facility Other ID
576 // Note that Medicare does NOT want this any more.
577 if ($claim->providerGroupNumber()) {
578 put_hcfa(61, 36, 2, $claim->providerNumberType());
579 put_hcfa(61, 38, 11, $claim->providerGroupNumber());
582 // 33a. Billing Facility NPI
583 put_hcfa(61, 51, 10, $claim->billingFacilityNPI());
585 // 33b. Billing Facility Other ID
586 // Note that Medicare does NOT want this any more.
587 if ($claim->providerGroupNumber() && $claim->claimType() != 'MB') {
588 put_hcfa(61, 63, 2, $claim->providerNumberType());
589 put_hcfa(61, 65, 14, $claim->providerGroupNumber());
592 // Put an extra line here for compatibility with old hcfa text generated form
593 put_hcfa(62, 1, 1, ' ');