Part 4 of 7 · Booking assistant series ~5 min read

How the assistant picks slots

A structured request walks in. The scheduler walks out with two or three slots that fit the customer’s window, your working hours, the service’s real duration, the buffer you need before the next appointment, your blackout dates, and which staff member can do which service. The trick is doing this entirely on top of Google Calendar — never a parallel database that drifts.

Slot picking: a pipeline of constraint filters narrows the candidate set A horizontal flow of five filter stages. From left, the structured request enters with a service ID and a time window. Stage one, “Calendar query,” calls Google Calendar’s freebusy API for the requested window across all qualified staff calendars and produces a raw list of free intervals. Stage two, “Working hours,” intersects each free interval with the business’s working hours from the service-rules file and drops anything outside them. Stage three, “Duration + buffers,” takes the service’s required duration and the configured pre/post buffer minutes, and slides a window of (duration + buffers) across the remaining intervals to produce candidate start times. Stage four, “Blackouts & capacity,” removes start times that fall on holiday or blackout dates from the rules file, and removes any that exceed the per-day or per-resource capacity caps. Stage five, “Rank & pick,” ranks remaining candidates by preference (earliest in the customer’s window, customer’s usual stylist if any, lower utilisation slots first) and picks the top 2–3. A bottom branch labelled ‘no candidates remain’ goes to a polite decline path. A bottom note reads: every constraint comes from the service-rules file or the calendar — nothing is hard-coded in the assistant. Structured request service + window + contact stage 1 Calendar query freebusy across qualified staff stage 2 Working hours intersect with open hours stage 3 Duration + buffers slide a window of (duration + pre/post buffers) across remaining intervals stage 4 Blackouts & capacity drop holidays, blackout dates, over-capacity start times stage 5 Rank & pick earliest, usual stylist, lower utilisation first Top 2–3 slots propose to customer via email or web reply no candidates remain Polite decline offer the nearest alt window, or escalate to a human Every constraint comes from the rules file or the calendar — nothing is hard-coded in the assistant.
Fig 4. Five filters in order. Each stage is cheap; the expensive ones (the calendar query, the ranking) only run on what survived the cheap ones.

The calendar is the source of truth

Most booking systems get into trouble by keeping a parallel availability database. The calendar says one thing; the database says another; somebody books over an existing event because the database is stale. The fix here is to never have the parallel database in the first place. Every availability check goes to Google Calendar’s freebusy API at request time, and every confirmed booking is written to the calendar atomically (the next post is about that).

The freebusy API is fast (typically under 100 ms for a 7-day window across a small number of calendars) and free at the volumes a small business sees. Caching is tempting but unnecessary; the latency budget for a slot proposal is several seconds, of which the calendar query is a small slice.

Stage 1 — Calendar query

The scheduler asks the calendar for free intervals across every staff member who can perform the requested service. The service-rules file maps each service to a list of qualified resources (people, rooms, machines), and the scheduler queries the calendars of all of them in a single freebusy call. The result is a list of free intervals per resource, in the customer’s requested window.

Stage 2 — Working hours

The freebusy API doesn’t know your working hours; it just knows what’s on the calendar. The scheduler intersects each free interval with the business’s open hours from the rules file. A free interval from 11pm to 9am is real on the calendar, but useless to the customer; this stage drops it.

Per-staff working hours are supported here too — the rules file can specify that stylist A only works Tuesday–Saturday, while stylist B works Monday–Friday, and the scheduler intersects against the right set per resource.

Stage 3 — Duration and buffers

The service has a real duration (a basic haircut takes 30 minutes; a colour takes 90; a first visit takes 60 because of intake forms). It also has buffers — the time you need before and after the appointment for setup, cleanup, or breathing room. The scheduler slides a window of (duration + pre + post) minutes across the surviving free intervals at a configurable step size (15 min by default), and emits one candidate start time per fit.

Buffers are why this isn’t just “is this slot free?” If the previous appointment ends at 2:00 and your post-buffer is 10 min, the next bookable start is 2:10, not 2:00. Skipping this is how you end up with no time to clean up between clients.

Stage 4 — Blackouts and capacity

Two more cuts. The blackout list (public holidays, your annual leave, the day the salon is closed for inventory) drops candidate starts that fall on those dates. Capacity caps drop candidates that would push the day or the resource over a configured limit — useful for businesses where the constraint isn’t just “the staff member is busy” but “we only do four colours per day so we don’t run out of dye.”

Stage 5 — Rank and pick

What survives gets ranked by preference, and the top 2–3 are returned. The default ranking puts earliest-in-window first (most customers want the soonest slot), then prefers the customer’s usual staff member if known, then prefers slots that smooth the day’s utilisation rather than concentrate it.

The ranking is configurable in the rules file, not hard-coded. Some businesses want the opposite — pack the morning so afternoons are free; some want premium services to go to the senior stylist by default. One config line, no deploy.

When nothing fits

Sometimes there’s nothing in the customer’s window. The scheduler doesn’t shrug; it offers the nearest alternative window (“Tuesday afternoon is fully booked, but I have Wednesday morning”), or escalates to a human if the alternative would be a substantial change. A polite decline is one of the four moves — not an error.

In plain words

Five filters in order. Get free time from the calendar. Trim it to working hours. Fit the service’s actual duration plus buffers. Drop blackouts and over-capacity slots. Rank what’s left and propose the top two or three. The scheduler does no thinking the calendar and the rules file haven’t already authorised.

All posts