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.

db_utils.py
python
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 slots

The 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.

booking_graph.py
python
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 state

The 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…