Skip to content

Inspection → approved customer offer

TyreFlow

DaiL Casablanca AI Lab · 2026 · six-hour build · 2nd place, Octopus day

In short

TyreFlow was built for the DaiL Casablanca AI Lab on Octopus day, against a six-hour constraint and starting from a client discovery session rather than a spec. A tyre shop inspects wheels it already has in storage; the useful observation — these are worn — has to become a customer conversation before that customer's next appointment, and often doesn't.

It placed 2nd. The part I'd defend hardest isn't a feature the software adds: it's one it refuses to do.

The problem

The brief's framing was that technicians inspect stored wheels and the information doesn't reliably reach the coordinator in time. Discovery changed two things about that.

First, the pain wasn't the inspection or the offer — it was the handoff. The note sits somewhere the coordinator doesn't see until it's too late.

Second, and more usefully, the primary case is asynchronous. I had assumed a customer standing in the shop waiting; the client's answer was that the wheels are in storage and the customer isn't there. There's no pickup moment to catch them at — the only real deadline is their next booked appointment. That single correction changed the shape of the product, and it only surfaced because the session was a conversation rather than a requirements list.

The third thing they were unambiguous about: a named person approves every single offer, including the clean ones. No "trusted enough to auto-send".

Architecture

Technician inspectionlogged with evidenceClassification + urgencydays to appointmentReview taskOffer draftNo actionStock gatehard block — not a warningStock boardconfirming a size unblocksCoordinator approvalnamed human, every timeCustomer contact3 languages · 4 channelsno offer is ever sent for a tyre the shop cannot supply

Every path from an inspection to a customer runs through the stock gate and a human approval. There is no bypass.

A coordinator queue, and one rule underneath it: no offer is ever sent for a tyre the shop cannot supply.

  • Classification. Every wheel set becomes exactly one of offer draft, review task, or no action — never more than one — ranked by urgency from days-to-appointment, weighted up for incomplete records and unconfirmed stock. A review task lists every applicable reason at once rather than collapsing them into one label, because "unknown size" and "unreachable contact" are two different pieces of work.
  • Evidence before proposal. The record popup always shows what the technician actually recorded before it shows any drafted offer. Approval is never a guess, and it's never one click away from a number nobody checked.
  • The stock gate. When stock isn't confirmed, Approve is disabled. The coordinator flags the size to a stock board instead; confirming that size unblocks contact automatically and resurfaces every record that was rejected for that exact reason.
  • Human approval, always. Approve, bulk-approve and reviving a rejected record are all explicit clicks.
  • Drafts in 3 languages × 4 channels — French, Darija and English across WhatsApp, SMS, email and call, from editable templates with a live price.

Everything runs on synthetic data, and every simulated send is labelled "simulated — not sent" in the interface itself, not only in the README.

Decisions & trade-offs

The stock gate became a hard block, not a warning

The original requirement allowed sending an unconfirmed-stock offer with hedged wording. Once the client said "make sure we can't contact clients if stock is unavailable", I removed the path entirely rather than making the warning louder.

This is the decision I'd defend first. A warning delegates the rule to whoever is tired at 5pm; a disabled button enforces it. The cost is real — a coordinator who is certain the stock exists still can't proceed — and the recovery path is what makes that acceptable: flagging the size to the stock board is one click, and confirming it unblocks everything waiting on it, including records already rejected for that reason. Cautious wording now covers only the genuinely ambiguous case: confirmed, but short on units.

Rejecting a record stopped being a dead end

Early on, rejection deleted the problem from view — which is fine until the blocking reason goes away and nobody knows to look again. The Rejected bin never deletes, and it's tied to the reminder that fires when the exact blocking stock size is confirmed. It closes the loop the stock gate opens; without it the gate would just be a way to lose work quietly.

A demo-only "inject an event" button was deleted

The brief warned that a scripted animation is not evidence that an agent performed an action, and my first prototype had exactly that: a button that simulated new information arriving. I replaced it with re-classification driven by actions that are part of the real workflow — confirming stock, logging an inspection. It demos less smoothly and proves considerably more. Every state change in the running prototype is computed from real component state; the only animations are transitions that follow a state change rather than standing in for one.

An explicit 80/20 on what to build in six hours

With six hours, feature count is the wrong objective function. I judged the handoff delay to be the cause of most of the miss rate, so the load-bearing 20% — visible evidence, the stock gate, an approval step that can't be bypassed — got the build time. Language switching, editable templates and the call channel came after those were solid, not before. That ordering is why the demo could survive questions instead of only surviving a happy path.

What was hard

Separating what the client said they wanted from what the situation actually required, live, under a clock. The "customer is in the shop" case felt obvious and was wrong — it's their secondary case, and building for it first would have produced a tool for a moment that mostly doesn't happen. It's still in there, raising a distinctly-toned urgent alert, but as the bonus it is.

The rest was honest debugging under time pressure. Testing surfaced two real bugs in the alerting: the chime was firing for the technician instead of the coordinator, and switching roles was destroying the very data the alert referred to. Both are fixed at the store level. I'd rather name them than present six hours of work as having gone cleanly.

Outcome

2nd place, and a deployed prototype — not a slide deck — at the end of six hours. It shipped with a repeatable start state (a reset that restores the seed data exactly, no drift, no randomness), documentation separating what's real from what's simulated, and an engineering handoff covering next integration, access, owner and unresolved risk.

What I took from it: discovery is engineering work. The most valuable thing I built that day was a disabled button, and I'd never have known to build it from the written brief.

What I'd do next

The honest next step isn't a feature, it's a validation test: put the queue in front of a real coordinator for a week and measure whether inspections actually become conversations before the next appointment. Everything else — real messaging integrations, a persistent backend instead of seeded state, authentication worth the name — is straightforward once that number exists and says the mechanism works.