Claims and benefits

Does obfuscation break coordination of benefits?

No, not on its own. We protected a coordination-of-benefits module on five presets and every claim settled identically. Then we renamed one field, and the two plans swapped places. Nothing was dropped, no benefit changed, nothing became unreadable - and the member was billed $600 more on a single surgery.

What we actually measured

When a member is covered by two plans, one of them is primary. The primary prices the claim against its own fee schedule and pays first; the other pays against what is left. Which one is primary is decided by a rule about dates of birth or enrolment order, not by which plan pays better - so the true primary is often the thinner of the two.

The module we built holds two plans and a coordination engine, with the engine copied in unprotected as a real dependency would be. Baseline: five presets, twenty-five comparisons, twenty-five identical settlements. Protection alone changed nothing.

Everything survived except the sequence

Each payer record carries a number called priority. The engine sorts by it, because payers come back from an eligibility query in whatever order the query returned them - which is precisely why the field exists.

We renamed it. Consider carefully what did not happen. No payer was dropped. No fee schedule changed. No deductible or percentage moved. Nothing became NaN, nothing threw, and the engine's finite check - which refuses a payment that is not a number - never fired, because there was no unreadable number anywhere.

The comparator simply could not read the key. Subtracting undefined from undefined gives NaN, and a comparator returning NaN tells the sort engine to leave that pair alone, so the array kept the order the query happened to return.

surgery ... spouse-plan(PRIMARY) allowed $6000.00 pays $3000.00 then employer-plan(secondary) ... member owes $2000.00

became

surgery ... employer-plan(PRIMARY) allowed $9000.00 pays $7000.00 then spouse-plan(secondary) ... member owes $2600.00

Every figure on that second line is individually correct. $9,000 really is the employer plan's allowed amount. $7,000 really is 80% of it after the deductible. The claim settled successfully. The member is simply $600 worse off, on a claim that a reviewer would read as perfectly normal.

The routine claim shows nothing at all

We put a third claim in the file on purpose: an $800 office visit. It is small enough that both plans allow the whole amount and the spouse plan's deductible swallows its share either way.

In both orders the member owes exactly $360. Identical to the cent.

This is the part worth remembering when planning a check. A spot test on a routine claim - which is what anybody would reach for first, because routine claims are what you have most of - cannot see this failure. Only claims large enough for the two fee schedules to diverge show it, and those are the minority of your volume and the majority of your money.

The tell is real, and it is on the wrong screen

There is a visible symptom. The payer list printed:

payers=employer-plan#undefined spouse-plan#undefined

That would be obvious to anybody who looked. But it is on a configuration dump, not on the claim. The explanation of benefits shows two plans, two payments and a balance, all correct arithmetic, in an order nobody can tell is wrong by looking at it. Whether you catch this depends entirely on whether the missing value is printed somewhere a person reads, and by default it is not.

The same concept, one arm silent and one arm nothing

The engine also takes an option naming which field to sort by, and it defaults to priority. We renamed that too, as a control.

Nothing happened at all - the value we passed equals the library's own default, so substituting the default changed nothing. Identical output, byte for byte.

Two names for one idea, measured in one file: the field that carries the ordering is the worst result in the module, and the option that names it is completely inert. The severity is not a property of how important the concept sounds. It is decided by whether the name that moved is one the library replaces with a default, or one it needs to read out of your data.

What the rest of the module did

For completeness, because a pass that only reports disasters is confirming rather than measuring. Renaming the plan names moved only a label. Renaming any of the money fields - the allowed amount, the deductible, the percentage - produced NaN, and the engine's finite check refused the settlement outright and loudly. The enabling flag in front of the order rule behaved exactly as it has in every other module we measured this pass: the union of flag and rule was byte-identical to the flag alone, while the configuration still reported the rule as caller-supplied and correct.

So the money fields fail closed and shout. The ordering field fails open and whispers. That is the same split we measured on a claim record in a different module, and it keeps landing the same way: the value fails safely, and the thing that says what to do with the value does not.

What actually protects you

Do not let an ordering key be optional. If a record cannot say where it belongs in a sequence, refuse to process the set rather than falling back on arrival order. A comparator that reads a missing key should throw, not shrug - a two-line guard in the comparator turns this article's silent failure into a loud one.

Validate the order you computed, not just the result. A coordination that names a primary can assert that the primary is the plan the birthday rule actually selects. That check is cheap, runs on every claim, and is independent of how the sort was implemented.

And keep identifiers and ordering keys out of any renaming pattern. Names that describe a record are candidates for obscuring; names that place a record - identify it, group it, sequence it - are read by code you did not build, and are not.

Frequently asked questions

Does protecting a coordination-of-benefits module change what a member owes?

Not in what we measured. Five presets covering the ES5 and modern targets plus the string transforms, every claim through every build: twenty-five comparisons, twenty-five identical settlements.

How much does losing an ordering key actually change?

In our measurement, $600 on one surgery and $240 on one imaging claim. The two plans swapped places, so the claim was priced against the wrong fee schedule first. Nothing was dropped, no benefit changed and no number became unreadable.

Why did the engine's own safety check not catch it?

Because there was nothing unreadable to catch. That check refuses a payment that is not a finite number, and every payment here was finite and correct. What moved was the sequence the correct payments were computed in, which no numeric guard can see.

Would a test on a normal claim have found this?

No, and we put that case in the file deliberately. An $800 office visit settled at exactly $360 in both orders, to the cent, because it was small enough that the two fee schedules did not diverge. Routine claims are the ones you have most of and the ones least able to show this.

Was there any visible symptom?

Yes, but on the wrong screen. The configuration dump printed the payer list with undefined priorities, which anybody would notice. The explanation of benefits showed two plans, two correct payments and a correct balance in an order that cannot be judged by looking at it.

Why was the option naming the sort key completely harmless?

Because the value we passed equalled the library's own default, so substituting the default changed nothing. It is a useful contrast: one name for the concept was the worst result in the module and the other was inert, decided purely by whether the library replaces it with a default or reads it from your data.

What is the cheapest safeguard to add?

Make the comparator refuse a record with no ordering key instead of leaving the pair alone. Two lines, and it converts this silent failure into an immediate one. Asserting that the computed primary matches the plan your ordering rule selects is a good second layer.

Related reading