Handle non-booking requests: take a message, log a callback note

A caller asking if their already-purchased frames were ready got railroaded
through the booking script and hung up. AVA had no path for requests it can't do
on the phone.

- Prompt: classify intent first — question (answer it), can't-do request (take
  name + a one-line note, confirm callback number, promise a staff callback;
  never force booking questions), or booking (the ordered steps).
- extract.py: request_type = appointment | callback | none. Callback gate needs
  a name or a request note. Records kind.
- practice.py / odoo_client.py: callbacks write a "📞 Callback request" lead
  (name, callback number, what they need) instead of an appointment card.

Verified the classifier: frames-status -> callback, booking -> appointment,
pure question -> none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tocmo0nlord
2026-06-29 14:46:23 +00:00
parent 2f7e2629fe
commit 97e109ed89
5 changed files with 90 additions and 37 deletions

View File

@@ -86,9 +86,11 @@ Trade-off: half-duplex — the caller can't barge in mid-utterance (fine for sho
**Post-call extraction (`extract.py`)** — single JSON-mode completion after call ends.
Correctly uses `format: json`, uses verified Twilio caller-ID instead of trusting model
output, falls back to JSONL if Odoo is unreachable. Keep it.
**Lead-quality gate:** a lead is only written if a NAME or a LOCATION was captured — a bare
reason (e.g. "check on my eyes") with no name and no office is skipped (not worth a worklist
card). Captures full name, phone (confirmed/alternate), insurance, reason, and resolved date.
**Classifies `request_type`:** `appointment` (booking), `callback` (a non-booking request staff
must handle off-phone → "📞 Callback request" lead), or `none` (just a question / nothing).
**Lead-quality gate:** appointment needs a NAME or LOCATION; callback needs a NAME or a request
note — otherwise skipped (no near-empty cards). Appointments capture full name, phone
(confirmed/alternate), insurance, reason, resolved date; callbacks capture name + what they need.
**Odoo integration (`odoo_client.py`)** — already uses `ODOO_API_KEY` for XML-RPC auth,
not password. Correct pattern. No changes.
@@ -276,8 +278,19 @@ AB_MODEL_B=
## Call Workflow
AVA runs a directed script (system prompt in `bot.py`) — warm but direct, one short turn at a
time, leading the call rather than waiting on the caller. Fixed order:
time, leading the call rather than waiting on the caller.
**Intent first — three kinds of call:**
- **Question** answerable from the practice facts → just answer it.
- **Can't-do request** (existing order/purchase, frames/lenses/prescription/lab status, billing,
account lookup, reach a person) → do NOT run the booking steps. Say it can't be looked up, take
the caller's name + a one-line note of what they need, confirm the callback number, and promise
a staff callback. Captured as a **callback note** lead in Odoo ("📞 Callback request — …"), NOT
an appointment. (Added after a caller asking if their frames were ready got railroaded into the
booking script and hung up.)
- **Booking** → the ordered steps below.
Booking steps (fixed order):
1. **Reason first** — find out what they're calling about (visit reason, or just a question → answer it).
2. **Location** — ask city/area, confirm the matching office (don't offer others — see office rule).
3. **Caller info** — full name (ask last name if only a first is given), then **address the caller