Financial
Calculating financial details like liability or payments is a common need for requesters of claims data.
This type of information can explain out-of-pocket costs to patients and can help providers and payers understand the financial implications of care.
Both financial liability and payments information can be calculated from fields in the Explanation of Benefit (EOB) resource.
#Liability
In the context of care or treatment, the following 3 parties are collectively responsible for the cost of a service or drug:
- The member (aka. patient)
- The insurer (aka. payer)
- The provider (aka. healthcare service provider)
In other words, the cost of the service or drug must be paid by someone, whether it is the member, the insurer, or the provider.
Financial liability is the amount a given party is responsible for paying, not necessarily the amount that anyone has paid at any given time.
The amount billed represents the total cost or liability for the service.
This amount is shared among the provider, the insurer, and the member.
In fact, the total liability for a given service is the sum of the provider's liability, the insurer's liability and the member's liability.
The primary equation for calculating liability is as follows:
#Calculate liability
We calculate liabilities for each party in an ExplanationOfBenefit resource.
Get liabilities by referencing the relevant field in the total[]
array.
Basic Example
Javascript
const ACCESS_TOKEN="flexpa-link-access-token"
const response = await fetch("https://api.flexpa.com/fhir/ExplanationOfBenefit/123", {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${ACCESS_TOKEN}`,
},
});
const eob = await response.json();
const findValueByCode = (total, code) => {
return total.find((total) => total.category.coding
.some((coding) => coding.code === code))
.amount?.value;
};
const providerLiability = findValueByCode(eob.total, "discount");
const insurerLiability = findValueByCode(eob.total, "benefit");
const memberLiability = findValueByCode(eob.total, "memberliability");
console.log(providerLiability);
// Output: 15.00
console.log(insurerLiability);
// Output: 80.00
console.log(memberLiability);
// Output: 5.00
However in practice, payers do not always explicitly provide submitted
, discount
, benefit
, and memberliability
values in the ExplanationOfBenefit resource.
For select payers, Flexpa ensures that all codes are present in the data via a normalization process.
For other payers, we will need to infer these values from other provided adjudication values.
There are multiple paths to calculate adjudication values in an ExplanationOfBenefit resource.
The following equations describe other relationships between adjudication values.
#Adjudication Equation
Simply put, an insurer will need to decide the eligible
amount for reimbursement.
The eligible
amount is equal to or less than the amount submitted
by the provider.
Sometimes, insurers will receive a discount
from the healthcare provider as part of a pre-negotiated contract.
The noncovered
amount is the amount that the insurer deems to be not covered by the plan.
#Member Liability Equation
The member is liable for all costs that are noncovered
by the insurer.
Additionally, the member may have to pay a copay
or coinsurance
amount.
The deductible
is the amount the member must pay before hitting the deductible minimum amount, as defined in their plan benefits summary.
The memberliability
is the sum of these elements.
To better understand these adjudication codes, see the Adjudication codes section below.
#Payments
Payments are the amounts that have been paid by the insurer, the member, or the provider at a given point in time.
For example, payments can have a status of paid
, denied
, or partiallypaid
.
These values should be considered in the context of the timeline of care.
For example, a typical interaction could look something like this:
- The provider renders a service to the member.
- The provider submits a claim to the insurer.
- The insurer processes the claim and determines the amount it will pay.
- The insurer sends reimbursement payments to the provider.
- The member receives a bill and pays the provider the amount they owe.
Or in the context of a pharmacy claim:
- The member buys and pays for a drug from the provider (aka. the pharmacy).
- The provider submits a claim to the insurer.
- The insurer processes the claim and determines the amount it will pay.
- The insurer sends reimbursement payments to the member and pharmacy.
#Calculate payments
In the context of care or treatment, the following 2 parties will make payments for a service or drug:
- The insurer (aka. payer)
- The member (aka. patient)
#Insurer Payments
To calculate the amount paid by the insurer at the time of ExplanationOfBenefit creation, you can use the following equation:
#Member Payments
To calculate the amount paid by the member at the time of ExplanationOfBenefit creation, you can use the following equation:
For more detail on these codes, see the Adjudication codes section below.
#Accumulators
In health insurance, an accumulator is a running total of the amount of money that the member has paid towards the following two plan milestones in a benefit year:
- Deductible Minimum - The amount the member must pay for covered services before the insurer starts to pay. You can think of this as a member spend floor.
- Out of Pocket Maximum - The maximum amount the member must pay before the insurer pays 100% of covered services. You can think of this as a member spend ceiling.
#What counts towards an accumulator?
Importantly, accumulators only apply to covered services.
For example, if a member pays for a service that is not covered (ie. noncovered
) by their plan, that amount will be excluded from the accumulator.
The calculation also excludes any premiums paid by the member.
Rather the accumulator amount is determined by looking at the deductible
, copay
, and coinsurance
amounts paid by the member.
| Deductible | Copay | Co-insurance | Non covered | Premiums |
---|
Counts towards Deductible Min? | | | | | |
Counts towards OOP Max? | | | | | |
Typically insurers do not charge copay or co-insurance amounts before the deductible minimum is met.
For example, let's say your plan's eligible cost for a doctor's office visit is $100. Your copay for a doctor's visit is $20.
- If you have not met your deductible: You pay $100, the full eligible amount for the visit and no copay amount.
- If you have met your deductible: You pay a copay of $20, usually at the time of the visit.
- If you have met your out-of-pocket maximum: You pay $0, not even a copay amount.
#What is a benefit year?
A benefit year is a 12-month period during which a member's health insurance plan provides coverage.
The benefit year can start on any date.
- You can find the benefit year start date in the
period
field of the Coverage
resource.
- Then calculating the benefit year end date is simple: Add 1 year to the benefit year start date.
While Coverage.period.end
is a valid FHIR field, based on analysis of real data, the Coverage.period.start
field is the most reliable field to derive both the benefit year start and end dates.
This is because payers do not always make Coverage.period.end
present, and the value in Coverage.period.end
field is not always observed to be same as the benefit year end date.
Not all health insurance plans have a 12-month period.
Many health plans offer coverage for a year, but some plans, called short-term limited duration plans, offer coverage for less than 12 months.
These plans are often designed to fill gaps in coverage and may offer fewer benefits and less consumer protection than other plans.
For simplicity, we assume a 12-month benefit year in this guide.
Finding the benefit year
const ACCESS_TOKEN = "flexpa-link-access-token";
const response = await fetch("https://api.flexpa.com/fhir/Coverage", {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${ACCESS_TOKEN}`,
},
});
const bundle = await response.json();
const getYearStart = (start: string | undefined) => start && new Date(start);
const getYearEnd = (start: string | undefined) => {
if (!start) return;
const end = new Date(start);
end.setFullYear(getYearStart(start).getFullYear() + 1);
return end;
};
const formatDate = (date: Date) => date.toISOString().split('T')[0];
const currentCoverage = bundle.entry.find((entry) => {
const start = getYearStart(entry.resource.period.start);
const end = getYearEnd(entry.resource.period.start);
return start.getTime() <= Date.now() && end.getTime() >= Date.now();
})?.resource;
const benefitYearStart = currentCoverage && getYearStart(currentCoverage.period?.start);
const benefitYearEnd = currentCoverage && getYearEnd(currentCoverage.period?.start);
console.log(benefitYearStart && formatDate(benefitYearStart));
// Output: 2024-04-01
console.log(benefitYearEnd && formatDate(benefitYearEnd));
// Output: 2025-04-01
#Sum accumulators
To sum accumulator amounts across multiple ExplanationOfBenefit resources, we need to search over the benefit year and use the following:
accumulators.deductible
= Σ deductible
accumulators.oop
= Σ (copay
+ coinsurance
+ deductible
)
Where accumulators.deductible
is the member's covered plan spend towards the deductible minimum and accumulators.oop
is the member's covered plan spend towards the out-of-pocket maximum.
Accumulators are typically calculated for a benefit year, so it's suggested to only use EOBs during that period.
Using a reduce
function, we can sum the accumulators across multiple ExplanationOfBenefit resources.
JSON
{
"accumulators": {
"deductible": 210,
"oop": 500
}
}
Summing accumulators across ExplanationOfBenefit resources
Typescript
type Accumulators = {
dedudctible: number;
oop: number;
};
if (!benefitYearStart || !benefitYearEnd) {
throw new Error('Benefit year could not be calculated');
}
const eobResponse = await fetch(
`https://api.flexpa.com/fhir/ExplanationOfBenefit
?created=gte${formatDate(benefitYearStart)}
&created=lt${formatDate(benefitYearEnd)}`, {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${ACCESS_TOKEN}`,
},
});
const eobBundle = await eobResponse.json();
const findValueByCode = (total, code) => {
return total.find((total) => total.category.coding
.some((coding) => coding.code === code))
.amount?.value;
};
const accumulators = bundle.reduce(
(acc: Accumulators, eob: ExplanationOfBenefit & { total: ExplanationOfBenefitTotal[] }) => {
const deductible = findValueByCode(eob.total, "deductible");
const copay = findValueByCode(eob.total, "copay");
const coinsurance = findValueByCode(eob.total, "coinsurance");
const oop = copay + coinsurance + deductible;
return {
deductible: acc.deductible + deductible,
oop: acc.oop + oop,
};
},
{
deductible: 0,
oop: 0,
},
);
console.log({ accumulators });
#Accumulators by claim type
Summing accumulators values by claim type is a common practice to calculate financial information.
For example, you might be interested in how insurer liability and member liability differ between institutional
, professional
and pharmacy
claims.
Types of claims can be found in the type
field of the ExplanationOfBenefit resource.
The type
field is a Coding
data type with a system
of http://terminology.hl7.org/CodeSystem/claim-type.
The type
field can have the following values:
Code | Title | Description |
---|
institutional | Institutional | Claims for services provided in an institutional setting, such as a hospital or nursing facility. |
pharmacy | Pharmacy | Claims for prescription drugs. |
professional | Professional | Claims for services provided by a healthcare professional, such as a doctor or nurse. |
vision | Vision | Claims for vision services, such as eye exams or glasses. |
oral | Oral | Claims for oral health services, such as dental exams or cleanings. |
#Adjudication codes
The ExplanationOfBenefit resource provides adjudication codes that can be used to calculate financial liability and payment information.
Adjudication codes contain a breakdown of the adjudication details from the processing of a claim.
#What are the adjudication codes?
Payers generally standardly encode adjudication codes according to the CARIN Blue Button 2.0 Adjudication Value Set, which is a combination of adjudication code systems from base FHIR and CARIN BB.
#Liability codes
Code | Title | Description |
---|
submitted | Submitted Amount | The total submitted amount for the claim or group or line item. |
noncovered | Noncovered | The portion of the cost of this service that was deemed not eligible by the insurer because the service or member was not covered by the subscriber contract. |
eligible | Eligible Amount | Amount of the change which is considered for adjudication. |
discount | Discount | The amount of the discount that the payer has pre-negotiated with the provider. |
benefit | Benefit Amount | Amount payable under the coverage. |
coinsurance | Coinsurance | The amount the insured individual pays, as a set percentage of the cost of covered medical services, as an out-of-pocket payment to the provider. Example: Insured pays 20% and the insurer pays 80%. |
copay | CoPay | The amount the insured individual pays, as a set amount for a covered medical service, as an out-of-pocket payment to the provider. Example: A doctor's visit has a copay of $20, payable during your visit. |
deductible | Deductible | Amount deducted from the eligible amount prior to adjudication. |
memberliability | Member liability | The amount of the member's liability. |
drugcost | Drug cost | Price paid for the drug excluding mfr or other discounts. It typically is the sum of the following components: ingredient cost, dispensing fee, sales tax, and vaccine administration |
#Payment codes
Code | Title | Description |
---|
priorpayerpaid | Prior payer paid | The reduction in the payment amount to reflect the carrier as a secondary payer. |
paidtopatient | Paid to patient | The amount paid to patient. |
paidtoprovider | Paid to provider | The amount paid to the provider. |
paidbypatient | Paid by patient | The total amount paid by the patient without specifying the source. |
paidbypatientcash | Paid by patient - cash | The amount paid by the patient using cash, check, or other personal account. |
paidbypatientother | Paid by patient - other | The amount paid by the patient using a method different than cash (cash, check, or personal account) or health account. |
paidbypatienthealthaccount | Paid by patient - health account | The amount paid by the patient using another method like HSA, HRA, FSA or other type of health account. |
#Flexpa codes
Additionally, Flexpa has added the following payment adjudication codes for convenience.
These codes are not part of the CARIN BB Adjudication Value Set, and have the code system http://flexpa.com/fhir/adjudication
.
Code | Title | Description |
---|
paidbypayer | Paid by payer | The total amount paid by the payer without specifying who was paid. |
owedbypatient | Owed by patient | The amount owed by the patient. In other words, the patient's payable amount. |
owedbypayer | Owed by payer | The amount owed by the payer. In other words, the payer's payable amount. |
#Code normalization
Fortunately, the Flexpa API normalizes financial codes in ExplanationOfBenefit resources so that you don't have to.
Behind the scenes, Flexpa completes the following steps:
- Parses the payer's original ExplanationOfBenefit resource to extract adjudication values.
- Translates the adjudication codes provided by each payer to a standard set of codes.
- Calculates missing financial data based on the Liability and Payment Equations.
- Inserts the calculated values into the ExplanationOfBenefit resource.
- Returns the normalized ExplanationOfBenefit resource to you.
This process makes it easier for you to access financial information from ExplanationOfBenefit resources.
Currently this feature normalizes the total[]
array. Normalizing the item.adjudication[]
array is on the roadmap.
This feature currently supports the following endpoints.
Label | Live Mode | Test Mode |
---|
Flexpa Sample Patient | medplum-live | medplum-test |
Anthem | anthem | elevance-test |
Humana | humana | humana-test |
Medicare | medicare-gov | medicare-gov-test |
Flexpa's Financial Normalization is a complex process that requires a deep understanding of the FHIR standard, the CARIN BB Adjudication Value Set and analysis of real-world data.
We are working to expand this feature to more payers.
#Where are adjudication codes located?
Adjudication codes are potentially available in 3 different locations within an ExplanationOfBenefit resource:
FHIR Path | Description | Fill rate in real data |
---|
total[] | Sum of all line items | 96% |
item.adjudication[] | Adjudication per line item | 88% |
adjudication[] | Header-level | 27% |
The total[]
array is the most reliable way to calculate financial information in an ExplanationOfBenefit, given that it is present in over 96% of ExplanationOfBenefit resources analyzed by Flexpa.
Additionally, to obtain adjudication values per line item in an ExplanationOfBenefit, item.adjudication[]
is a feasible option, as it is present in over 88% of ExplanationOfBenefit resources analyzed by Flexpa.
The header-level adjudication[]
array, present in only 27% of ExplanationOfBenefit resources analyzed by Flexpa, has been observed to typically contain codes from http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudicationDiscriminator
with qualitative information about reasons for adjudication.
The location of the adjudication values in an ExplanationOfBenefit resource can vary depending on the payer and the type of claim.
We recommend checking each location to ensure you have the most complete set of adjudication values.
Locate adjudication values
Javascript
const ACCESS_TOKEN = "flexpa-link-access-token";
const response = await fetch("https://api.flexpa.com/fhir/ExplanationOfBenefit/123", {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${ACCESS_TOKEN}`,
},
});
const eob = await response.json();
let adjudicationValues;
if(eob.total.length > 0) {
adjudicationValues = eob.total;
} else if(eob.item.length > 0) {
adjudicationValues = eob.item.flatMap((item) => item.adjudication);
} else {
adjudicationValues = eob.adjudication;
}