// healthcare and medtech

A feature that touches patient data is ready to ship. Here is what we test before it reaches a patient.

Clinical software fails quietly: a unit dropped in a message, a weight rounded too early, a record readable by the wrong user. We test those paths on every release, from a team certified to ISO 13485.

Start a two-week pilot
ISO 13485 certifiedISO 27001:2022clients include Sully.ai4.9 Clutch, 70 reviews
The bug I remember on a radiology platform: one clinic's fax sync could pull in another clinic's faxes, so a different practice's patient documents landed in the wrong inbox. Nothing errored. Since then I test every patient feature with two clinics side by side and try to reach one from the other.
Tiberiu Onisor, QA engineer on radiology and patient-portal software
// what keeps you up at night, and what you get
"A bug reaches a patient."
Testing planned around your risk file under ISO 14971, so the riskiest functions get the most testing.BugBoard
"The auditor finds requirements with no tests behind them."
Every requirement traced to the tests that cover it, from a team certified to ISO 13485.BugBoard
"Patient data leaks through something nobody thought to check."
Security and accessibility checked before each release, not after a complaint.AI Security Toolkit and Auditi
// four places clinical software breaks
01 · hl7 and fhir

The unit falls off the lab result

A lab sends an HL7 v2 ORU message. The interface engine maps OBX-5 (the value) into a FHIR Observation but ignores OBX-6 (the unit). The app then shows 5.3 in its usual unit, mg/dL, when the lab meant mmol/L. A normal glucose reads as a critical low.

also: OBX-14 time sent without a UTC offset, corrected results (OBX-11 = C) ignored
OBX|1|NM|14749-6^Glucose^LN||5.3|mmol/L value 5.3 -> valueQuantity.value unit mmol/L -> (not mapped) system, code -> (not mapped)
UNIT LOST5.3 shown as mg/dL, false critical alarm
how we test itRound-trip real message samples through the interface and assert that value, unit and observation time arrive together. A value without a unit must be rejected, never defaulted.
for (const msg of fixtures.oru.withUnits) {
  const obs = toFhir(parseHl7(msg))
  expect(obs.valueQuantity.unit).toBe(unitOf(msg, 'OBX-6'))
  expect(obs.valueQuantity.code).toBeDefined()  // UCUM
  expect(obs.effectiveDateTime).toHaveOffset()   // tz kept
}
02 · dose maths

The weight is rounded before the dose

A nurse enters a premature baby's weight as 1.5 lb. The app converts to kilograms and rounds to a whole number before it calculates the dose per kilogram. The calculation is correct, but the input was already wrong, and a neonatal dose has little room for error.

also: mg vs mcg (x1000), "5.0" misread as 50, missing leading zero in ".5"
weight entered1.5 lbx 0.453592370.680 kgrounded to whole kg1 kgdose, 10 mcg/kg, from 1 kg10 mcgdose from 0.680 kg6.80 mcgoverdose+47%
convertkeep full precisionround the dose onlyper the label rule
FAILdose differs from the exact calculation
PASSrounded once, at the end
how we test itProperty-based tests over weights and units against an independent reference calculation, plus boundary doses. Rounding follows the product's own labelled rule.
fc.assert(fc.property(weight(0.4, 200), unit('lb','kg'), (w, u) => {
  const kg = toKg(w, u)
  return dose(w, u, 10) === roundDose(10 * kg)  // round once
}))
expect(dose(1.5, 'lb', 10)).toBe(6.8)            // got 10
03 · iec 62304

The test passes the wrong requirement

A hazard needs an alarm within 10 seconds. Later the requirement is tightened to 5. The test case still asserts 10 and goes green, so the trace from risk to requirement to test looks complete and verifies nothing current.

found by the auditor, or by nobody
HAZ-07hazard
RC-07control
REQ-31software
TC-88test
run 41result
PASSgreen against the old limit
how we test itCheck the trace itself: every software requirement has a test that targets its current version, and every risk control has a test that proves it works.
for (const req of bugboard.requirements({ safetyClass: ['A', 'B', 'C'] })) {
  const tests = bugboard.testsFor(req.id)
  expect(tests.length).toBeGreaterThan(0)
  expect(tests.every(t => t.reqVersion === req.version)).toBe(true)
}  // TC-88 targets v2
04 · patient data

Any signed-in user can open any report

The API checks that the token is valid, not that the report belongs to the caller. Report ids are sequential, so changing one number in the URL returns another clinic's patient. The search term, which holds the patient's name, is also written to the request log.

valid token is not the same as permission to this record
user BapidblogsGET /reports/8812token ok, owner uncheckedSELECT * WHERE id = 8812clinic A, patient row200 diagnosis, DOB?name=Jane+Doe stored
FAILa stranger reads a patient record, and the name sits in the logs
how we test itCreate a record as one clinic, request it as another, and scan the logs for identifiers. The second clinic must get a 404, and the logs must hold no patient data.
const a = await login('clinicA'), b = await login('clinicB')
const { id } = await a.post('/reports', sampleReport)
expect((await b.get(`/reports/${id}`)).status).toBe(404)  // got 200
expect(logs.matching(PHI_PATTERNS)).toEqual([])

Illustrative examples built from defect patterns we test for. Not client data.

// on every pull request

Your code is written in hours. Our pipeline keeps up.

Pull requestfrom your coding agent
Flowsjourneys and fault injection
Security Toolkit15 scanners + 20 own checks
QA engineerconfirms every finding
BugBoardrelease report, Jira sync
// the regulation, in testing terms
regimewhere it actually breakswhat we run
IEC 62304Tests that verify a requirement version that no longer existstrace checks per safety class, from requirement to test result
ISO 14971Risk controls with no test that proves they worktest cases linked to each control in the risk file
21 CFR Part 11Audit trails that overwrite instead of appendedit, delete and replay checks on every electronic record
HIPAA and GDPRRecords reachable by id, identifiers written to logscross-clinic access tests, PHI pattern scans on logs and exports
EU MDRSoftware verification evidence an assessor cannot followrelease test reports tied to the exact software version
// proof

Tested inside regulated medical software

ISO 13485 certified636 test cases, Sully.ai44 risks mappedabout 189 test cases
  • Sully.ai, an AI medical scribe used by clinicians: a security and accessibility pass with 636 test cases.
  • Radiology software classed as a medical device: 44 risks mapped in an ISO 14971 matrix and about 189 test cases.
  • Clients include AdviNow Medical, CardiaSync and Owlet.
  • A US e-prescribing integration, tested in 2024 and 2025.

The radiology client's name is withheld.

BUG-0731 · results / audit trail · examplehigh

Editing a lab result erases the original value

  1. Open a final glucose result of 5.3 and change it to 6.3
  2. Open the audit trail for that result
  3. Only 6.3 is listed; 5.3, the reason and the old time are gone

Fix: Write an append-only audit row on every change with old value, new value, user, time and reason.

21 CFR 11.10(e)REQ-AUD-04TC-0731 addedJira MED-1184

Two weeks on your clinical software. Free if you're not happy.

Start a pilot