Appearance
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 makesis_bookablereason-driven and brings theconfirmed_bookingoverride 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=truenow also counts these caveat saves. - Alert tuning (#11858 → #12180). #11858 widened the
appointmentBookedsignal 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_idwas backfilled for 307 calls in 6/22–28 (filling only empty reasons;is_bookableleft untouched). On those calls the reason andis_bookableare therefore not reconciled. The weekly enterprise report was rebuilt and sent to EAS admins (matched to the dashboard).
What's needed
- Skip the
confirmed_bookingoverride for EAS (flag-gated) — the primary fix. InevaluateBookabilityBySource, when a feature flag is enabled for the team/enterprise, passundefinedforconfirmedBookingsois_bookable = reason.isBookabledirectly. Add the flag, target enterprise 117, off by default. Removes the phantom bookable leads; reversible; A/B-able. (Drafted, not yet shipped — touches sharedresolveBookabilitybehavior, gated to EAS, so needs Avoca review.) - Re-derive
is_bookablefrom the reasons on the backfilled calls (and any others where reason and bookability disagree), so they reconcile. - If the override is kept anywhere, improve the
confirmed_booking_yes_or_noextraction 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_bookedmeaning.is_booked=truenow 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_bookingoverride for EAS only (flag), or revisit it for all call-reasons teams. - Alert vs bookability split. The alerts gate on
appointmentBooked, independent ofis_booked/is_bookable— a told-booked-but-failed call isis_booked=falseplus 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 branchfeat/enterprise-report-redesign.