Skip to content

EAS classification & bookability

Reference for EAS (enterprise 117): the current state of call-reason classification and bookability, how the system reached its current behavior, what work is needed, and the open decisions. File:line references throughout.

State of things

Classification. Each EAS call is classified into the team's call reasons by classifyTeamCallReason (apps/web/lib/call-reasons/classify-team-call-reason.ts, gpt-4o) reading the call transcript. All 24 EAS teams share a custom call-reason taxonomy (live in the DB). Classification is working correctly.

Bookability. All 24 EAS teams are on bookable_source = 'call-reasons', so is_bookable is derived from the matched reason's bookable flag — except an override in resolveBookability (apps/web/lib/workflow/stages/booking/booking-info.ts): when a separate post-call prompt confirmed_booking_yes_or_no === 'Yes', is_bookable is forced true even when the reason is non-bookable. That confirmation prompt sometimes false-positives (e.g. an "Excused – Other Questions" call read as a confirmed booking), so is_bookable reads true on non-bookable calls (~34 in the 6/22–28 window) — inflating the lead count with no corresponding booking. This is the core issue.

Booking outcome (is_booked). Set once post-call: true only when a real AutoOps job lands for a Booking call (auto-service/booking-flags.ts:84); reschedule/cancel/ETA are forced false. Never re-flipped afterward. Per "booking always lands" (below), is_booked=true now includes jobs that landed with placeholder/caveat data (UNKNOWN vehicle, generic service) for the shop to verify — so it reliably means "a job is on the board," not "a clean, complete booking."

Alert emails. Two alerts — a Slack booking-failure ping (booking-autoops/booking-failure-alert.ts) and a [URGENT — Customer Told Booked, Booking FAILED] email (booking-autoops/booking-failure-urgent-email.ts) — gate on the AI appointmentBooked flag (run-booking-autoops.ts:149,267), not on is_booked. They fire when the agent told the customer they were booked but the job couldn't land. They run inline during the workflow, before is_booked is written, so they're independent of the reason/bookability logic.

How we got here

  • Call-reasons bookability. EAS is on bookable_source = 'call-reasons', which makes is_bookable reason-driven and brings the confirmed_booking override into the bookability path as a real-booking safety net. The override couples two independent AI judgments — the reason classifier and the booking-confirmation prompt — and produces the phantom bookable leads whenever they disagree.
  • "Booking always lands" (PR #11753, merged 2026-06-10). AutoOps allows double-booking and doesn't enforce availability/capacity — but it does require a valid, designated appointment time. So Avoca's own pre-flight checks (availability re-verify, exact service match, customer lookup) were the only thing rejecting bookings AutoOps would otherwise accept — after the customer had already been told "you're booked," leaving nothing on the shop's board. #11753 turned those checks into advisory notes on the job so the promised booking always lands. The only hard blockers left are a missing or invalid appointment time (AutoOps requires a valid slot) or a genuine AutoOps API error. Consequence: is_booked=true now also counts these caveat saves.
  • Alert tuning (#11858 → #12180). #11858 widened the appointmentBooked signal to catch more "told booked" cases; it over-fired and flagged deferrals ("we'll call you back") as bookings → false URGENT emails. #12180 added a deferral carve-out and fixed it.
  • Taxonomy + backfill. The EAS call-reason taxonomy was built and synced to all 24 teams' team_call_reasons. team_call_reason_id was backfilled for 307 calls in 6/22–28 (filling only empty reasons; is_bookable left untouched). On those calls the reason and is_bookable are therefore not reconciled. The weekly enterprise report was rebuilt and sent to EAS admins (matched to the dashboard).

What's needed

  1. Skip the confirmed_booking override for EAS (flag-gated) — the primary fix. In evaluateBookabilityBySource, when a feature flag is enabled for the team/enterprise, pass undefined for confirmedBooking so is_bookable = reason.isBookable directly. Add the flag, target enterprise 117, off by default. Removes the phantom bookable leads; reversible; A/B-able. (Drafted, not yet shipped — touches shared resolveBookability behavior, gated to EAS, so needs Avoca review.)
  2. Re-derive is_bookable from the reasons on the backfilled calls (and any others where reason and bookability disagree), so they reconcile.
  3. If the override is kept anywhere, improve the confirmed_booking_yes_or_no extraction prompt — its false positives are the underlying cause.

Decisions needed

  • Capture definition. Should "capture / booking rate" be measured off is_bookable (the live dashboard number) or off the reason-derived bookability? They differ — that's why internal analysis and the client dashboard can diverge.
  • is_booked meaning. is_booked=true now includes "always-lands" saves (jobs with placeholder data). Decide whether reporting treats those as bookings or distinguishes clean bookings from caveat saves.
  • Override scope. Skip the confirmed_booking override for EAS only (flag), or revisit it for all call-reasons teams.
  • Alert vs bookability split. The alerts gate on appointmentBooked, independent of is_booked/is_bookable — a told-booked-but-failed call is is_booked=false plus an URGENT email, by design. Confirm that's the intended split.

Reference

  • Bookability override: apps/web/lib/workflow/stages/booking/booking-info.ts (evaluateBookabilityBySource, resolveBookability).
  • Booking outcome / always-lands: apps/web/lib/workflow/stages/booking-autoops/{run-booking-autoops,auto-service-post-call}.ts, apps/web/lib/auto-service/booking-flags.ts.
  • Alerts: apps/web/lib/workflow/stages/booking-autoops/{booking-failure-alert,booking-failure-urgent-email}.ts.
  • Taxonomy + report tooling (FDE toolkit): .indusk/call-reasons/ (taxonomy versions, sync reconciler, backfill, report generator). Enterprise email redesign: avoca-next branch feat/enterprise-report-redesign.