Measured Behaviour

Does obfuscation break component expiry dating?

There is a one-line fix for a number that has lost its name: require it to be finite before you use it, and treat a non-finite one as a refusal. It is cheap, it is correct, and it is worth having whether or not anything is ever obfuscated. Applied to a COMPARISON it turns a silent wrong answer into a loud stop. We measured what it does to a REDUCTION, and it does the opposite.

An expiry is not one limit

A bag of red cells does not have an expiry date the way a carton of milk does. It has several, and the one printed on the label is whichever comes first.

The base shelf life depends on what solution the unit was collected into: twenty-one days in CPD, thirty-five in CPDA-1, forty-two in an additive solution such as SAG-M or AS-3. Five days for platelets. A year for frozen plasma. If the unit has been irradiated - which is done for patients whose immune system cannot deal with donor lymphocytes - it gets twenty-eight days from the day of irradiation, and never more than it had to begin with. If the closed system has been opened, to split a unit for a neonate for instance, the clock becomes twenty-four hours, or four for platelets. And if the cold chain went out of range for long enough, the unit is dated to the day it happened, which in practice means it is discarded.

So the expiry is a MINIMUM over a set of limits, and which limits are in the set depends on what has happened to that particular unit. The most fragile units are the ones with the most limits on them - the irradiated ones, the split ones, the ones that came out of the refrigerator - and those are the units for the patients least able to tolerate a mistake.

Where the finite check goes wrong

`Math.min` with a NaN anywhere in it is NaN. Every implementation of a multi-limit expiry therefore filters the non-finite limits out before reducing, because otherwise one unreadable input makes the whole answer unusable.

That filter is the standard mitigation for a missing number. It is also, in this shape, the defect. After the filter, a limit whose input has gone missing does not poison the answer. IT LEAVES THE SET. The reduction returns the earliest of whatever remained: a real date, computed from a real limit, and days or weeks later than the truth.

This is the crucial difference between a comparison and a reduction. A comparison against NaN is false, which is loud in one direction and silent in the other but is at least a signal you can look for. A reduction over a shrunken set produces a genuine member of the set. There is no NaN on the report, no implausible value, no exception, and nothing for a range check to catch, because the date that comes back is a date the unit could legitimately have carried under a different history.

What we built and what the correct run says

Twelve units. Four ordinary ones with a single limit each. Two collected into CPD and CPDA-1 rather than an additive solution, so their real shelf lives are twenty-one and thirty-five days and not the forty-two on the front of the datasheet. Three irradiated units. One split for a neonate yesterday. Two that the cold-chain monitor recorded out of range for over an hour.

The correct run computes every limit that applies to every unit, takes the earliest, and finds six units issuable and six past a date. Our price counter - units still issuable on a date arrived at by ignoring a limit that genuinely applied to them - reads zero, and so does the volume past date. A diagnostic counter of limits that could not be computed also reads zero, which matters for what follows.

The same wrong date by two routes, one of them visible

Whether a limit APPLIES and what it EVALUATES TO are two different fields on a real record, as they have to be: a boolean says the unit was irradiated and a date says when.

We removed the DATE. The limit still enters the set, evaluates to NaN, and is filtered out of the reduction. ISSUABLE went from 6 to 8, the price counter to two units and 566 mL, and the diagnostic counter of limits that could not be computed went from 0 to 4. Two units that were four and thirteen days past their irradiation date were issuable again, dated by their base shelf life instead.

We put that back and removed the FLAG. Now the limit never enters the set at all. The counters are identical - ISSUABLE 8, two units, 566 mL - and the diagnostic counter reads ZERO. The same two units are issuable on the same wrong dates, and the one number that would have told you something was missing is itself missing.

Then we removed both. The counters are still identical and the diagnostic is back to zero. Losing both halves is QUIETER than losing the value alone. We reproduced the whole shape a second time on the storage-excursion pair, with the same result: identical counters, 494 mL past date, and the diagnostic reading 2 when the date went and 0 when the flag did.

The default that is the headline figure

Forty-two days is what everybody knows about red cells, because it is the figure for the additive solutions most units are collected into. A library that has not been told which solution a unit is in picks that number, and it is not being careless: it is the modal answer.

It is wrong for exactly the units that are not in an additive solution, which are the minority. Removing the additive table and removing the additive field on the unit record produced identical results - ISSUABLE 7, two units past date, 552 mL - with the CPD unit dated to twenty-one days sitting on a shelf labelled forty-two.

That the option half and the record half landed in the same place here is worth contrasting with the compatibility table in the companion article, where they landed in opposite places. Same kind of pairing, different answer, and the difference is in what the library does when the lookup misses rather than in anything about the two names.

For the record, the loud arms behaved as loud arms do. Removing the collection date drove ISSUABLE to 2 with twelve limits uncomputable. Removing the enforcement flag made all twelve units issuable with six of them genuinely past date and 1,612 mL of component available for a patient - the largest figure in the area, and the one everybody predicts.

Why a plausibility check cannot help here

Earlier work in this series concluded that a RANGE check is the cheapest defence available against a value that has quietly gone wrong: ask whether the number could plausibly be real, and you catch the wrong unit, the wrong basis and the wrong sign.

A multi-limit expiry defeats that outright, and not by luck. Every date the reduction can return is a date some limit genuinely produced for that unit. The forty-two-day date on a CPD unit is a real date arrived at by real arithmetic on a real collection date. The base shelf life date on an irradiated unit is the date that unit would have carried had it never been irradiated. Nothing in the answer is out of range because every candidate answer is inside it.

What does work is a check on the SET rather than on the value. Count the limits you expected to compute against the limits you did compute, and refuse when they differ. That is the diagnostic counter in our fixture, and it is why the flag arm is worse than the date arm: the flag decides how many limits you expected, so losing it moves both sides of the comparison together.

What this means if you protect an inventory system

Protect the bundle. Keep the rename pattern away from the names an installed dating library reads and from the fields on the unit records you hand it, either by scoping the member pattern or by supplying an explicit member mapping.

Beyond that, this area has one design lesson that is worth having whether or not you ever obfuscate anything, because a dropped key over a serialisation hop and a partially populated import produce the same effect. Do not let a reduction silently shrink. If your expiry is the earliest of four limits, carry the count of limits you intended to apply alongside the answer, and make a mismatch a refusal rather than a smaller minimum.

And build the fixture that has something to lose. Our negative control is the same twelve units on a shelf where nothing has been irradiated, nothing has been opened and nothing has been out of range - one limit each, so there is no minimum to take. It is the shelf on an ordinary day, it is what most test data looks like, and a reduction over a set of one cannot lose a member and still have an answer.

Frequently asked questions

How can obfuscation change a blood component expiry date?

Through the names on the unit record and the dating library's options, not through the arithmetic. An expiry is the earliest of several limits, and a limit whose input name has moved is filtered out of that minimum rather than poisoning it, so the reduction returns the earliest of what remained - a real date, later than the truth.

Does requiring numbers to be finite protect an expiry calculation?

It does the opposite of what it does to a comparison. On a comparison it turns a silent wrong answer into a loud refusal. On a minimum over several limits it removes the unreadable limit from the set, and the answer that comes back is a genuine member of the set with nothing implausible about it.

Which units are most affected?

The ones with the most limits on them: irradiated units, units split from a closed system, and units that came out of the cold chain. Those are also the units for patients least able to tolerate a mistake. A unit with a single shelf-life limit has no minimum to shrink.

Why did removing the flag turn out worse than removing the date?

Because the flag decides how many limits you expected to compute. Losing the date left a diagnostic counter reading four limits uncomputable; losing the flag produced the same wrong expiries with that counter reading zero. Losing both was quieter still.

Will a range check on the expiry date catch this?

No, and not by luck. Every date the reduction can return is one some limit genuinely produced for that unit - the date it would have carried had it never been irradiated, or had it been collected into a different solution. There is no implausible value to find.

What happens to units not collected into an additive solution?

They are the population the library's default is wrong for. Removing the additive table or the additive field on the record both dated every unit at forty-two days in our fixture, which put a twenty-one-day CPD unit and a thirty-five-day CPDA-1 unit back on the issuable list - two units and 552 mL past date.

What is the design change worth making regardless?

Carry the number of limits you intended to apply alongside the expiry, and make a mismatch between intended and computed a refusal. A dropped key over a serialisation hop and a partially populated import shrink a reduction the same way a rename does, so the check earns its keep either way.

Related reading