Case file
A gateway that turns my pulse monitor's serial stream into FHIR R4 heart-rate Observations. Each 30-second window becomes one LOINC-coded value, or an explicit "not performed" when the sensor was off. Deterministic identifiers and conditional creates make retries safe, and a SQLite outbox means a network drop costs a delay, not data. Tested against a real HAPI FHIR server and the official HL7 validator.
Problem
My pulse monitor writes a reading to the serial port 200 times a second. A health record cannot use that. An electronic health record wants one trustworthy value per interval, coded so that any system can read it, and it wants that value stored exactly once. Not zero times because the network dropped, and not twice because a retry went through after a timeout. This gateway sits between the device and the server and makes those three things true.
From samples to vital signs
Each 30 second window becomes one Observation with an effective period, a start and an end, because the value describes an interval and the record should say so. The value is the median of the firmware's rate over the window. A mean would be pulled by the 123 BPM spike the firmware emits for about a second and a half when the sensor reacquires the pulse; the median ignores it.
Time comes from counting samples. USB serial delivers bytes in bursts, so timestamping each line on arrival would put jitter into every window boundary. The firmware samples on a hardware timer at exactly 200 Hz, so sample N happened at the start instant plus N divided by 200 seconds. The host clock fixes only the starting instant.
A window carries a number only if the sensor gave a valid rate for at least 80% of it. When the finger leaves the sensor the firmware reports zero, and a window that is 58% zeros still has a median of the remaining 42%. That number would look perfectly normal in a chart, and it would be a measurement of 12 seconds presented as 30. Below the 80% line the Observation uses FHIR's own mechanism instead: a dataAbsentReason with the code not-performed and a text saying how much of the interval was covered. A median outside 25 to 250 BPM is marked as an error the same way. The R4 vital signs profile requires exactly one of a value or an absent reason, and the tests assert that.

The firmware's rate stream (grey) and the Observations the gateway sends (purple), on a 3 minute recording where the finger leaves the sensor from 70 to 110 s. Those two intervals are sent as "not performed" with the reason, not as a number. The spike at 111 s is the firmware reacquiring the pulse; it never reaches the record.
Standard coding
Every Observation follows the FHIR R4 heart rate profile: LOINC 8867-4, the vital-signs category, and a quantity in the UCUM unit /min. A Device resource describes the hardware, with its serial number as identifier, so every value traces back to the device that produced it. Each resource carries a generated narrative and each Observation names the patient as performer, so the official HL7 validator reports no errors and no warnings about the content.
Exactly once
The hard part of sending data over a network is not sending it. It is knowing whether it arrived. Every Observation carries an identifier derived from the device serial and the window start, a UUID version 5, so the same input always gives the same id. Each is sent as a conditional create: if the server already holds that identifier, it returns the existing resource instead of creating another. The Device is created the same way, conditionally on its serial number, and the Observations refer to it by its in-bundle URN, which the server rewrites to the stored Device whether it was just created or already existed.
All resources in a batch travel in one transaction bundle, so they succeed or fail together and there is never an Observation pointing at a Device that failed to save. Before a bundle is sent it is written to a SQLite outbox, and it is deleted only after the server returns success. A network error, a 408, a 429 or a 5xx means try later, so the flush stops and keeps order. Any other 4xx means the bundle itself is wrong and will fail forever, so it moves to a dead letter table with the server's OperationOutcome, and the rest of the queue keeps moving.
The integration test replays the same recording twice into a real HAPI FHIR server and still finds six Observations, four with values, and exactly one Device.
Traceability
The gateway is specified in 14 software requirements and analysed as 6 hazards, scored before and after controls in the hazard, situation, harm shape of ISO 14971. Code is tagged with the requirement it implements and each test with the requirement it verifies. On every commit, CI collects the JUnit results from the unit and HAPI integration jobs and runs medtrace on them, so a requirement counts as verified only by a test that passed on that build. The matrix is published as the job summary: traceability report for the current build.
One hazard, a reading attached to the wrong patient, has no control in the software, because the gateway passes on whatever patient identifier it is given. The report states that control as outside the software, device assignment in the clinical workflow, instead of pretending code covers it.
Limits
The device is a teaching prototype, not a medical device. The FHIR is correct; the measurement behind it is not clinically validated. Authentication is a static bearer token, where a production deployment would use SMART Backend Services. One device and one patient per process: the device to patient association is a command line argument, not a workflow. Live mode anchors time to the host clock at start and then counts samples, so over long sessions the device's crystal and the host clock drift apart, and this is not corrected.