Decoding downtime: two apps I built to find out why the sorter stops
Every time the sorter goes down, everyone asks the same two questions. Why did we stop, and where do we start fixing? The answers were sitting right there in the exports the whole time. They were just impossible to read. So I built a decoder for each one.
01The problem: the answers were buried in the exports
I work around an automated parcel sorter, a machine that sends thousands of packages an hour down dozens of chutes. When it stops, everyone wants the same numbers: how long were we down, what actually caused each stop, and which chutes need attention before the next sort.
Here's the thing. All of that information already existed, buried in two exports nobody wants to read. The first is the HMI alarm journal, a spreadsheet with thousands of rows, one for every alarm event. Every E-stop, every jam, every comm fault, timestamped to the millisecond and completely raw. The second is the chute fault report, a per-chute count of faults like failed-to-divert. It tells you which chutes are acting up, but nothing about where to start or when you're done.
Turning the journal into a downtime report used to mean filtering rows by hand, guessing at durations, and arguing about which of fifteen simultaneous alarms "caused" the stop. Turning the fault report into actual work meant printing it out and crossing off lines with a pen. Both felt like decoding problems to me. The information was all there, just written for machines instead of people. So I built two small apps, one for each export.
02The ADTA Decoder: from alarm journal to answers
Drop in a journal, set the sort window, get the report.
Using it is simple on purpose. You double-click one file and the interface opens in your browser, running completely on your own machine. The journal never leaves it. Drag the alarm journal onto the page, confirm the sort window (it prefills from the journal's own span), add breaks and lunch, and hit Generate. A little stack-light on the page turns amber while it works and green when it's done. You get the headline numbers right on screen: total downtime, availability, episode count. Plus a full multi-sheet Excel workbook with the episode log, shutdown trigger analysis, alarm durations, a Pareto, an hourly timeline, and the cleaned data itself.
The fun part is everything that happens in between. The engine treats the downtime methodology as a set of explicit, testable rules. And almost every rule exists because a real journal broke a simpler assumption.
Reading the journal the way it's actually written
The biggest discovery was about the data itself. The journal logs each alarm twice. One row the moment it goes active, one row the moment it clears, and nothing in the export tells you which is which:
03:17:04.112 E-STOP 3 ENGAGED HIGH ← active 03:17:09.845 COMM LOSS, ZONE DR1_3 HIGH ← active (cascade) 03:17:11.203 COMM LOSS, ZONE DR1_4 HIGH ← active (cascade) 04:09:02.556 COMM LOSS, ZONE DR1_3 HIGH ← clear 04:09:02.601 COMM LOSS, ZONE DR1_4 HIGH ← clear 04:09:04.019 E-STOP 3 ENGAGED HIGH ← clear → one episode · 52.0 min · shutdown trigger: E-STOP 3 · comm faults credited to the stop
An early version assumed each row was its own ~30 second event, which is a pretty standard way to read alarm logs. It was also quietly wrong. It undercounted every stop longer than 30 seconds, so a 52 minute E-stop showed up as two half-minute blips. Once I checked the paired active/clear structure against the HMI's live state view, durations became exact. Clear minus active, no estimates. Rebuilding on that model was the single biggest accuracy fix in the whole project.
The pipeline
Rules that earn their place
Raw alarm data kind of lies to you. It records symptoms and causes side by side like they're equals, and the rule engine's whole job is telling them apart. Every rule in it was validated, and in a few cases discovered, by going through real journals event by event:
When the data fights back
The hardest part wasn't the happy path. It was defending the pairing model against messy reality. An alarm that was already active before the export starts shows up as an orphaned clear. A panel restart can re-announce seventy alarms in a single second, which orphans rows mid-journal and flips the pairing for everything after it. That one invented hours of phantom downtime for a single alarm before the engine learned to catch and repair the flip. And one stray power-supply row once fabricated four thousand minutes of downtime all by itself. Every one of these cases now has a guard in the engine, a plain-language note in the report, and a regression test that has to pass before a release ships.
Same story at the analysis level. Running a week-long journal as one flat window gave a nonsense answer: thousands of minutes of "downtime" that were really just nights and gaps between sorts. Weekly mode fixes that by treating each sort day as its own run and rolling the results up:
03The Chute Remediation Checklist: from fault counts to a fix list
The whole app is one HTML file. Open it anywhere, even offline.
The second decoder is smaller, and it's all about the follow-through. Fault reports come in as per-chute counts with coded names like S0508#8. There's more meaning packed in there than it looks like. The prefix is the chute address, and the #N suffix is the sorter's zoning value, basically where along the chute's columns packages get dropped. The checklist decodes it on sight:
Import a spreadsheet, paste rows straight from a query, or drop in a CSV. The parser figures out the shape on its own, including one weird export format where every field arrives stacked in a single column and the records have to be rebuilt by spotting the repeating pattern. Chutes get grouped by address, sorted worst first, and turned into a checklist:
Each row expands into its per-position detail: zone chips, severity dots, and a readout comparing every position against its own zone's average across the dataset. So a position running at 2.1x its zone average gets a red outlier flag even if the raw count looks small. Techs check off positions as they go, leave notes on what they found, and the progress bar tracks the sweep. Everything saves in the browser between shifts, and progress exports back out as a CSV with the zones, ratios, and notes intact.
My favorite detail is hiding in the import button. The app reads native Excel files with zero libraries and zero network. An .xlsx file is secretly just a ZIP full of XML, so the app walks the ZIP's directory by hand, unzips the worksheet with the browser's built-in DecompressionStream, and parses the cells itself. About a hundred lines of code doing the job of a big third-party library. That's what keeps the whole tool a single file that works on a locked-down machine with no internet, which is exactly where it gets used.
04Two decoders, one loop
The apps split the problem the same way the work actually splits:
After the sort. How long were we down, what triggered each stop, and what deserves engineering attention. The workbook is the record and the Pareto is the priority list.
Before the next sort. Which chutes and positions are faulting, which ones are outliers for their zone, who has checked what, and what they found.
One tells you why the machine stopped. The other turns the fault data into a list you can actually finish. The loop closes when the next journal and the next fault export show whether the fixes held.
05What building them taught me
The paired active/clear discovery invalidated every number the tool had produced before it. And what caught it wasn't luck. It was a habit of checking computed durations against the HMI's live view, and that habit became the backbone of the whole project.
Nothing went into the engine just because it sounded reasonable. The cart-jam window is 35 seconds because measured journals said so. Over-current became non-stopping because one specific 42 minute alarm proved the sorter runs right through it. When a rule has a story behind it, people can challenge it. That's the point.
Every reattributed cascade, negated break, and dropped stray row is listed in the workbook itself. A downtime number people can dig into gets trusted. A black-box number gets argued with.
One tool is a double-click .exe serving a local page. The other is a single HTML file. No installs, no accounts, no cloud, nothing leaves the machine. On an industrial floor, deployment friction decides whether a tool gets used. Not features.
Version one shipped, and eighteen more releases followed inside a week, each one driven by a real journal that broke an assumption. A regression suite of audited nights is what made that pace safe. Every fix had to reproduce the already-verified reports byte for byte before it shipped.
The data was never missing. It was encoded in pairing conventions, cascade patterns, and zone suffixes. Both apps are just decoders with opinions, and every opinion had to prove itself against a real night on the floor.
If you've got a machine spitting out logs nobody reads, there's probably a decoder waiting to be built. What would yours be?