Chapter 6
How a WIA-BIO-011 sensor moves from its first calibration through routine measurement, quality control and recalibration, and what the reference SDK does and does not enforce along the way.
The earlier chapters defined what a biosensor measures (Chapters 1 and 2), how its performance is expressed as numbers (Chapter 3), how those numbers are stored (Chapter 4) and which SDK and CLI calls produce them (Chapter 5). This chapter puts those pieces in order. It answers the questions a laboratory supervisor or a device firmware engineer asks on an ordinary working day: in what state is the sensor now, when must it be calibrated again, which result may be released, and what happens when a control sample falls outside its limits.
A word on sources is needed first. The WIA-BIO-011 repository contains a file called PHASE-3-PROTOCOL.md, but that file is generic template text. It carries the identifier of a different standard, its heading does not match its file name, and it describes no biosensor procedure at all. This chapter therefore does not use it. The protocol presented here is built from the parts of the standard that really do describe operation: spec §7 (calibration procedures, validation, recalibration frequency and quality control), §5.4 and §6.5–§6.9 (temperature compensation, selectivity, response time, accuracy, precision and stability), the Quality Assurance list in the README, and the state and error behaviour of the reference implementation BiosensorSDK. Where the book goes beyond these sources, for example by proposing state transitions that v1.0 lacks or by mapping the QC rules to Westgard-style rules, the text labels it as book guidance.
The Biosensor record defined in Chapter 4 carries a status field with six permitted values: active, calibrating, measuring, idle, error and expired. A status field is only useful if every program that touches the sensor agrees on when it changes. The type definition lists the values but does not say how the sensor moves between them, so the behaviour has to be read from the SDK code.
In the reference SDK only three of the six values are ever assigned. createSensor starts a new sensor in idle. calibrateSensor sets calibrating at the start of the call and returns the sensor to idle when the fit is accepted. measureAnalyte sets measuring and returns to idle after storing the result. The values active, error and expired exist in the type but no code path sets them.
The consequence shows up on the failure paths. Every check in these two methods runs after the status has been changed. If a calibration is rejected because it has fewer than five points (BIO010) or because R² is below 0.99 (BIO011), the exception leaves the sensor in calibrating. If a measurement is refused because the sensor has expired (BIO007), has never been calibrated (BIO003) or its calibration has lapsed (BIO004), the sensor stays in measuring. A dashboard that reads status will then show a sensor busy with a task that ended in an exception. Two other lifecycle limits are recorded but never acted on: lifetime.maxMeasurements is stored but not compared with measurementCount, and a failed QC result does not change the sensor state at all.
There is also a subtle point in how the lifetime is created. createSensor attaches a default lifetime (180 days, 1,000 measurements, "2-8°C, dry") only when the string in biorecognitionElement contains oxidase or ase. A sensor configured with an enzyme name such as glucose-oxidase or urease gets the default. A sensor configured with the generic enum value enzyme gets no lifetime at all, and so it never expires in the SDK. Implementers should always set lifetime explicitly from the manufacturer's data rather than rely on this string match.
The following state machine is a proposal for implementers. It uses only the six status values already defined in types.ts, so records written this way remain valid v1.0 records. The main idea is that idle means "ready to release results", while two other states capture the reasons a sensor may not be used: error is recoverable by recalibration, and expired is terminal.
createSensor()
|
v
+---------> [idle] <------------------------------+
| | \ |
| cal accepted | \ measure (pre-checks pass) | result stored
| v \-----------> [measuring] -----+
| [calibrating] |
| | | BIO003 / BIO004
| cal rejected | v
| (BIO010/011)+-----------------> [error] <-- QC failure, drift alarm
| |
+------ recalibration accepted ----------+
any state --(lifetime.expirationDate passed
or measurementCount >= maxMeasurements
or BIO007)--> [expired] (terminal: replace sensor)
| Event | From | To (book guidance) | v1.0 SDK behaviour |
|---|---|---|---|
| Sensor created | — | idle, but results blocked until the first calibration | idle |
| Calibration accepted | calibrating | idle | idle |
| Calibration rejected (BIO002, BIO010, BIO011) | calibrating | error | stays calibrating |
| Calibration older than 24 h | idle | error (proactive, on a timer) | detected only at the next measurement (BIO004) |
| QC result fails | idle | error until the corrective action is complete | no change |
| Lifetime date passed or measurement count reached | any | expired | date checked only at measurement (BIO007); count never checked |
| Measurement pre-check fails | measuring | error or expired | stays measuring |
In practice this can be implemented as a thin wrapper around the SDK. The wrapper catches BiosensorError, maps each code to the target state and writes the state back. The sketch below shows the idea. It is not part of the standard.
import { BiosensorSDK } from './index'; // api/typescript/src
import { BiosensorError, BiosensorErrorCode } from './types';
const TERMINAL = new Set(['BIO007']); // sensor expired
const RECOVERABLE = new Set(['BIO002', 'BIO003', 'BIO004', 'BIO010', 'BIO011']);
function guarded<T>(sdk: BiosensorSDK, sensorId: string, op: () => T): T {
const sensor = sdk.getSensor(sensorId);
const lt = sensor.lifetime;
if (lt && sensor.measurementCount >= lt.maxMeasurements) {
sensor.status = 'expired'; // v1.0 never checks this
throw new BiosensorError(BiosensorErrorCode.SENSOR_EXPIRED,
'Measurement budget exhausted');
}
try {
return op();
} catch (e) {
if (e instanceof BiosensorError) {
if (TERMINAL.has(e.code)) sensor.status = 'expired';
else if (RECOVERABLE.has(e.code)) sensor.status = 'error';
}
throw e;
}
}
Spec §7.1 sets four minimum requirements for a multi-point calibration: at least five calibration points, coverage of the entire analytical range, inclusion of a blank (zero concentration) and duplicate measurements at each point. It then lists the standards as C1 = LOQ, C2 = 0.25 × Cmax, C3 = 0.5 × Cmax, C4 = 0.75 × Cmax and C5 = Cmax.
Read together, these clauses describe six concentrations, not five: a blank plus five non-zero levels. The distinction matters because the SDK counts differently. calibrateSensor checks only that the array standardConcentrations has at least STANDARD_CALIBRATION_POINTS (five) entries, and the blank counts as one of them. A set such as 0, 7.5, 15, 22.5 and 30 mM passes the SDK check but does not satisfy §7.1, because it has only four non-zero levels and no standard at the LOQ. This book treats "blank + five levels" as the normative design and the SDK check as a lower bound that catches only gross mistakes.
The LOQ standard also sets the bottom of the reportable interval. The SDK computes linearRange.min as the lowest non-zero standard, and any result below it is flagged. A calibration whose lowest standard is well above the LOQ therefore throws away part of the useful range. Conversely, a first standard placed below the true LOQ produces a point whose own imprecision can dominate the low-end residuals.
| Level | §7.1 definition | Example: glucose, Cmax = 30 mM (illustrative) | Purpose |
|---|---|---|---|
| Blank | zero concentration | 0 mM (buffer or analyte-free matrix) | intercept, blank SD for the LOD |
| C1 | LOQ | 0.061 mM (10σ/S with σ = 0.007 µA, S = 1.15 µA/mM from Appendix A.1) | lowest reportable value |
| C2 | 0.25 × Cmax | 7.5 mM | lower mid-range |
| C3 | 0.5 × Cmax | 15 mM | centre; best-determined region of the line |
| C4 | 0.75 × Cmax | 22.5 mM | upper mid-range; first place curvature appears |
| C5 | Cmax | 30 mM | upper limit of the reportable interval |
The duplicates required by §7.1 raise a practical question, because the SDK accepts one signal per concentration. CalibrationPoint has replicates and stdDev fields, but calibrateSensor does not fill them. There are two reasonable approaches. The first passes each replicate as its own point, repeating the concentration value. The regression then sees 12 points instead of six, the residual standard deviation has more degrees of freedom, and replicate scatter becomes visible in qualityControl.residuals. The second passes the mean of each level and stores the individual replicates in points[i].replicates after the call. This book recommends the first approach for the weekly full calibration and the second for routine daily calibration, where speed matters more. Whichever is chosen, the record must say which, because the R² values of the two approaches are not comparable.
// Full calibration, blank + 5 levels, duplicates passed as separate points
const standards = [0, 0, 0.061, 0.061, 7.5, 7.5, 15, 15, 22.5, 22.5, 30, 30];
const signals = readDuplicates(standards); // from the reader, in µA
const cal = sdk.calibrateSensor({
sensorId: sensor.id,
standardConcentrations: standards,
measuredSignals: signals,
concentrationUnit: 'mM',
signalUnit: 'µA',
calibrationType: 'linear', // the SDK fits a straight line whatever this says
});
One design caution comes from the enzyme kinetics in Chapter 1. A glucose oxidase electrode follows a saturating response whose apparent Michaelis constant depends on the membrane. With the SDK default GOX_KM of 33 mM, a 0–30 mM range is not linear, and a design that spans it will fail the acceptance tests in the next section. Before fixing Cmax, the developer should confirm from preliminary data that the chosen range lies in the linear part of the response curve.
Spec §7.2 gives four acceptance tests. The fit must have R² > 0.99 (with a target of 0.995). The residuals must be randomly distributed around zero with no systematic pattern. The relative standard deviation of replicates must be below 5 % at each level with n ≥ 3. Spiked samples must show 90–110 % recovery. The SDK automates only the first test, at the lower 0.99 threshold. It computes the residuals and stores them with maxResidual, but it always sets qualityControl.passed to true once R² is accepted. The other three tests are the operator's responsibility.
| Test (spec clause) | Criterion | Enforced by SDK? | How to perform it |
|---|---|---|---|
| Linearity (§7.2.1) | R² > 0.99; target > 0.995 | Yes, 0.99 only (BIO011) | automatic; add a warning below 0.995 |
| Residual pattern (§7.2.1) | random around zero, no trend | No; residuals stored | inspect signs and magnitudes of qualityControl.residuals |
| Precision (§7.2.2) | RSD < 5 % per level, n ≥ 3 | No | triplicates at each level; compute CV per level |
| Accuracy (§7.2.3) | recovery 90–110 % | No | spiked samples measured against the new calibration |
Note that §7.1 asks for duplicates while §7.2.2 asks for n ≥ 3. The two are compatible if duplicates are the routine minimum and triplicates are used whenever precision is being demonstrated, for example in the weekly full calibration and during validation (Chapter 8).
R² measures how much of the variance the line explains. It says nothing about whether a line is the right model. A calibration can pass R² comfortably and still be biased by several percent in the middle of its range. The following example is constructed for illustration. It uses a glucose electrode with a diffusion-limiting membrane that raises the apparent Km to 100 mM, and a nominal low-concentration slope of 1.15 µA/mM. Seven standards from 0 to 30 mM follow a Michaelis–Menten curve exactly, with no noise at all.
| Standard (mM) | Signal (µA) | Residual from the straight line (µA) | Back-calculated (mM) | Error |
|---|---|---|---|---|
| 0 | 0.00 | −1.02 | −1.15 | — |
| 5 | 5.48 | +0.06 | 5.07 | +1.3 % |
| 10 | 10.45 | +0.62 | 10.71 | +7.1 % |
| 15 | 15.00 | +0.77 | 15.87 | +5.8 % |
| 20 | 19.17 | +0.53 | 20.60 | +3.0 % |
| 25 | 23.00 | −0.05 | 24.95 | −0.2 % |
| 30 | 26.54 | −0.91 | 28.96 | −3.5 % |
The least-squares line is signal = 1.015 + 0.8813 × C, with R² ≈ 0.9943. The SDK accepts this calibration, since 0.9943 is above its 0.99 limit. Three things show that it should be rejected. First, R² is below the spec target of 0.995. Second, the residuals form an arch: negative at both ends, positive in the middle. That is the signature of curvature, and it is exactly the systematic pattern §7.2.1 forbids. Third, the line misplaces the blank by more than 1 mM and over-reads 10 mM by about 7 %, which is outside the ±5 % accuracy target of §6.7. The example also shows why the intercept deserves attention. A large positive intercept on an amperometric sensor, whose blank current should be small, is often the first sign that the line is bending.
When this pattern appears, there are three remedies, in order of preference. Narrow Cmax until the residuals become random. Change the sensor chemistry (for example a thicker diffusion membrane) so that the linear region widens. Or move to a non-linear model. The last option is not yet available end to end in v1.0. calibrateSensor always fits a straight line, and measureAnalyte throws BIO002 for any type other than linear. A 4PL or Michaelis–Menten calibration must therefore be fitted and inverted outside the SDK, and stored with the appropriate type value for the record.
A different residual pattern appears with the spec's own Appendix A.2 data (standards 0, 1, 5, 10, 50 and 100 mM). Recomputed, the fit gives a slope of about 1.1585 µA/mM, an intercept of about 0.047 µA and R² ≈ 0.99999, with residuals of about +0.003, −0.006, +0.010, −0.133, +0.226 and −0.100 µA. The residuals have no arch, but their size grows with concentration. That is heteroscedasticity, not curvature, and it is common for amperometric sensors whose noise scales with the current. It does not make the calibration invalid. It does mean that an unweighted fit gives the high standards too much influence over the low end. Weighted regression (for example with weights of 1/x or 1/x²) is the usual answer. The SDK does not offer it, so it is another step to perform outside the SDK when the low end matters.
A measurement run turns one raw signal into a reportable concentration. The order of operations matters because each step assumes the previous ones are complete. This book divides the run into five stages: pre-checks, signal processing, inversion, flagging and, where needed, dilution.
Before a signal is accepted, three conditions must hold. The sensor must be within its lifetime. The calibration must be valid. The sample temperature must be controlled. The SDK checks the first two at the start of measureAnalyte: an expired lifetime.expirationDate raises BIO007, a missing calibration raises BIO003, and a calibration older than its validUntil (24 hours after it was made) raises BIO004. It does not check the measurement budget, as noted in 6.1.
Temperature needs more care. The README Quality Assurance list says the temperature should be kept within ±1 °C for enzymatic biosensors. The SDK raises its temperature-out-of-range flag only when the sample is more than 10 °C away from 25 °C. That is a sanity limit, not a control limit. The practical meaning of ±1 °C follows from the SDK default temperature coefficient of 3 %/°C. A sample that is really at 26 °C but recorded as 25 °C receives no correction, and its result reads about 3 % high. That uses more than half of the ±5 % accuracy budget of §6.7 before any other error is counted. An implementation should either control the cell temperature to ±1 °C or record the actual temperature for every measurement so that compensation is applied. It should also raise its own warning when the temperature is outside the tighter window.
The SignalProcessingParams type anticipates four processing steps: baseline correction, temperature correction, noise filtering (moving average, Savitzky–Golay, low-pass or median) and drift compensation (a drift rate in signal per hour multiplied by the time since calibration). The logical order is the one just given. The baseline is removed first because it is additive and independent of the analyte. Temperature correction is multiplicative and acts on the analyte-related part only. Filtering then smooths what remains, and drift compensation removes the slow change since the last calibration.
The v1.0 SDK implements only the first two steps, in its private processSignal method, and measureAnalyte passes it only the temperature. No baseline is ever subtracted inside a measurement. The filtering and drift-compensation fields are declared in the type but ignored. A reader therefore has to subtract the baseline and apply any filtering before it calls measureAnalyte, and it has to handle drift by recalibrating on schedule rather than by correction.
Temperature compensation also needs a declared convention. As Chapter 3 showed, spec §5.4 writes the compensated signal as the signal multiplied by [1 + α(T − Tref)], whereas the SDK and CLI divide by the same factor. This book follows the SDK convention: α is the fractional increase in sensor response per degree, so the measured signal is divided by [1 + α(T − Tref)] to refer it back to 25 °C. Whichever convention a product uses, it should record α and Tref with the calibration so that the correction can be reproduced.
With the corrected signal y, the SDK computes the concentration as C = (y − a)/b from the stored intercept a and slope b. Using the recomputed Appendix A.2 line, a signal of 23.5 µA at 25 °C gives C = (23.5 − 0.047)/1.1585 ≈ 20.2 mM. The spec prints 20.3 mM because it rounds the regression coefficients differently. The same 23.5 µA recorded at 26 °C would be divided by 1.03 first and give about 19.7 mM. The 0.6 mM difference shows why the temperature must be recorded.
The SDK then attaches quality flags. Two behaviours affect the protocol. First, the array always starts with ok, and warnings are appended to it rather than replacing it. Consumers must therefore test whether any flag other than ok is present, not whether ok is present. Second, any result below the lowest non-zero standard is flagged below-lod, even when it is above the actual LOD. For release decisions, this book recommends the following interpretation.
| Condition after inversion | SDK flag(s) | Recommended reporting action |
|---|---|---|
| C < LOD | below-lod | report "not detected (< LOD)", with the LOD value |
| LOD ≤ C < LOQ | below-loq (and below-lod if below the lowest standard) | report "detected, below LOQ"; no numeric value for clinical use |
| LOQ ≤ C < lowest standard | below-lod (misleading) | report with a comment, or extend the calibration downwards |
| Within the calibrated range | ok only | release |
| C > highest standard | above-range | dilute and re-measure (6.4.4) |
| |T − 25 °C| > 10 °C | temperature-out-of-range | reject; book guidance tightens this to ±1 °C of the controlled set point for enzymatic sensors |
The flags for drift, noise and interference exist in the QualityFlag vocabulary, but the SDK never sets them. If an implementation detects these conditions, for example a QC trend, noise beyond a threshold on the raw trace or a standard-addition check that disagrees with the external calibration, it should add these flags itself so that the record carries the reason.
A result flagged above-range must not be reported as a number, because the calibration says nothing about the response above Cmax, and saturation makes extrapolation read low. The standard procedure is to dilute the sample by a known factor D with the calibration diluent, measure it again and multiply the result by D. The diluted result must itself fall inside the range, preferably in its middle third. MeasurementRequest has no dilution field, so the factor should be recorded in sample.metadata along with a link to the original over-range measurement. Dilution also changes the matrix, which is one reason the recovery check of 6.7 exists.
A valid calibration shows that the sensor was accurate at the moment it was calibrated. Quality control shows that it has stayed accurate since then. Spec §7.4 defines a three-level scheme: a low control near the LOQ, a medium control in the mid-range and a high control near the top of the range. Two acceptance criteria apply to every control: the result must be within ±10 % of the target value, and within ±2 SD of the historical mean for that control material. The QualityControlSample type also allows a blank level, which is useful for detecting contamination and baseline shifts. (The README list is shorter and names only blank, low and high controls. This book follows the three-level scheme of the spec and adds the blank.)
The SDK method runQualityControl(sensorId, qcSample, measuredConcentration) does not measure anything itself. The caller measures the control like any sample, using measureAnalyte, and passes the concentration in. The method then does three things. It sets passed by checking whether the value is inside qcSample.acceptableRange. It computes the percent deviation from targetConcentration. And, if a historical mean and SD are provided, it computes the zScore. If the result has failed, it attaches one of three corrective actions depending on the size of the deviation: above 20 %, "Recalibrate sensor immediately"; above 10 %, "Run additional QC sample to confirm"; otherwise "Monitor next QC result".
Two consequences follow. The z-score is calculated but plays no part in passed, so the ±2 SD criterion of §7.4 is enforced only if the caller builds it into acceptableRange. The simplest way to do that is to use the intersection of the two windows, as in the code below. And because the QC measurement goes through measureAnalyte, it increments measurementCount and appears in getMeasurements next to patient or field results. Setting sampleType to a value such as qc-medium keeps the two kinds of result apart.
function qcRange(target: number, histMean: number, histSD: number) {
// Spec §7.4: within ±10 % of target AND within ±2 SD of the historical mean
return {
min: Math.max(target * 0.90, histMean - 2 * histSD),
max: Math.min(target * 1.10, histMean + 2 * histSD),
};
}
const medium = {
level: 'medium' as const,
targetConcentration: 10.0, // mM, illustrative control material
historicalMean: 10.1,
historicalStdDev: 0.3,
acceptableRange: qcRange(10.0, 10.1, 0.3), // { min: 9.5, max: 10.7 }
lotNumber: 'QC-M-0425',
};
const m = sdk.measureAnalyte({ sensorId: sensor.id, signal: s, temperature: 25,
sampleType: 'qc-medium' });
const qc = sdk.runQualityControl(sensor.id, medium, m.concentration);
Using the illustrative medium control above (target 10.0 mM, historical mean 10.1 mM, SD 0.3 mM, combined window 9.5–10.7 mM), five results are evaluated as follows.
| Measured (mM) | Deviation | z-score | ±10 % only | §7.4 (both criteria) | SDK action text |
|---|---|---|---|---|---|
| 10.3 | +3 % | +0.67 | pass | pass | — |
| 9.5 | −5 % | −2.00 | pass | pass (at the limit) | — |
| 10.8 | +8 % | +2.33 | pass | fail | Monitor next QC result |
| 11.6 | +16 % | +5.00 | fail | fail | Run additional QC sample to confirm |
| 12.5 | +25 % | +8.00 | fail | fail | Recalibrate sensor immediately |
The 10.8 mM row is the instructive one. It fails the spec only because this control material has a tight historical SD. With the ±10 % window alone, the SDK would have called it a pass. With the combined window, the SDK reports a failure but suggests the mildest action, because the deviation is under 10 %. The protocol should treat this as a warning that calls for a second control, not as a reason to recalibrate.
measure control (measureAnalyte, sampleType = 'qc-<level>')
|
v
inside acceptableRange (±10 % target AND ±2 SD history)?
|-- yes --> record pass; check trend (last 4-10 results same side?)
| |-- no trend --> continue patient/field runs
| `-- trend -----> flag 'drift-detected'; schedule recalibration
`-- no ---> |deviation| > 20 % ? -- yes --> status 'error'; recalibrate now;
| re-run all QC levels
no
v
|deviation| > 10 % ? -- yes --> hold results; repeat control
| (fresh aliquot); if it fails again,
no recalibrate
v
beyond ±2 SD only --> warning; run a second control level
before releasing the batch
Clinical laboratories usually express QC acceptance through multirule schemes associated with Westgard. The best known rules are 12s (one result beyond 2 SD, a warning), 13s (one beyond 3 SD, reject), 22s (two consecutive beyond the same 2 SD limit, reject), R4s (a range of more than 4 SD within a run, reject), 41s and 10x̄ (trend rules for systematic shifts). The ±2 SD criterion of §7.4 is the 12s limit applied as a rejection rule rather than a warning.
That choice has a cost that is easy to calculate. For a normally distributed, perfectly stable process, a single control falls outside ±2 SD about 4.6 % of the time. With three independent control levels in each run, the chance that at least one of them falls outside is about 13 %. Roughly one run in eight would then be rejected with nothing wrong. For a device that runs QC every 20–50 samples, that means a lot of unnecessary recalibration. A common compromise, and the one this book recommends, keeps ±2 SD as the warning trigger and uses 13s, 22s and R4s as rejection rules. The ±10 % target window remains an absolute limit. The SDK records the z-score for every QC result, so these rules can be evaluated from stored QualityControlResult records without changes to the data model.
Spec §7.3 lists four events: an initial calibration before first use, a routine calibration daily during continuous use, quality control every 20–50 samples, and a full recalibration weekly or whenever QC fails. The spec does not define how a "routine" calibration differs from a "full" one. This book reads them as follows. The routine calibration meets the SDK minimum and the §7.2.1 linearity test, and uses duplicates. The full calibration applies the whole §7.1 design with triplicates, and repeats all four §7.2 acceptance tests including spike recovery.
The SDK hard-codes one part of this schedule: every calibration expires 24 hours after it is created, and the next measurement after that is refused with BIO004. A deployment that cannot recalibrate daily, such as an unattended field sensor, has to pass its own calibration object with a longer validUntil to measureAnalyte. That deviation from the SDK default should be recorded and justified with stability data.
| Interval | Activity | Source | Record produced |
|---|---|---|---|
| Before first use | Full calibration (blank + 5 levels, triplicates); all four acceptance tests; three-level QC | §7.3, §7.1–§7.2 | CalibrationData, QualityControlResult ×3 |
| Start of each day or shift (continuous use) | Routine calibration; temperature check; blank control | §7.3; SDK 24 h expiry | CalibrationData |
| Every 20–50 samples (batch) | Low/medium/high controls bracketing the batch | §7.3, §7.4 | BatchMeasurement.qualityControl |
| On QC failure | Hold the batch; follow the decision flow in 6.5.3; recalibrate when indicated; re-run affected samples | §7.3 | new CalibrationData; corrective action recorded |
| Weekly | Full calibration with spike recovery | §7.3 | CalibrationData + recovery results |
| Per stability study time point | Sensitivity and QC on stored sensors | §6.9 (see 6.8) | PerformanceMetrics.stability |
The phrase "bracketing the batch" is worth spelling out. Controls run before a batch show that the sensor was in control when the batch started. Controls run after it show that the sensor was still in control when the batch ended. If the closing controls fail, every result since the last passing control is suspect. For this reason, the QC interval of 20–50 samples is also the maximum number of results that may have to be repeated after a failure. A laboratory with expensive or unrepeatable samples should choose the lower end.
A continuous glucose monitor cannot be taken out of the body and exposed to six standards. CGM systems are therefore calibrated either at the factory, with a sensitivity assigned to each manufacturing lot, or in use by one-point or few-point recalibration against capillary fingerstick readings. A one-point recalibration adjusts only the sensitivity (or only the offset) under the assumption that the other parameter is known. It depends heavily on the timing of the reference reading, because interstitial glucose lags blood glucose and a reading taken while glucose is changing quickly gives a wrong sensitivity.
This model does not fit the v1.0 SDK. calibrateSensor refuses fewer than five points, and a 24-hour validUntil would halt a multi-day stream. A CGM implementation should keep its calibration logic in the device or companion app, store the result as a CalibrationData record whose points list the reference readings actually used, and set validUntil to match the manufacturer's calibration policy. Chapter 7 returns to the CGM data flow.
Chapter 3 showed that the selectivity data in Appendix A.3, once normalized by concentration, give sensitivity ratios of only about 39:1 for fructose, 1.5:1 for ascorbate and 4.9:1 for urate. These are far below the §6.5 target of more than 100:1. In operation this means interference is not a theoretical issue for an amperometric glucose sensor. It has to be managed by chemistry, by protocol and by flagging.
| Interferent or matrix factor | Affected sensors | Mechanism | Operational countermeasure |
|---|---|---|---|
| Ascorbate, urate, acetaminophen | Amperometric oxidase sensors at high working potential (e.g. +0.6 V vs Ag/AgCl) | Direct oxidation at the electrode adds current unrelated to the analyte | Permselective membrane, mediator allowing a lower potential, sentinel (enzyme-free) electrode for subtraction |
| Hematocrit | Whole-blood strip sensors | Changes plasma volume fraction and diffusion in the sample layer | Hematocrit correction in the meter; restrict the claimed hematocrit range; test across the range during validation |
| Oxygen tension | First-generation oxidase sensors | O₂ is the co-substrate; low O₂ limits the response | Mediator or O₂-independent design; control sample handling |
| pH | Enzyme sensors, aptamer sensors | Enzyme activity and aptamer folding depend on pH | Buffered diluent; record pH in MeasurementRequest |
| Ionic strength, divalent cations | Aptamer and DNA sensors | Folding and hybridization depend on salt, especially Mg²⁺ | Matrix-matched calibrants; fixed buffer composition |
| Proteins, lipids (fouling) | Most electrodes in serum, blood or wastewater | Adsorbed layer reduces sensitivity over time | Anti-fouling coatings; QC trend monitoring; shorter recalibration interval |
Spec §7.2.3 defines recovery as Cmeasured/Cspiked × 100 %, which applies directly when the spike goes into an analyte-free matrix. When the spike goes into a real sample that already contains the analyte, the usual form subtracts the unspiked result: recovery = (Cspiked sample − Csample)/Cadded × 100 %. Values outside 90–110 % indicate that the sensitivity in that matrix differs from the sensitivity in the calibrants.
When a matrix effect is suspected, the method of standard addition measures the sensitivity in the sample itself. Known amounts of analyte are added to aliquots of the sample. The baseline-corrected signal is plotted against the added concentration, and the original concentration is the intercept on the concentration axis, taken as a positive value (intercept/slope). The following numbers are illustrative. For simplicity, the spikes are treated as adding negligible volume.
Added glucose (mM): 0 2 4
Baseline-corrected (µA): 6.20 8.36 10.52
Standard-addition fit: slope = 1.080 µA/mM, intercept = 6.20 µA
Sample concentration: 6.20 / 1.080 = 5.74 mM
External calibration (Appendix A.2, recomputed):
(6.20 - 0.047) / 1.1585 = 5.31 mM
Sensitivity in matrix / sensitivity in calibrant = 1.080 / 1.1585 = 0.93
=> about 7 % sensitivity suppression; the external result reads ~7 % low
In this example, the external calibration under-reads by about 7 %. That is outside the ±5 % accuracy target and would show up as recovery near 93 %. The protocol responses are to add interference-detected to the affected results, to switch to matrix-matched calibrants for that sample type, or to validate a dilution that brings the matrix effect within limits. Standard addition is laborious and is not a routine method for point-of-care devices. It is most useful during method development and when investigating QC or recovery failures.
The README Quality Assurance list advises storage according to the manufacturer's guidelines, typically at 2–8 °C, and says shelf life should be monitored for performance degradation, typically over 3 to 12 months. Spec §6.9 gives the same shelf-life window and adds two further targets: operational stability of 100–1,000 measurements and long-term drift below 5 % per month. These numbers are typical design targets. The actual shelf life of a product is whatever its own stability data support.
The data model has a place for the results. PerformanceMetrics.stability holds shelfLife in days, operationalStability in number of measurements and drift in percent per month. A stability study produces those three values as follows. Store sensors from at least one production lot at the labelled condition. At planned time points, calibrate a subset and measure QC materials. Fit the loss of sensitivity to a model such as the first-order decay S(t) = S₀e−kt from §6.9. Then define shelf life as the time at which a pre-set acceptance limit is reached. Guidance on designing such studies for IVD reagents exists outside the WIA standard, for example ISO 23640 and CLSI EP25. This is a book addition and not part of WIA-BIO-011.
| Time (days) | Sensitivity (µA/mM) | Loss vs day 0 |
|---|---|---|
| 0 | 1.158 | — |
| 90 | 1.100 | 5.0 % |
From these two illustrative points, k = ln(1.158/1.100)/90 ≈ 5.7 × 10⁻⁴ per day. The half-life ln2/k is then about 1,200 days, and the average drift is about 1.7 % per month. That half-life does not mean the sensor is usable for three years. Half-life is the time to lose 50 % of the signal, and no quantitative sensor stays within ±5 % accuracy after losing half its sensitivity without recalibration. A meaningful shelf-life criterion is a limit such as "sensitivity within 10 % of its initial value and all QC levels passing without recalibration". With the fitted k, a 10 % loss is reached at ln(1/0.9)/k ≈ 185 days. In this example the shelf life would be about six months, not three years. A two-point fit is of course only an illustration. A real study uses several time points and more than one lot, and it reports the confidence interval of the fitted rate.
Operational stability is a separate measurement. It counts how many measurements a sensor can make, or how long it can operate in the sample, before its QC fails. That number, not the calendar, should set lifetime.maxMeasurements, which the implementation must then enforce itself (6.1). Accelerated studies at raised temperature can give early estimates, but the extrapolation assumes a single degradation mechanism with Arrhenius behaviour. That assumption often fails for protein-based layers, so real-time data at the storage condition must confirm the claim.
The operating protocol of WIA-BIO-011 is short in the spec but has many consequences in practice. The table below collects the rules of this chapter as a checklist for an implementer. The third column states whether the reference SDK enforces each one. Wherever the answer is "no", the burden falls on the application built around it.
| Rule | Source | Enforced by v1.0 SDK |
|---|---|---|
| Blank + five non-zero levels, first level at LOQ | §7.1 | Partly (≥5 array entries, blank included) |
| Duplicates at each level; n ≥ 3 when proving precision | §7.1, §7.2.2 | No |
| R² > 0.99, target 0.995 | §7.2.1 | 0.99 only |
| Random residuals | §7.2.1 | No (residuals stored) |
| Recovery 90–110 % | §7.2.3 | No |
| Calibration valid for one day in continuous use | §7.3 | Yes (24 h validUntil) |
| Temperature controlled to ±1 °C (enzymatic) | README QA | No (flag at ±10 °C only) |
| Three-level QC every 20–50 samples | §7.3, §7.4 | No scheduling; per-result evaluation only |
| QC within ±10 % of target and ±2 SD of history | §7.4 | Only through the caller's acceptableRange |
| Weekly full recalibration or on QC failure | §7.3 | No |
| Lifetime and measurement budget respected | §6.9; Biosensor.lifetime | Date at measurement only; count never |
| Sensor status reflects failures | Biosensor.status | No (stays calibrating/measuring) |
Chapter 7 carries these procedures into real products: glucose meters and CGM, immunoassay point-of-care tests, lateral-flow readers and field monitors, where the calibration and QC steps have to fit the constraints of the device and its user. Chapter 8 then shows how the evidence produced by this protocol becomes a validation file and a conformance claim.