Chapter 7
How WIA-BIO-011 records travel from a glucose strip, a CGM patch, a lateral-flow reader or a field water sensor to a gateway, a service and a health record, and where the v1.0 data model and SDK need help along the way.
Chapters 1–6 built the pieces: chemistry and physics, metrics, record types, the SDK and CLI, and the operating protocol. This chapter puts them into products. It walks through the four application families that spec §8 names — glucose monitoring, cardiac markers, infectious-disease tests and environmental monitoring — and then follows the data out of the device, through a gateway and a service, and into an electronic health record.
This is the book's equivalent of an integration phase. The repository contains a file called spec/PHASE-4-INTEGRATION.md, but that file is generic template text: it carries the wrong standard identifier (WIA-BIO-010), its title does not match its file name, and its quality thresholds contradict the main specification. Only its general security lines and its non-normative Annex J (deployment topologies) are used here, and both are labelled as such wherever they appear. Regulatory clearance, clinical validation studies and conformance evidence are deliberately left to Chapter 8.
Three kinds of statement appear in this chapter.
spec/WIA-BIO-011-v1.0.md or types.ts. Application figures are quoted as the spec's targets, not as properties of any product.index.ts actually does, checked against the source, including behaviour that is probably unintended.
Glucose is the reference application for the whole field and the one the WIA-BIO-011 defaults were written for: the SDK's GOX_KM of 33 mM and its 3 %/°C enzyme temperature coefficient are glucose-oxidase defaults. Two very different products sit under the same analyte, and they stress the data model in different ways.
Spec §8.1 describes the self-monitoring meter with three figures: a sample volume of 0.3–1 µL, a measurement range of 1.1–33.3 mM (20–600 mg/dL) and a response time of 5–10 s. Each is a design constraint. With a sub-microlitre sample, the fill chamber often limits precision: an underfilled strip gives a low current that the meter must reject, not report. The roughly 30-fold reportable range exceeds the linear portion of many enzyme electrodes (Chapter 1), so meters use diffusion-limiting membranes and mediators to stretch linearity. The short read time is why many meters sample the current at a fixed time after fill rather than waiting for a steady state.
Whole blood adds a matrix effect that bench calibration in buffer never shows: hematocrit. Red cells occupy volume and slow diffusion to the electrode, so the same plasma glucose can give different currents at different hematocrits; meters compensate, for example by measuring a second electrical property of the sample. WIA-BIO-011 v1.0 has no field for hematocrit or any other matrix covariate on MeasurementRequest; if a reader measures one, the integrator must carry it alongside the record (see the adapter pattern in §7.5).
In the WIA data model a strip reading is one MeasurementResult. The sensor is configured with type: 'electrochemical', electrochemicalType: 'amperometric', biorecognitionElement: 'glucose-oxidase' (or a dehydrogenase string, which the BiorecognitionElement | EnzymeType | string union allows), and sampleType: 'blood'. The result carries concentrationUnit from the calibration's linearRange.unit; storing mmol/L internally and converting for display (§7.6) avoids a class of rounding errors.
A CGM is a small amperometric enzyme electrode inserted into subcutaneous tissue. Spec §8.1 gives a measurement interval of 1–5 minutes and a sensor lifetime of 7–14 days. Three physiological and engineering facts shape how its data should be handled.
The spec's CGM accuracy line ("90 % within Zone A, Clarke Error Grid") has no cited source and is not used in this book; accuracy assessment of glucose systems is covered with the correct references in Chapter 8.
The natural container for a CGM stream is TimeSeries: an id, a sensorId, an analyte, an array of TimeSeriesPoint objects (time, value, unit and an optional single quality flag) and an open metadata object with optional patientId and studyId. Each point can be produced by measureAnalyte, but four v1.0 behaviours get in the way of a naïve loop that calls the SDK every five minutes for two weeks. Table 7.1 lists them.
| Behaviour (SDK) | Effect on a 14-day CGM | Book guidance |
|---|---|---|
calibrateSensor sets validUntil to 24 h after calibration; measureAnalyte throws BIO004 after that | Every reading after day 1 fails unless calibration is renewed | For factory-calibrated sensors, build the CalibrationData object from the factory parameters with validUntil set to the end of the wear period and pass it in MeasurementRequest.calibration; for in-vivo recalibration, store each new calibration as its own record |
createSensor gives any recognition element whose string contains "ase" a default lifetime of 180 days and maxMeasurements: 1000 | The expiry check (BIO007) fires months after the patch is removed; 1,000 is far below the 4,032 readings of 14 days at 5-minute intervals, although v1.0 never enforces the count | Overwrite lifetime.expirationDate with insertion time plus the labelled wear period and set maxMeasurements to match the interval |
Temperature correction divides the signal by 1 + 0.03(T − 25) and flags temperature-out-of-range when |T − 25| > 10 °C | Passing a body-site temperature of 37 °C divides the signal by 1.36 and always raises the flag | Correct relative to the temperature at which the calibration applies, not a hard-coded 25 °C (see below) |
Results below linearRange.min are flagged below-lod; qualityFlags always keeps 'ok' | Low readings, which are the clinically important ones, can be labelled as below the detection limit when they are only below the lowest standard | Set linearRange.min to the device's validated low end and choose the point's single quality flag by severity, ignoring 'ok' when any other flag is present |
The temperature row deserves a number. The SDK treats 25 °C as the reference temperature for every calibration. A factory calibration for an implanted sensor, however, is by construction valid at body conditions. If the integrator passes the measured site temperature of, say, 37 °C, the SDK divides the signal by 1.36 and the reported concentration drops by roughly a quarter (for a small intercept) — an error introduced by the correction itself. If the integrator passes nothing, the SDK assumes 25 °C, applies no correction and writes temperature: 25 into the result, which is false. Neither is acceptable. The book recommends a thin adapter that applies any correction relative to the calibration's own reference temperature, calls measureAnalyte with temperature: 25 so that the SDK leaves the signal alone, and then writes the true temperature back into the result. Chapter 3 also notes that the spec writes the correction as a multiplication while the SDK divides; the adapter should document which convention it uses.
// Book guidance, not part of @wia/bio-011 v1.0.
import { BiosensorSDK, MeasurementResult, QualityFlag, TimeSeriesPoint } from '@wia/bio-011';
const SEVERITY: QualityFlag[] = [
'sensor-expired', 'calibration-expired', 'above-range', 'below-lod',
'below-loq', 'interference-detected', 'high-noise', 'drift-detected',
'temperature-out-of-range', 'ok',
];
function worstFlag(flags: QualityFlag[]): QualityFlag {
const real = flags.filter(f => f !== 'ok');
if (real.length === 0) return 'ok';
return SEVERITY.find(f => real.includes(f)) ?? real[0];
}
function measureCgmPoint(
sdk: BiosensorSDK, sensorId: string, rawSignal: number,
siteTempC: number, calTempC: number, alphaPerC = 0.03,
): { result: MeasurementResult; point: TimeSeriesPoint } {
// Correct relative to the calibration temperature (divide convention, as in the SDK).
const corrected = rawSignal / (1 + alphaPerC * (siteTempC - calTempC));
const result = sdk.measureAnalyte({ sensorId, signal: corrected, temperature: 25 });
result.signal = rawSignal; // keep the raw value on the record
result.correctedSignal = corrected;
result.temperature = siteTempC; // write back the true temperature
const point: TimeSeriesPoint = {
time: result.timestamp, value: result.concentration,
unit: result.concentrationUnit, quality: worstFlag(result.qualityFlags),
};
return { result, point };
}
The severity order in worstFlag is itself book guidance: v1.0 defines the ten flag values but no ranking among them. A product team should fix its own order, document it and keep it stable, because a TimeSeriesPoint can hold only one flag and the choice decides what a clinician's chart shows. On the wire, the resulting stream looks like the fragment below. Dates are ISO 8601 in UTC, the unit is explicit on every point, and the extra metadata keys use the index signature that TimeSeries.metadata allows. All values are illustrative.
{
"id": "TS-CGM-0001",
"sensorId": "BS-1767225600000-k3v9q2x7a",
"analyte": "glucose",
"metadata": {
"patientId": "P-5c1e9b",
"sampleMatrix": "interstitial-fluid",
"insertedAt": "2026-01-05T08:00:00Z",
"intervalMinutes": 5,
"calibrationMode": "factory"
},
"data": [
{ "time": "2026-01-05T10:00:00Z", "value": 6.4, "unit": "mmol/L", "quality": "ok" },
{ "time": "2026-01-05T10:05:00Z", "value": 6.9, "unit": "mmol/L", "quality": "ok" },
{ "time": "2026-01-05T10:10:00Z", "value": 7.3, "unit": "mmol/L", "quality": "high-noise" },
{ "time": "2026-01-05T10:15:00Z", "value": 7.8, "unit": "mmol/L", "quality": "ok" }
]
}
Note the sampleMatrix key. The BiosensorConfig.sampleType enum lists blood, serum, plasma, urine, saliva, water, food and environmental, but not interstitial fluid, so a CGM integration has no correct enum value to choose. Recording the matrix in metadata is a workaround; adding 'interstitial-fluid' and 'sweat' to the enum is an obvious candidate for a future revision.
Cardiac markers are the second family in spec §8. They are proteins present at very low concentrations, so they are measured with antibody-based immunosensors rather than enzyme electrodes, and the result has to be interpreted against a clinical decision limit rather than simply reported.
Spec §8.2 describes cardiac troponin I in a sandwich immunoassay format, with a target LOD of 1 pg/mL and a measurement time of 10–15 minutes. The sandwich format (Chapter 1) captures the analyte between an immobilized capture antibody and a labelled detection antibody, which gives good specificity and a signal that rises with concentration. It also carries a known failure mode: at extremely high analyte concentrations both antibodies can be saturated separately, so fewer sandwiches form and the signal falls again. This "high-dose hook effect" means that a very low signal is not always a low concentration, and one-step sandwich devices should be characterized for it.
Units need care. Laboratory troponin results are commonly reported in ng/L, while older point-of-care literature uses ng/mL; 1 pg/mL equals 1 ng/L, and 1 ng/mL equals 1,000 ng/L. The spec's target of 0.001 ng/mL is therefore 1 ng/L. A system that mixes these units without an explicit unit string on every record will be wrong by a factor of one thousand.
The spec also quotes a "clinical cutoff" of 0.04 ng/mL. This book does not use that figure. Current high-sensitivity troponin assays use a decision limit defined as the 99th-percentile upper reference limit of a healthy population, and that limit is specific to each assay. There is no universal troponin cutoff that a data standard can hard-code. For the same reason, clinical interpretation of troponin commonly depends on the change between serial samples as well as on a single value. POCTestResult describes one sample, so serial interpretation has to be built from several records linked by patient.id and timestamp.
For B-type natriuretic peptide (BNP) the spec gives a clinical range of 0–5,000 pg/mL, a cutoff of 100 pg/mL for heart failure and a response time of about 15 minutes. The 100 pg/mL figure is the widely used rule-out threshold for BNP, but even here the decision limit belongs to the assay and its intended use, not to the data format. BNP and its N-terminal fragment NT-proBNP are different molecules with different reference values, so the analyte string must name which one was measured.
For these tests the right record is POCTestResult rather than a bare MeasurementResult. It adds a testType ('cardiac-marker' here), a numeric value and unit, an interpretation (normal, abnormal or borderline for quantitative tests), an optional referenceRange and cutoff, device information (serialNumber, lotNumber, expirationDate) and a small qualityControl block with a pass/fail flag and quality flags.
Two gaps matter in practice. First, cutoff is a bare number: it records neither its unit (the record's unit is implied) nor where it came from. Book guidance: keep the assay's labelled decision limit in the device or product configuration, identify the assay unambiguously through the lot number, and never let a reader app fall back to a generic value. Second, v1.0 does not define when a quantitative result is borderline. If a product uses that value, its instructions for use should define the grey zone around the cutoff, and the app should implement exactly that definition.
Spec §8.3 names two lateral-flow examples: a SARS-CoV-2 nucleocapsid antigen test and a malaria test targeting Plasmodium lactate dehydrogenase (pLDH) or histidine-rich protein 2 (HRP2). Lateral-flow strips are increasingly paired with an optical reader or phone app that turns a visual line into a number.
A molecular test such as PCR amplifies the target nucleic acid, so it can detect a very small number of starting copies. An antigen lateral-flow test has no amplification step: it captures viral protein on a line and makes it visible with coloured or fluorescent labels. The consequence is structural: antigen tests have LODs orders of magnitude higher than molecular tests and are most reliable when antigen load is high. The spec's antigen LOD (in viral copies per millilitre) and its clinical sensitivity and specificity ranges are not used here because no source supports them. What matters for integration is that a negative antigen result carries less weight than a negative molecular one, so the record must name the method.
HRP2 is produced by Plasmodium falciparum only, while pLDH tests can be designed to detect all human-infecting species or specific ones. HRP2 persists in the blood for some time after successful treatment, so an HRP2 test can stay positive after the infection has cleared, and parasites with deletions of the hrp2/hrp3 genes can produce false negatives. The spec lists a target LOD of 5–50 parasites/µL and a time to result of 15–20 minutes. For integration purposes the lesson is the same as for troponin: the analyte string should name the exact target (for example "P. falciparum HRP2" or "pan-Plasmodium LDH"), because the clinical meaning of the result depends on it.
A lateral-flow reader measures the intensity of the test line and of the control line. A common approach is to compute a test-to-control (T/C) ratio and compare it with thresholds set during product validation. Moving the positivity threshold trades clinical sensitivity against clinical specificity: a lower threshold catches more true positives and produces more false positives. Where exactly to set it is a clinical validation decision (Chapter 8), not something the data standard can fix. What the integration must get right is the mapping from reader outcome to the interpretation enum and the QC block, shown in Table 7.2.
| Reader outcome | interpretation | qualityControl.passed | Notes |
|---|---|---|---|
| Control line absent or below its minimum intensity | indeterminate | false | Invalid test; the result must never be shown as negative |
| Control valid, T/C below the lower threshold | negative | true | Record the T/C value in value for later review |
| Control valid, T/C between the two thresholds (if the product defines a grey zone) | indeterminate | true | The instructions for use decide whether to repeat the test |
| Control valid, T/C at or above the positivity threshold | positive | true | Keep lot and reader serial so that results can be traced to a lot |
| Device or kit past its expiry date | not reported | false | Reject before reading; see device.expirationDate |
The resulting record for a reader-based malaria test might look like this. The T/C value, the thresholds and all identifiers are illustrative; in particular the cutoff of 0.10 is an example, not a recommended value.
{
"id": "POC-20260112-0042",
"testType": "infectious-disease",
"analyte": "P. falciparum HRP2",
"value": 0.42,
"unit": "T/C ratio",
"interpretation": "positive",
"cutoff": 0.10,
"timestamp": "2026-01-12T06:41:18Z",
"patient": { "id": "P-a81f07" },
"qualityControl": { "passed": true, "flags": ["ok"] },
"operator": "OP-017",
"device": {
"serialNumber": "RDR-00391",
"lotNumber": "LOT-2511-B",
"expirationDate": "2027-03-31T00:00:00Z"
}
}
Note that "T/C ratio" is not a unit in any formal unit system; it is a dimensionless reader output. When such a record is exported to a health record (§7.6) it should be sent as a coded qualitative result with the ratio as supporting data, not as a quantitative concentration.
Spec §8.4 and the benchmark table in §10.3 extend the standard beyond the clinic. They list water-quality targets (E. coli with an LOD of 1–10 CFU/100 mL, heavy metals at 1–100 ppb, pesticides at 0.1–10 ppb) and a food-safety target (aflatoxin with an LOD of 0.1 ppb, a range of 0.1–100 ppb and 5–10 minutes to result). These are the spec's targets for the application class, not properties of a particular device. The same section also lists CO, NO₂, O₃, particulate matter and VOCs under air quality. Those are normally measured with chemical gas sensors or optical particle counters, which have no biorecognition element and fall outside the BiorecognitionElement enum; this chapter does not treat them.
Organophosphate and carbamate pesticides inhibit acetylcholinesterase (AChE), which appears in the SDK's EnzymeType list as 'acetylcholinesterase'. An inhibition biosensor measures enzyme activity before and after exposure to the sample and reports the inhibition as I% = (A₀ − A)/A₀ × 100, where A₀ is the activity without the sample and A the activity after incubation with it. Two features follow from the principle. First, the sensor responds to the whole class of inhibitors rather than to one named compound, so a result should be reported as "AChE-inhibiting activity" (or as an equivalent of a reference compound) unless a separate method identifies the pesticide. Second, the signal decreases as contamination increases. The v1.0 inversion in measureAnalyte assumes a straight-line calibration of signal against concentration; an inhibition calibration can be expressed that way only within a limited range, and the sign of the slope is negative.
A practical trap: because 'acetylcholinesterase' contains the letters "ase", createSensor gives such a sensor the default 180-day lifetime with 1,000 measurements. Inhibition sensors are frequently single-use strips, and many inhibition chemistries are irreversible. The integrator should set lifetime explicitly to match the product.
"ppb" is ambiguous and should not be stored as a unit. In water it usually means µg/L, and in a solid food it usually means µg/kg, so a record should carry µg/L or µg/kg explicitly. Heavy-metal biosensors (enzyme-inhibition, DNA-based or whole-cell) respond to the free, bioavailable fraction of the metal, which depends on pH, hardness and organic matter, so a total-metal laboratory method can legitimately disagree with them. Bacterial targets are counted in colony-forming units; reaching single-digit CFU per 100 mL usually requires concentrating the sample first, and that step belongs in the method description. For aflatoxin in grain, sampling and extraction often dominate the variability, because contamination is unevenly distributed in a lot.
A sensor left in a river or a treatment plant is exposed to conditions that bench protocols never meet. Table 7.3 summarizes the main issues and how they appear in WIA records.
| Issue | Why it matters | Representation in v1.0 | Book guidance |
|---|---|---|---|
| Temperature swings | README asks for ±1 °C control for enzymatic sensors; field water can vary far more over a day | SDK corrects to 25 °C and flags only beyond ±10 °C | Log temperature with every reading; apply a validated correction; treat readings outside the validated span as flagged |
| Biofouling and surface fouling | Deposits slow diffusion and lower sensitivity over days | drift-detected exists as a flag, but the SDK never sets it | Detect drift from periodic check standards and set the flag in the adapter |
| Unattended calibration | Nobody is on site to run a 5-point calibration every day | Calibration expires after 24 h (BIO004) | Use on-board blank and check standards between full calibrations; record each check as a QC result |
| Intermittent connectivity | Readings must survive hours without a network | BatchMeasurement groups results with startedAt/completedAt | Buffer locally, upload as batches, keep IDs stable across retries (§7.5) |
Whatever the analyte, a connected biosensor product has the same three hops: a reader that turns chemistry into numbers, a gateway (often a phone) that talks to the reader and to the network, and a service that stores, analyses and forwards records. Figure 7.1 shows the chain with the WIA record types that cross each boundary.
The reader holds everything that is physical: the potentiostat or optical front end, the analogue filter and the ADC whose 12–24-bit resolution spec §2.1.4 mentions. It should also send what later hops cannot reconstruct: the raw signal, the temperature, the lot, the reader serial and a timestamp. A reader that emits only a final concentration makes Chapter 6's residual and drift analyses impossible.
BiosensorSDK is a TypeScript class that keeps sensors and measurements in in-memory Map objects. It has no persistence, no transport and no concurrency control. In practice it belongs in the gateway app or in a service worker, wrapped by code that loads the sensor and its current CalibrationData from storage before each run and saves the results afterwards. v1.0 defines the BatchMeasurement type, with a statistics block for mean, median, standard deviation, CV, minimum and maximum, but the SDK has no method that creates one. An integrator who uses batches computes the statistics and attaches the run's QualityControlResult records to the batch.
SDK identifiers are built from the local clock and a short random string (BS-<epoch>-<rand> for sensors, M-<epoch>-<rand> for measurements). They are adequate inside one process but carry no device context. Book guidance for connected products:
The template file PHASE-4-INTEGRATION.md contains a non-normative Annex J that lists four reference topologies. They are generic, but they map well onto biosensor products, as Table 7.4 shows. None of them is a WIA-BIO-011 requirement.
| Topology (Annex J) | Shape | Biosensor example | Integration note |
|---|---|---|---|
| Single-tenant edge | One runtime per organization, no shared state | A hospital's own POC glucose meter fleet with an on-site data manager | Simplest to validate; the organization owns calibration and QC records |
| Multi-tenant gateway | One shared runtime, tenants isolated by request headers | A manufacturer's cloud service that receives CGM data from many clinics | Tenant isolation must extend to pseudonym mappings and audit logs |
| Federated mesh | Peer runtimes publishing to a shared directory | A regional network of water utilities sharing field-sensor results | Harmonize units and QC flags before results are compared between peers |
| Air-gapped batch | No live link; batches carried out of band | Field malaria testing in areas without coverage, uploaded when a device returns to base | Batch IDs, device clocks and lot information carry the whole audit trail |
Spec §10.4 and the README name WIA-HEALTH (health-record integration) and WIA-OMNI-API (a universal data API) as the companion standards, but WIA-BIO-011 v1.0 defines no mapping to either. The mapping in this section is therefore book commentary. It uses HL7 FHIR, whose Observation resource is the common target for a laboratory or point-of-care result in current health-record systems, because it has a published structure that can be checked field by field.
| WIA field | Observation element | Comment |
|---|---|---|
targetAnalyte / POCTestResult.analyte | code | A free-text analyte name is not enough; map it to a code from a terminology such as LOINC chosen for the specimen and method |
concentration + concentrationUnit, or value + unit | valueQuantity | Send a UCUM unit code (mmol/L, mg/dL, ng/L, pg/mL) |
interpretation (qualitative tests) | valueCodeableConcept and/or interpretation | Positive/negative results are coded values, not quantities |
referenceRange, cutoff | referenceRange | Include the unit; state which population and assay the range applies to |
timestamp | effective[x] | Measurement time, not upload time |
patient.id, metadata.patientId | subject | Resolved from the pseudonym only inside the organization that holds the mapping (§7.7) |
device (serial, lot, expiry), sensorId | device reference | Lot numbers make it possible to find every result affected by a lot recall |
qualityFlags, qualityControl | note, status | Do not send results from a failed QC run as final observations |
A glucose-meter result mapped this way looks like the sketch below. The LOINC code is deliberately left as a placeholder: choosing the right code depends on specimen (capillary blood, plasma, interstitial fluid) and method, and that choice belongs to the product's terminology mapping, not to a textbook.
{
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [{ "system": "http://loinc.org", "code": "<code chosen for specimen and method>" }],
"text": "Glucose, capillary blood, meter"
},
"subject": { "reference": "Patient/<resolved from P-5c1e9b>" },
"effectiveDateTime": "2026-01-05T07:32:10Z",
"valueQuantity": {
"value": 5.6, "unit": "mmol/L",
"system": "http://unitsofmeasure.org", "code": "mmol/L"
},
"device": { "reference": "Device/<meter serial>" },
"note": [{ "text": "WIA-BIO-011 qualityFlags: ok; calibration CAL-1767590400000" }]
}
Glucose is reported in mmol/L in many countries and in mg/dL in others. The conversion follows from the molar mass of glucose, 180.16 g/mol: 1 mmol/L = 18.016 mg/dL, usually rounded to ×18.0. For example, 5.6 mmol/L corresponds to about 101 mg/dL, and the upper end of the meter range in spec §8.1, 33.3 mmol/L, corresponds to 600 mg/dL. Three rules keep conversion from becoming a source of error:
TimeSeriesPoint. The v1.0 types make the unit a separate string field precisely so that it cannot be lost.mg/dL and mmol/L are valid UCUM expressions; "ppb", "T/C ratio" and "RFU" are not, so they must be converted (§7.4.2) or sent as qualitative or supporting data.Between the reader and the gateway, personal-health devices often follow existing interoperability standards rather than a custom protocol: the ISO/IEEE 11073 personal health device family (which includes a glucose-meter specialization, ISO/IEEE 11073-10417) and the Bluetooth SIG's Glucose and Continuous Glucose Monitoring profiles. WIA-BIO-011 does not reference them and does not define a transport protocol of its own (Chapter 6 explains why the book's "protocol" is the operating protocol instead). Any of them can carry the data, provided raw signal, temperature, lot and timestamps survive the hop.
Biosensor results are health data. In the European Union, data concerning health are a special category of personal data under the GDPR (Article 9), and in the United States, identifiable health information handled by covered entities and their business associates is protected health information under HIPAA. WIA-BIO-011 v1.0 does not define security requirements of its own. The generic lines in the PHASE template files list AES-256 encryption at rest, TLS 1.3 in transit, role-based access control, multi-factor authentication and audit logging; they are reasonable baseline controls and are treated here as non-normative context, not as WIA-BIO-011 requirements.
The v1.0 types carry patient identity in two places: POCTestResult.patient (an id plus optional age and sex) and TimeSeries.metadata.patientId. Neither field says what kind of identifier it holds. Book guidance: put a pseudonym there, never a name, national identifier or medical record number, and keep the table that links pseudonyms to real identities in a separate store with its own access control, inside the organization responsible for the patient. Pseudonymized data are still personal data under the GDPR, so this reduces risk but does not take the records out of scope.
Other fields are personal too: operator identifies staff, a home meter's serial identifies its owner, and a dense CGM TimeSeries reveals meals, sleep and exercise. Minimization applies to the whole record: send age and sex only where the interpretation needs them (for example when reference ranges depend on them), and export time series at the resolution the receiving use actually requires.
| Hop | Main risk | Control |
|---|---|---|
| Reader ↔ gateway | Pairing with the wrong device; injected or replayed readings | Authenticated pairing; per-reading sequence numbers; reject readings from unknown serials |
| Gateway storage | Lost or shared phone exposes history | Encryption at rest (for example AES-256 through the platform keystore); keep only what has not yet been uploaded |
| Gateway ↔ service | Interception, spoofed service | TLS 1.3 with certificate validation; token-based client authentication (for example OAuth 2.0) |
| Service | Over-broad staff access; silent edits | RBAC with least privilege, MFA for staff, append-only audit log of reads, edits, recalibrations and QC overrides |
| Service ↔ health record | Mis-linked patient; wrong units | Resolve pseudonyms only at the export boundary; validate UCUM units and value ranges before sending |
Audit logging deserves one biosensor-specific remark. In an analytical system the most consequential edits are not changes to results but changes to calibrations, QC acceptance and lot information, because a single wrong calibration silently shifts every result that follows. The audit trail should therefore cover CalibrationData and QualityControlResult records as carefully as it covers patient-facing results.
Table 7.7 condenses the chapter into a checklist that an integration team can work through before the first pilot. Every row is book guidance built on the v1.0 behaviour described above; Chapter 8 turns the testable ones into validation evidence.
| # | Check | Where it bites |
|---|---|---|
| 1 | lifetime set explicitly to the product's labelled wear period or shelf life | CGM, AChE strips (180-day default) |
| 2 | Calibration validity matches the product's calibration model, not the SDK's 24 h | CGM, unattended field sensors |
| 3 | Temperature correction referenced to the calibration temperature; true temperature stored | CGM, field sensors |
| 4 | linearRange.min equals the validated low end, so that low results are not mislabelled below-lod | Glucose (hypoglycemia), trace contaminants |
| 5 | A documented severity order for choosing one flag per TimeSeriesPoint | CGM charts |
| 6 | Assay-specific cutoffs held in product configuration, never a generic value | Troponin, BNP, lateral flow |
| 7 | Invalid control line maps to indeterminate with QC failed | Lateral flow |
| 8 | Units explicit on every record; UCUM on export; "ppb" and ratios never exported as units | All |
| 9 | Stable IDs across retries, UTC timestamps, measurement and receipt time both stored | All connected products |
| 10 | Pseudonyms only in WIA records; mapping held separately; audit covers calibrations and QC | All clinical products |
With the sensor now producing records that survive the trip from the reader to a health record, the remaining question is how to prove that the sensor and the implementation actually meet the standard and the regulations of the market where they will be sold. That is the subject of Chapter 8.