Architecture · verdict · #403

BenzAuto — Availability model: the verdict

How the booking engine should decide and show availability. Synthesised from the real KL Chan failure, the workshop domain memory, and a two-round interview with a simulated seasoned workshop owner. Pairs with the decision brief, the four engines, and the current architecture.

1 · The verdict, in one line

This overturns the brief's "exact-time interval planner (B) as the default." The default output is drop-off day + block (D-shaped); exact slots survive only as a second mode for short wait-jobs.

2 · The model

2.1 · Unit of availability — day + block

Two blocks per working day, each shown open / tight / full. The customer picks a day + block to drop the car, not a minute to start work.

BlockWindowNote
Morning dropopen → ~12:00The 8:30–9:30 drop-off crush is the real constraint
Afternoon drop~14:00 → 17:00Rush over; reserve released
Lunch13:00–14:00Dead — no wait-jobs offered

2.2 · "Available" = a three-bucket test (replaces "any empty bay")

A block is offerable for a job only when all three hold:

"Any empty bay = available" is a lie — it is the root of the KL Chan disaster.

2.3 · Block capacity — the open / tight / full counting rule

A dumb engine can add these up; no eyeballing the yard. A heavy job counts as 2.

BlockOnline bays~Bay-hrsopentightfull
Morning2 (3rd held till 15:00)~70–3 cars4–56
Afternoon3 (reserve released)~90–4 cars5–67

2.4 · Bimodal booking style — chosen by the JOB, not the shop

The line: predictable AND under ~1 h AND no diagnosis → WAIT; otherwise → DROP.

2.5 · Job classification at booking (from the customer's words)

ClassBooking effectExample complaints
HEAVYblock; consumes the ≤2/day expert cap"check engine light", "gearbox jerking/slipping", "misfire", "engine knock/noise", electrical, "transmission"
LIGHTflow it (wait or block per §2.4)"change tyre", "puncture", "alignment", "balancing", "wiper", "bulb", "battery", "oil top-up", "service A", known "brake pads"
UNKNOWNbook as light + tag "may grow"; do NOT consume the heavy cap until inspection"aircon not cold", "brake noise", "Service B", any "noise / vibration / pulling / leak"

2.6 · Duration = bay time, not calendar time; multi-day = sessions + yard

2.7 · Reserve capacity

Regulars/walk-ins don't use the bot, so the reserve is trivial: the bot may not book more than 2 service bays' worth of morning capacity; the 3rd bay's morning hours stay out of the online pool until 15:00, then release.

2.8 · Collection promise — exact bot wording

CaseBot says
Normal"Drop before 9:30 — ready to collect after 5pm today; we'll WhatsApp if it's done earlier."
May-grow"Leave it in the morning. We inspect and WhatsApp by noon with what it needs and a collection time — plan for after 5pm; if bigger, next morning."
Parts / multi-day"This one needs a part — leave the car, we confirm the part tomorrow and give an exact collection day, usually 2–3 working days. We update at each step."

Rule: earliest promise "after 5pm today"; never a clock finish before mid-afternoon; for may-grow/parts promise an update, not a finish.

2.9 · Conversational fixes (the exact KL Chan failures)

3 · The kill list — what dies

4 · What this means for the #403 engine choice

The four options weren't the real question — the output shape was.

5 · Schema lock — the three-way split

The brief's single bayManagement blob shadowed the relational tables. Correct homes:

// Bay table — extend the existing bayType
bayType: "service" | "tyre_alignment" | "big_job"   // Bay 3 = tyre_alignment (not a general lift)

// ServiceItem table — extend
jobClass:            "wait" | "light" | "heavy" | "unknown"   // from service_type + name
bookingStyle:        "appointment" | "block"              // derived from jobClass (§2.4)
durationMin:         { min, max }   // wait = tight range; drop = estimate for bay-hours
bufferMin:           int            // cleanup / test-drive pad
mayGrow:             bool           // unknown jobs: tag, reclassify after inspection
countsAgainstHeavyCap: bool        // heavy jobs only

// Organization.settings.bayManagement — org-wide policy only (parsed like `booking`)
blocks:            [ { key:"am", start:"08:30", end:"12:00" },
                     { key:"pm", start:"14:00", end:"17:00" } ]
blockCapacity:     { am:{ tight:4, full:6 }, pm:{ tight:5, full:7 } }  // cars; heavy=2
reserve:           { holdBayHoursMorning: 1, releaseAfter: "15:00" }
heavyPerDay:       2
morningOverbookPct: 20
waitSlotWindows:   [ "10:00-12:00", "14:00-17:00" ]   // no wait-slots in crush / lunch
lookaheadDays:     12
collectionPromise: { normal, mayGrow, partsWait }     // wording templates
bookingMode:       "auto"    // auto = pick wait/drop per jobClass; can be forced per tenant

6 · Open calibration — only the REAL pilot owner (KL Chan) can confirm

Uncle Tan gives the general model; these numbers must be validated against KL Chan's actual floor before we build.