Specimen reported → a report the patient and the doctor can read
The analyser has the numbers. The report is what the patient actually receives.
Trigger → artefact
A panel finishes and the LIS has resultsa laboratory report PDF, with the interpretation derived from the ranges
A lab has two options today: the one fixed layout its LIS module prints, or an engineer who maintains a Puppeteer template for ever. Neither survives the day a new panel is added, and both put the interpretation — the LOW, the HIGH, the CRITICAL — in the hands of whatever code assembled the payload.
Here the caller sends the measurement and the range. The document compares them and derives the reading, counts what is outside range, and lifts critical results into a panel on page one. Send a flag of your own and the render is refused with the field named — a report that prints "Normal" beside a potassium of 6.4 is the document that gets a lab struck off.
Built with the HTML API. Need a designed image or certificate instead? Use the Canvas API — same data, same free year.
Laboratory report — rendered by the template this page calls.
What ships in this category
The documents, and the part of each that is hard.
Laboratory report
Results grouped into panels, each with its reference range, method and derived reading; critical results lifted to page one; results with no range reported as measurements and explicitly not interpreted.
Hard part The interpretation itself — LOW, HIGH, CRITICAL and the counts — derived from the range printed on the same line, never sent by the caller.
template_id 466e1838-eb86-4e8b-b691-982d00b210dd
Appointment confirmation
What the patient gets after booking: date and time in a block they can read on a phone, clinician, room, what to bring, the deposit taken, and the cancellation terms.
Hard part A confirmation that carries the cancellation charge on its face, so it stands up when the patient disputes it.
template_id 61c1bdde-d3ba-4b6b-ab62-4877e4ef6c25
Veterinary invoice
Clinic visit billing with the patient — the animal — identified by species, age and microchip, treatments and medication priced per line, tax and the amount paid at checkout.
Hard part Two identities on one document: the animal treated and the human who pays.
Run against the API before publishing: 200, application/pdf, and that potassium prints as CRITICAL HIGH with a contact-the-clinician panel on page one — without the payload saying so anywhere.
The contract
Exactly what you send, exactly what you get.
These templates take named keys — no positional arguments, no layout to describe. Send a row array and the pagination follows it.
Send as many as you have. Pagination, running totals and the carried-forward header are the renderer's problem, not yours.
What you would ask next.
Our LIS already computes the flags. Why send ranges instead?
Because then two systems decide what is abnormal, and one day they disagree in a document with a patient’s name on it. Send the measurement and the range and there is exactly one answer, printed beside the numbers it was derived from. A row carrying its own flag is a 400 with the field named.
Some of our tests are qualitative — Reactive, Nil, Trace.
Send value_text and expected. Matching the expected string is Normal, not matching is ABNORMAL. Send neither and the result is printed as a measurement with "not interpreted" against it, and the report states how many results it declined to interpret rather than guessing a range.
A big profile runs to several pages.
We tested 90 analytes over six panels: eleven pages, the column header repeating for every panel, the patient and report number in the running header on each page, and per-panel counts that add up. Criticals stay on page one regardless of where the analyte sits.
Is this a medical device or a diagnostic claim?
No. It is a rendering layer: it compares the number you sent to the range you sent and prints the result. The clinical content, the ranges, the methods and the verifying pathologist are all yours, and the template is yours and versioned — publish a new version and pin the old one to reproduce a historical report byte for byte.
Your system already knows when the document is owed.