Clinical safety

Does obfuscation break lab reference ranges?

No, not on its own. We protected a lab result classifier on five presets and every flag came out identical. Then we renamed the unit field on the result rows, and the same edit produced a false critical alarm on one analyte and released acute kidney injury as unremarkable on another.

The setup

A result classifier takes a number off an analyser feed and decides what flag it carries into the chart: normal, out of range, or critical. Critical values are held until somebody phones the ordering clinician. That is the rule that stops a panic value sitting unread in a queue.

Our module has reference ranges per analyte, sex-specific overrides for haemoglobin, a two-sided critical band, an alias table for the ways analysers spell units, and a callback that checks whether the call was actually logged.

The engine consuming it is an installed library, copied in unprotected. Ranges are expressed in each analyte's canonical unit and the engine converts incoming results into it. When a row does not say what unit it is in, the engine assumes it is already canonical, because that is the only thing a converter can do with a bare number.

Baseline first: five presets, every result row, twenty-five comparisons, all identical. Protection did not move a single flag.

One rename, two opposite outcomes

The unit on a result row is a record field, not an option key, and record fields genuinely can become unreadable. We renamed it.

This hospital reports glucose in mmol/L. The engine's canonical unit for glucose is mg/dL. A perfectly normal glucose of 5.5 mmol/L is 99 mg/dL. With the unit lost, the engine read 5.5 as mg/dL, which is profound hypoglycaemia:

glucose-normal RELEASED=false flag=critical-low held-pending-critical-call(no-call-logged-for-r-1)

A normal result became a panic value. That is loud, and somebody rings the lab.

The same hospital reports creatinine in mg/dL, and the engine's canonical unit for creatinine is umol/L. A creatinine of 5.1 mg/dL is 451 umol/L, which is acute kidney injury. With the unit lost, the engine read 5.1 umol/L, which is merely below the reference range:

creatinine-critically-high RELEASED=true flag=below-range released(... ASSUMED-CANONICAL ...)

Released. No critical call. No hold. A failing kidney reported as a slightly low creatinine.

Why the same edit went both ways

The two analytes are configured on adjacent lines and the field name is identical. What differs is which side of the vendor's canonical unit the true value sits on.

Glucose in mmol/L produces numbers smaller than the mg/dL range, so losing the unit pushes results toward the bottom of the scale and into the critical-low band. Creatinine in mg/dL produces numbers smaller than the umol/L range, so losing the unit pushes a critical-high result down through the range and out the bottom, where there is no critical band at all, because a low creatinine is not an emergency.

So the direction is not a property of the analyte, or of how important the test is, or of whether the number was high or low to begin with. It is decided by the arithmetic between two units and by whether the destination has a guard on that side.

Reviewers protect the analyte that sounds urgent. The one that opens the door is whichever one lands somewhere nobody put a rail.

The critical band, and each half's own victim

The critical low and the critical high are two comparisons in one expression. They look like one guard and behave like two independent single points of failure.

Renaming the critical low demoted a glucose of 32 mg/dL, which is severe hypoglycaemia, from critical-low to below-range. Below-range results are released without a call, so it went to the chart with no phone call attached. The critical high was untouched and kept working perfectly.

Renaming the critical high did the mirror image: the acute kidney injury became above-range and released, and a glucose of 487 mg/dL that had been held for a missing call was released as well.

Neither half masks the other, because they guard disjoint cases. There is no redundancy to fall back on and nothing anomalous to detect. Each loss simply has its own patient.

The alias table, and a spelling nobody looks at

Analysers do not agree on how to spell units. Ours emits creatinine as mg/dl with a lowercase L, and the canonical spelling is mg/dL. An alias table maps one to the other.

Rename the option key carrying that table and the library substitutes its default, which is no aliases at all. The engine no longer recognises mg/dl, assumes the row is canonical, and a normal creatinine of 80 umol/L is reported as 0.9 umol/L and flagged below range.

This is a small, unglamorous configuration item that most reviews would not look at twice. It sits directly upstream of every number the system interprets.

The calls that were and were not made

The critical-call gate behaved the way an enabling flag in front of a strict control always does in this series. Renaming the flag, renaming the verifier callback, or renaming both produced the same thing: critical results released unannounced, and the union identical to the flag alone because the flag is consulted first.

In the flag arm the configuration still reported the verifier as caller-supplied and checking the call log. That report is accurate. The verifier is present, bound and correct. It is simply never called.

That is why the question is not whether the safety check is configured. It is whether anything downstream of the thing that broke is what would have told you.

The verifier's own return field went the other way. It is worded as a permission, so losing it caused every critical result to be held, including the ones that had been called properly. Loud, disruptive, recoverable, and much the better failure of the two.

What to do about it

Keep the unit attached to the value. A result row whose unit can go missing independently of the number it describes is the whole precondition for this failure, whatever caused the field to disappear. Normalising at the boundary where the analyser feed enters the system removes it entirely.

Scope member renaming so it cannot reach fields on records that cross a boundary, and remember that a record which arrives as parsed JSON only moves on one side. When your own code both writes and reads a field, a rename moves both and you will measure no change at all, which is why a test built from an object literal can pass while production fails.

And give every critical band an explicit guard on both sides, or accept that the side without one is the direction your failures will take.

Frequently asked questions

Does obfuscating a lab result classifier change the flags it assigns?

Not by itself. We protected the classifier on five presets covering the ES5 target, the modern target and the string transforms, and compared every result row against the unprotected run. All twenty-five comparisons matched. Flags moved only when member renaming reached the fields the installed engine reads.

How can one rename produce both a false alarm and a missed emergency?

Because the direction depends on the ratio between the reported unit and the engine's canonical unit for that analyte, and the two analytes sat on opposite sides of it. Glucose in mmol/L reads low against a mg/dL scale and triggered a false critical. Creatinine in mg/dL reads low against a umol/L scale and fell below the range, where no critical band exists.

Why does a missing unit not simply throw?

Because a converter cannot tell the difference between a row with no unit and a row already in its canonical unit. Assuming canonical is the only behaviour available to it. The result is an ordinary finite number that passes every downstream check, which is precisely why nothing objects.

Are the critical low and critical high two layers of protection?

No. They are two comparisons in one expression that guard entirely different situations, so neither can cover for the other. Losing one does not degrade a shared safety net; it removes the only guard on that side of the range while the other half keeps reporting correctly, which is what makes it hard to notice.

Our checks report that the critical-call verifier is configured. Is that reassuring?

Not on its own. In our measurement that report stayed accurate while the verifier was never invoked, because the enabling flag in front of it had been lost. Any reasoning of the form we would know if this broke only holds when the thing that would tell you is not downstream of the thing that broke.

What is the safest way to word a verifier's return value?

As a permission rather than a prohibition. Our call verifier returns whether the call was made, so losing that field held every critical result including correctly handled ones. That is disruptive and immediately visible. Had it returned whether the result was blocked, losing it would have released everything silently.

How much harder does obfuscation make clinical rules to extract?

It raises the cost of casual reading and of automated extraction from your bundle, which is a reasonable goal for logic you would rather competitors and scrapers not lift. It is not a substitute for keeping consequential decisions server-side, because the code still executes in a browser the user controls.

Related reading