Availability queries
The classifier picked a specialty. The catalog picked a doctor. Now we need three slot options the user can choose from. In a real system, this would hit a calendar service. For the workshop, we derive deterministic weekday slots so the UX feels real without any external dependency.
def list_slots(doctor_id: int, count: int = 3) -> List[str]:
"""Generate deterministic available slots for a given doctor.
In a real system this would query a calendar service. For this workshop
we derive upcoming weekday slots from today so the UX feels real.
"""
base = datetime.now().replace(minute=0, second=0, microsecond=0) + timedelta(days=1)
hours = [9, 11, 14, 16]
slots: List[str] = []
d = base
while len(slots) < count:
if d.weekday() < 5: # Mon-Fri
hour = hours[(doctor_id + len(slots)) % len(hours)]
when = d.replace(hour=hour)
slots.append(when.strftime("%A %b %d, %I:%M %p"))
d += timedelta(days=1)
return slotsThe helper returns human-readable slot strings. Skipping weekends keeps the output realistic. Seeding the hour rotation with doctor_id means different doctors get different-looking availability, so demo conversations feel varied.
async def propose_slots(state: BookingState) -> BookingState:
doctor = state.get("doctor")
if not doctor:
state["assistant_reply"] = (
"I couldn't find a matching doctor right now. "
"Would you like to try another specialty?"
)
state["status"] = "ended"
return state
slots = list_slots(doctor["id"], count=3)
state["slot_options"] = slots
bullet = "\n".join(f" {i+1}. {s}" for i, s in enumerate(slots))
state["assistant_reply"] = (
f"I'd recommend {doctor['name']} "
f"({doctor['specialty'].replace('_', ' ')}, "
f"{doctor['years_experience']} years experience).\n"
f"Here are the next available slots:\n{bullet}\n"
"Reply with the slot number to confirm."
)
state["status"] = "ready_to_confirm"
_record_event(state, "propose_slots", {"slots": slots})
return stateThe node writes slot_options (structured, for the confirm node to consume), assistant_reply (human-facing), and status = ready_to_confirm (so the next turn jumps directly to confirm_booking).
Because this node is the last one to run on this turn. After the slot options are presented, the turn ends and the user gets to respond. When they come back with "2", the next turn starts fresh, reads the status from thread memory, and sees ready_to_confirm. That tells the runner to skip the router and go straight to confirm_booking. The status field is effectively a message from this turn to the next one.
Checkpoint: Data node checkpoint
Loading practice…