diff --git a/src/main/java/org/mitre/synthea/export/rif/InpatientExporter.java b/src/main/java/org/mitre/synthea/export/rif/InpatientExporter.java index 219b52e14..9348974f8 100644 --- a/src/main/java/org/mitre/synthea/export/rif/InpatientExporter.java +++ b/src/main/java/org/mitre/synthea/export/rif/InpatientExporter.java @@ -41,7 +41,6 @@ long export(Person person, long startTime, long stopTime) throws IOException { boolean previousEmergency = false; for (HealthRecord.Encounter encounter : person.record.encounters) { - HashMap fieldValues = new HashMap<>(); if (encounter.stop < startTime || encounter.stop < CLAIM_CUTOFF) { continue; } @@ -67,7 +66,7 @@ long export(Person person, long startTime, long stopTime) throws IOException { long claimGroupId = RIFExporter.nextClaimGroupId.getAndDecrement(); long fiDocId = RIFExporter.nextFiDocCntlNum.getAndDecrement(); - fieldValues.clear(); + HashMap fieldValues = new HashMap<>(); exporter.staticFieldConfig.setValues(fieldValues, BB2RIFStructure.INPATIENT.class, person); // The REQUIRED fields diff --git a/src/main/java/org/mitre/synthea/export/rif/OutpatientExporter.java b/src/main/java/org/mitre/synthea/export/rif/OutpatientExporter.java index 4b1bb791b..cb721538d 100644 --- a/src/main/java/org/mitre/synthea/export/rif/OutpatientExporter.java +++ b/src/main/java/org/mitre/synthea/export/rif/OutpatientExporter.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.mitre.synthea.export.ExportHelper; import org.mitre.synthea.world.agents.Person; @@ -33,15 +34,11 @@ public OutpatientExporter(BB2RIFExporter exporter) { */ long export(Person person, long startTime, long stopTime) throws IOException { long claimCount = 0; - HashMap fieldValues = new HashMap<>(); for (HealthRecord.Encounter encounter : person.record.encounters) { if (encounter.stop < startTime || encounter.stop < CLAIM_CUTOFF) { continue; } - if (encounter.claim.getTotalClaimCost().compareTo(Claim.ZERO_CENTS) == 0) { - continue; - } if (!hasPartABCoverage(person, encounter.stop)) { continue; } @@ -55,11 +52,15 @@ long export(Person person, long startTime, long stopTime) throws IOException { for (Claim.ClaimEntry lineItem: billableItems) { billableTotal.addCosts(lineItem); } + if (billableTotal.getTotalClaimCost().compareTo(Claim.ZERO_CENTS) == 0) { + continue; + } long claimId = RIFExporter.nextClaimId.getAndDecrement(); long claimGroupId = RIFExporter.nextClaimGroupId.getAndDecrement(); long fiDocId = RIFExporter.nextFiDocCntlNum.getAndDecrement(); + HashMap fieldValues = new HashMap<>(); exporter.staticFieldConfig.setValues(fieldValues, BB2RIFStructure.OUTPATIENT.class, person); // The REQUIRED fields @@ -80,13 +81,7 @@ long export(Person person, long startTime, long stopTime) throws IOException { fieldValues.put(BB2RIFStructure.OUTPATIENT.RNDRNG_PHYSN_NPI, encounter.clinician.npi); fieldValues.put(BB2RIFStructure.OUTPATIENT.ORG_NPI_NUM, encounter.provider.npi); fieldValues.put(BB2RIFStructure.OUTPATIENT.OP_PHYSN_NPI, encounter.clinician.npi); - setClaimCosts(fieldValues, billableTotal); - if (encounter.claim.coveredByMedicare()) { - fieldValues.put(BB2RIFStructure.OUTPATIENT.NCH_PRMRY_PYR_CLM_PD_AMT, "0"); - } else { - fieldValues.put(BB2RIFStructure.OUTPATIENT.NCH_PRMRY_PYR_CLM_PD_AMT, - String.format("%.2f", billableTotal.getCoveredCost())); - } + fieldValues.put(BB2RIFStructure.OUTPATIENT.PRVDR_STATE_CD, exporter.locationMapper.getStateCode(encounter.provider.state)); // PTNT_DSCHRG_STUS_CD: 1=home, 2=transfer, 3=SNF, 20=died, 30=still here @@ -111,67 +106,23 @@ long export(Person person, long startTime, long stopTime) throws IOException { } } - // Use the active condition diagnoses to enter mapped values - // into the diagnoses codes. - List mappedDiagnosisCodes = getDiagnosesCodes(person, encounter.stop); - boolean noDiagnoses = mappedDiagnosisCodes.isEmpty(); - if (!noDiagnoses) { - int smallest = Math.min(mappedDiagnosisCodes.size(), - BB2RIFStructure.outpatientDxFields.length); - for (int i = 0; i < smallest; i++) { - BB2RIFStructure.OUTPATIENT[] dxField = BB2RIFStructure.outpatientDxFields[i]; - fieldValues.put(dxField[0], mappedDiagnosisCodes.get(i)); - fieldValues.put(dxField[1], "0"); // 0=ICD10 - } - if (!fieldValues.containsKey(BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD)) { - fieldValues.put(BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD, mappedDiagnosisCodes.get(0)); - } + int diagnosisCount = mapDiagnoses(fieldValues, person, encounter); + int procedureCount = mapProcedures(fieldValues, person, encounter); + if (icdReasonCode == null && (diagnosisCount + procedureCount == 0)) { + continue; // skip this encounter } - // Check for external code... - exporter.setExternalCode(person, fieldValues, - BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD, - BB2RIFStructure.OUTPATIENT.ICD_DGNS_E_CD1, - BB2RIFStructure.OUTPATIENT.ICD_DGNS_E_VRSN_CD1); - exporter.setExternalCode(person, fieldValues, - BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD, - BB2RIFStructure.OUTPATIENT.FST_DGNS_E_CD, - BB2RIFStructure.OUTPATIENT.FST_DGNS_E_VRSN_CD); - - // Use the procedures in this encounter to enter mapped values - boolean noProcedures = false; - if (!encounter.procedures.isEmpty()) { - List mappableProcedures = new ArrayList<>(); - List mappedProcedureCodes = new ArrayList<>(); - for (HealthRecord.Procedure procedure : encounter.procedures) { - for (HealthRecord.Code code : procedure.codes) { - if (exporter.conditionCodeMapper.canMap(code)) { - mappableProcedures.add(procedure); - mappedProcedureCodes.add(exporter.conditionCodeMapper.map(code, person, true)); - break; // take the first mappable code for each procedure - } - } - } - if (!mappableProcedures.isEmpty()) { - int smallest = Math.min(mappableProcedures.size(), - BB2RIFStructure.outpatientPxFields.length); - for (int i = 0; i < smallest; i++) { - BB2RIFStructure.OUTPATIENT[] pxField = BB2RIFStructure.outpatientPxFields[i]; - fieldValues.put(pxField[0], mappedProcedureCodes.get(i)); - fieldValues.put(pxField[1], "0"); // 0=ICD10 - fieldValues.put(pxField[2], - RIFExporter.bb2DateFromTimestamp(mappableProcedures.get(i).start)); - } - } else { - noProcedures = true; - } - } - if (icdReasonCode == null && noDiagnoses && noProcedures) { - continue; // skip this encounter + setClaimCosts(fieldValues, billableTotal); + if (encounter.claim.coveredByMedicare()) { + fieldValues.put(BB2RIFStructure.OUTPATIENT.NCH_PRMRY_PYR_CLM_PD_AMT, "0"); + } else { + fieldValues.put(BB2RIFStructure.OUTPATIENT.NCH_PRMRY_PYR_CLM_PD_AMT, + String.format("%.2f", billableTotal.getCoveredCost())); } - String revCenter = fieldValues.get(BB2RIFStructure.OUTPATIENT.REV_CNTR); + + String originalRandomRevCenter = fieldValues.get(BB2RIFStructure.OUTPATIENT.REV_CNTR); if (encounter.type.equals(HealthRecord.EncounterType.VIRTUAL.toString())) { - revCenter = person.randBoolean() ? "0780" : "0789"; + String revCenter = person.randBoolean() ? "0780" : "0789"; fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR, revCenter); } @@ -181,7 +132,7 @@ long export(Person person, long startTime, long stopTime) throws IOException { String hcpcsCode = null; if (lineItem.entry instanceof HealthRecord.Procedure) { hcpcsCode = getFirstMappedHCPCSCode(lineItem.entry.codes, person); - fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR, revCenter); + fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR, originalRandomRevCenter); fieldValues.remove(BB2RIFStructure.OUTPATIENT.REV_CNTR_IDE_NDC_UPC_NUM); fieldValues.remove(BB2RIFStructure.OUTPATIENT.REV_CNTR_NDC_QTY); fieldValues.remove(BB2RIFStructure.OUTPATIENT.REV_CNTR_NDC_QTY_QLFR_CD); @@ -206,18 +157,15 @@ long export(Person person, long startTime, long stopTime) throws IOException { exporter.rifWriters.writeValues(BB2RIFStructure.OUTPATIENT.class, fieldValues); } - if (claimLine == 1) { - // If claimLine still equals 1, then no line items were successfully added. - // Add a single top-level entry. - fieldValues.put(BB2RIFStructure.OUTPATIENT.CLM_LINE_NUM, Integer.toString(claimLine)); - fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR_DT, - RIFExporter.bb2DateFromTimestamp(encounter.start)); - // 99241: "Office consultation for a new or established patient" - fieldValues.put(BB2RIFStructure.OUTPATIENT.HCPCS_CD, "99241"); - setClaimCosts(fieldValues, encounter.claim.totals); - setLineItemCosts(fieldValues, encounter.claim.totals); - exporter.rifWriters.writeValues(BB2RIFStructure.OUTPATIENT.class, fieldValues); - } + // Add a total charge entry. + fieldValues.put(BB2RIFStructure.OUTPATIENT.CLM_LINE_NUM, Integer.toString(claimLine)); + fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR_DT, + RIFExporter.bb2DateFromTimestamp(encounter.start)); + fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR, "0001"); + // 99241: "Office consultation for a new or established patient" + fieldValues.put(BB2RIFStructure.OUTPATIENT.HCPCS_CD, "99241"); + setLineItemCosts(fieldValues, billableTotal); + exporter.rifWriters.writeValues(BB2RIFStructure.OUTPATIENT.class, fieldValues); } claimCount++; } @@ -259,4 +207,67 @@ private void setLineItemCosts(HashMap fieldV fieldValues.put(BB2RIFStructure.OUTPATIENT.REV_CNTR_NCVRD_CHRG_AMT, String.format("%.2f", claim.getPatientCost())); } + + private int mapDiagnoses(Map fieldValues, Person person, + HealthRecord.Encounter encounter) { + // Use the active condition diagnoses to enter mapped values + // into the diagnoses codes. + List mappedDiagnosisCodes = getDiagnosesCodes(person, encounter.stop); + boolean noDiagnoses = mappedDiagnosisCodes.isEmpty(); + int smallest = Math.min(mappedDiagnosisCodes.size(), + BB2RIFStructure.outpatientDxFields.length); + if (!noDiagnoses) { + for (int i = 0; i < smallest; i++) { + BB2RIFStructure.OUTPATIENT[] dxField = BB2RIFStructure.outpatientDxFields[i]; + fieldValues.put(dxField[0], mappedDiagnosisCodes.get(i)); + fieldValues.put(dxField[1], "0"); // 0=ICD10 + } + if (!fieldValues.containsKey(BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD)) { + fieldValues.put(BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD, mappedDiagnosisCodes.get(0)); + } + } + + // Check for external code... + exporter.setExternalCode(person, fieldValues, + BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD, + BB2RIFStructure.OUTPATIENT.ICD_DGNS_E_CD1, + BB2RIFStructure.OUTPATIENT.ICD_DGNS_E_VRSN_CD1); + exporter.setExternalCode(person, fieldValues, + BB2RIFStructure.OUTPATIENT.PRNCPAL_DGNS_CD, + BB2RIFStructure.OUTPATIENT.FST_DGNS_E_CD, + BB2RIFStructure.OUTPATIENT.FST_DGNS_E_VRSN_CD); + + return smallest; + } + + private int mapProcedures(Map fieldValues, Person person, + HealthRecord.Encounter encounter) { + // Use the procedures in this encounter to enter mapped values + int procedureCount = 0; + if (!encounter.procedures.isEmpty()) { + List mappableProcedures = new ArrayList<>(); + List mappedProcedureCodes = new ArrayList<>(); + for (HealthRecord.Procedure procedure : encounter.procedures) { + for (HealthRecord.Code code : procedure.codes) { + if (exporter.conditionCodeMapper.canMap(code)) { + mappableProcedures.add(procedure); + mappedProcedureCodes.add(exporter.conditionCodeMapper.map(code, person, true)); + break; // take the first mappable code for each procedure + } + } + } + if (!mappableProcedures.isEmpty()) { + procedureCount = Math.min(mappableProcedures.size(), + BB2RIFStructure.outpatientPxFields.length); + for (int i = 0; i < procedureCount; i++) { + BB2RIFStructure.OUTPATIENT[] pxField = BB2RIFStructure.outpatientPxFields[i]; + fieldValues.put(pxField[0], mappedProcedureCodes.get(i)); + fieldValues.put(pxField[1], "0"); // 0=ICD10 + fieldValues.put(pxField[2], + RIFExporter.bb2DateFromTimestamp(mappableProcedures.get(i).start)); + } + } + } + return procedureCount; + } } diff --git a/src/main/resources/export/bfd_field_values.tsv b/src/main/resources/export/bfd_field_values.tsv index 185a6c152..15b045f20 100644 --- a/src/main/resources/export/bfd_field_values.tsv +++ b/src/main/resources/export/bfd_field_values.tsv @@ -565,7 +565,7 @@ Line Field BENEFICIARY BENEFICIARY_HISTORY INPATIENT OUTPATIENT CARRIER PDE DME 563 RDS_NOV_IND Coded N/A N/A N/A N/A N/A N/A N/A N/A N/A TRUE 564 RDS_OCT_IND Coded N/A N/A N/A N/A N/A N/A N/A N/A N/A TRUE 565 RDS_SEPT_IND Coded N/A N/A N/A N/A N/A N/A N/A N/A N/A TRUE -566 REV_CNTR N/A N/A "0300,0301,0305,0450,0730,0306,0324,0424,0420,0272 (most frequent)" "0001 (total charge, lots of alternatives)" N/A N/A N/A "0551,0551,0551,0421,0421,0023,0001 (hha codes)" "0571,0571,0551,0551,0651,0001 (hospice codes)" Coded FALSE +566 REV_CNTR N/A N/A "0300,0301,0305,0450,0730,0306,0324,0424,0420,0272 (most frequent)" "XXXX (lots of alternatives)" N/A N/A N/A "0551,0551,0551,0421,0421,0023,0001 (hha codes)" "0571,0571,0551,0551,0651,0001 (hospice codes)" Coded FALSE 567 REV_CNTR_1ST_ANSI_CD N/A N/A N/A N/A N/A N/A N/A N/A TRUE 568 REV_CNTR_1ST_MSP_PD_AMT N/A N/A N/A 0 N/A N/A N/A N/A N/A N/A FALSE 569 REV_CNTR_2ND_ANSI_CD N/A N/A N/A N/A N/A N/A N/A N/A N/A TRUE diff --git a/src/test/java/org/mitre/synthea/export/rif/BB2RIFExporterTest.java b/src/test/java/org/mitre/synthea/export/rif/BB2RIFExporterTest.java index b78681653..1bbc665a1 100644 --- a/src/test/java/org/mitre/synthea/export/rif/BB2RIFExporterTest.java +++ b/src/test/java/org/mitre/synthea/export/rif/BB2RIFExporterTest.java @@ -597,6 +597,7 @@ private static class OutpatientTotals { private BigDecimal revCenterChargeTotal; private BigDecimal revCenterPaymentTotal; private BigDecimal revCenterBenePaymentTotal; + private static final String TOTAL_CHARGE_REV_CENTER = "0001"; OutpatientTotals(LinkedHashMap row) { claimID = row.get("CLM_ID"); @@ -609,12 +610,14 @@ private static class OutpatientTotals { } void addLineItems(LinkedHashMap row) { - revCenterChargeTotal = revCenterChargeTotal.add( - new BigDecimal(row.get("REV_CNTR_TOT_CHRG_AMT")).setScale(2)); - revCenterPaymentTotal = revCenterPaymentTotal.add( - new BigDecimal(row.get("REV_CNTR_PMT_AMT_AMT")).setScale(2)); - revCenterBenePaymentTotal = revCenterBenePaymentTotal.add( - new BigDecimal(row.get("REV_CNTR_BENE_PMT_AMT")).setScale(2)); + if (!TOTAL_CHARGE_REV_CENTER.equals(row.get("REV_CNTR"))) { + revCenterChargeTotal = revCenterChargeTotal.add( + new BigDecimal(row.get("REV_CNTR_TOT_CHRG_AMT")).setScale(2)); + revCenterPaymentTotal = revCenterPaymentTotal.add( + new BigDecimal(row.get("REV_CNTR_PMT_AMT_AMT")).setScale(2)); + revCenterBenePaymentTotal = revCenterBenePaymentTotal.add( + new BigDecimal(row.get("REV_CNTR_BENE_PMT_AMT")).setScale(2)); + } } void validate() {