Case study
OpsKwan
SaaS logistics for medical device distribution, deployed with Zimmer in 2011, tracking consignment, loaner sets, recalls and lots for implants.
- Client
- OpsKwan Technologies
- Dates
- 2009 to 2024
- of Canadian hospitals, for Zimmer Biomet's logistics
- 40%+
- commits on the shared platform, all mine, 2011 to 2023
- 1,641
- PHP
- CakePHP
- MySQL
- JavaScript
- IndexedDB
- AWS
The problem
An orthopaedic implant distributor places its stock on consignment in hospitals: implants and instrument sets that sit in a hospital’s storeroom until a surgery uses them. Every item has a catalogue number, a lot and an expiry date, and the distributor has to know where each one is, what was used in which case, what’s on loan, and, when a manufacturer recalls a lot, which hospitals are holding it.
The work happens in storerooms. A sales rep counts stock with a barcode scanner, often with no connection, and the labels don’t agree with each other: each manufacturer encodes the same facts its own way.
My role
Director of Development at OpsKwan from 2009 to 2024. I wrote the platform’s shared code, every commit in it from 2011 to 2023: the models, the barcode handling, the manufacturer imports and the offline audits.
What I built
- Consignment and loans. Hospitals, stock down to lot and expiry, transfers, loaner sets, surgery bookings and what each case used, with sterilisation and, later, biologics tracked back to their donors.
- Manufacturer imports. Each manufacturer’s ERP export arrives in its own format. Importers for Zimmer, DePuy and Wright load them into the client’s database; Zimmer’s is turned into bulk SQL each day, with the results mailed out.
- One database per client. Each distributor gets its own MySQL database beside a shared master database, and the platform builds a new client’s tables itself.
- Recalls. A recall list is imported, by product and lot, and a recalled lot is caught at the moment of scanning, online or offline.
Architecture
A CakePHP application over MySQL, with its models, behaviors and drivers shared between the apps the company and its distributors used. The front end ran in the browser on tablets and phones, with scanners that type into the page; there was no native app.
The hard part
Reading implant labels in a storeroom with no signal
A hand-held scanner acts as a keyboard: it types the barcode into whatever field has focus. That’s simple to deploy and hard to get right, because the barcode is the data.
The labels come in several forms. HIBC, the health industry’s barcode, puts a labeller code, catalogue number and unit of use in a primary symbol, and the lot and expiry in a secondary one, either joined to the primary or printed as a second barcode. The expiry is a Julian date, year and day of the year. GS1 labels carry the product number first, then the lot and expiry under numbered identifiers, in either order. Some older labels followed neither standard, and OpsKwan printed its own.
The server’s parser handles all of them, and I wrote a second one in the browser, for HIBC, GS1 and the simplest layouts, so audits work without a connection.
- Characters lost in transit. HIBC uses
+,/and$, and a+in a form post arrives as a space. Each scan was sent as character codes and rebuilt on the server. - Two-symbol labels. A batch of scans is split by line and the symbols are paired, in either order. The secondary carries a link character that has to match the primary’s check character, and a scan that doesn’t match is rejected rather than guessed.
- Finding the product. By catalogue number, then without leading zeros, then through a cross-reference table for numbers that differ between a manufacturer and a distributor.
- Offline. In 2013 audits moved into the browser: each scan is parsed there, checked against a preloaded product list, and stored in IndexedDB until it syncs.
- Recalls at the scanner. The recall list goes to the device too, and a recalled lot plays a warning sound instead of the usual one, online or not.
Two copies of a parser can drift. In 2019 I fixed a bug in the browser’s GS1 parser, where a lot read after the expiry kept the identifier’s digits; the server’s copy got the same fix eleven months later. It’s the clearest lesson in the code base: one parser, shared, beats two that agree most of the time.
Outcome
- Deployed with Zimmer, now Zimmer Biomet, in 2011, it ran their logistics for over 40% of Canadian hospitals.
- Thirteen years of the shared platform, from 2011 to 2023, every commit mine.
- Offline audits with recall checks at the scanner, built in 2013.