I build computer-vision systems for heavy industry, automation for small businesses, and open-source tools. This is where I write down what broke, what worked, and why. Everything here starts from my own project notes, postmortems, and research — and I use AI heavily to draft and edit it.
The Cache That Cancels Your Improvement
A batch job matches free-text line items against a large product catalogue. Supplier wording never matches catalogue wording, so it runs a cascade — remembered aliases, then embedded codes, then exact matches, then fuzzy retrieval, and finally one language-model call to adjudicate the survivors. That last step costs money, so results are cached. I improved the text normalisation feeding the cascade. Tested it properly: extracted the production matching code, ran it against the real catalogue, confirmed zero previously working matches broke and roughly 230 previously failing lines now cleared the acceptance gate. ...
A Classifier That Can Say "Not Mine"
A camera looks down at a road. A truck passes underneath and the system has to say what it’s carrying. Some materials are coarse families that split into commercial grades; the grades matter for billing, and they look similar enough that a single flat classifier over every leaf class does poorly. So it’s staged: a coarse model picks the family, then a specialist refiner splits that family into products. Standard hierarchical classification. ...
One Substrate, Two Renderers
I built a tool that generates demo videos from a running system, then decided not to productise it. Separately, I keep a backlog of blog posts seeded from postmortems. It took me an embarrassingly long time to notice these are the same pipeline with different output formats. The shape Both do this: source of truth → verify the claim → scrub what identifies the client → structure → publish For a video, that reads: query the production database and run the deployed models → confirm every number can be re-derived by a command → replace client identities with stable pseudonyms and prove nothing leaked → lay out scenes and narration → render an MP4. ...
Zero Is Not Unset
A cost model resolves an hourly rate by walking a chain: the machine’s own override, then its template, then the site, then the organisation. First non-null wins. Standard inheritance, the same shape as CSS cascades, config layers or permission scopes. A UI change prepopulated numeric fields with 0 so the form never showed an empty box. Reasonable-looking polish. From that moment, every machine created from a template resolved to a rate of zero. Not because anything failed — because the chain worked exactly as designed. The template now had a value. Zero is a value. The lookup stopped there and never consulted the site or organisation rates underneath. ...
The 20 cm Cable
A Raspberry Pi 5 with an OAK-D Pro depth camera bolted to it, recording material on a conveyor belt. First boot on the rig: hard crash within minutes. vcgencmd get_throttled returned 0x50000, the journal had fourteen Undervoltage detected! lines, the USB bus was dead — no camera, no tag reader — and the capture service was in a reset loop. Two days of bench work later the fix was a 20 cm cable. Getting there went through two hypotheses that were both partly right and individually wrong, and ended somewhere more useful than a culprit: an equation with about 0.3 V of headroom in it. ...
"Verify the Mechanism Is Firing" — It Had Never Been Deployed
A client operates two businesses under one legal entity, at two addresses. Supplier invoices arrive by email and have to be attributed to the right one. The mechanism to do that had been designed, written and documented. The task on my desk was a diagnostic: confirm it’s firing, and if it’s mostly falling through to the AI fallback, find out why. The spec proposed a sensible first step — count how many invoices were resolved by the database lookup versus guessed by the model. I ran it. The answer was zero and zero. ...
Don't Resize Your Textures Away
A five-class material classifier reads a crop of a truck bed and calls what’s in it. Four of the five classes were fine — F1 between 0.92 and 0.97. One was hopeless: class F1 coarse classes (3 of them) 0.92 – 0.97 the coarser of two stone grades 0.720 the finer of two stone grades 0.476 macro 0.791 The two problem classes are the same crushed stone at two grain sizes. Between 13 and 18 of 30 validation frames of the finer grade came back labelled as the coarser one. A coin flip with extra steps. ...
The Validation Split That Flattered Every Number
The classifier reported a macro F1 of 0.975. It was, in the narrow sense, telling the truth: that number came out of a validation set the model never trained on, computed correctly, reproducibly. What it measured was “a new frame of a truck I have already seen.” Roughly 94% of the validation images had another frame of the same truck pass sitting in the training set. Different filename, different moment, same vehicle, same load, same lighting, half a second apart. ...
The Demo Video Was the Easy Part
I needed demo videos for two pre-revenue products: one that reads supplier invoices and computes per-product margins, one that watches trucks leave a quarry and classifies what they’re carrying. No budget for an agency. So I built them with an agent, ffmpeg, a headless Chromium and a free text-to-speech voice. The rendering pipeline took an afternoon. Everything else took two days, and none of it was video work. The problem isn’t generation Script-to-video is a solved commodity. Synthesia will turn text into a presenter for about USD 18/month. Arcade will turn a screen recording into a guided demo for USD 32. Both are good at what they do, and neither touches the part that actually costs anything. ...
Publishing an MCP Server to the Registry: What the Schema Doesn't Tell You
I shipped rihla — a flight-search MCP server — to GitHub, PyPI, and the official MCP registry in a single day. The mechanical steps went exactly as planned. The one thing that didn’t was the thing no schema could have told me: a server.json that validates perfectly can still describe an install that doesn’t work. Here’s the trap, the fix, and three smaller ordering gotchas that are cheap to avoid and expensive to discover after upload. ...