;; lib/events/api.sx — public events surface over calendar + availability. ;; ;; A `store` is an immutable value holding scheduled events and bookings: ;; ;; {:events (event ...) :bookings ((actor key) ...)} ;; ;; All queries are windowed: agenda/free/next-free expand recurring events into ;; concrete occurrences within an explicit (or derived) window before running ;; the Datalog availability rules. Phase 2 replaces `ev/book` with a capacity- ;; safe persist append; the rest of this facade stays put. (define ev/store (fn (events bookings) {:bookings bookings :events events})) (define ev/empty (fn () (ev/store (list) (list)))) (define ev/events (fn (store) (get store :events))) (define ev/bookings (fn (store) (get store :bookings))) ;; Add a (constructed) event to the store. (define ev/add-event (fn (store event) (ev/store (cons event (ev/events store)) (ev/bookings store)))) ;; Schedule a fresh event from parts, returning the updated store. rrule may be ;; nil for a one-off. (Booking is separate — see ev/book.) (define ev/schedule (fn (store id dtstart duration rrule capacity) (ev/add-event store (ev-event id dtstart duration rrule capacity)))) ;; Record that `actor` holds the occurrence with `key` (ev-occ-key of an ;; expanded occurrence). Phase 1: append-only, no capacity check. (define ev/book (fn (store actor key) (ev/store (ev/events store) (cons (list actor key) (ev/bookings store))))) ;; The maximum event duration in the store (0 when empty) — used to widen ;; expansion windows so any occurrence overlapping a query is captured. (define ev/store-max-duration (fn (store) (reduce (fn (m ev) (max m (get ev :duration))) 0 (ev/events store)))) ;; All occurrences across all events within [ws, we), ascending by start. (define ev/agenda (fn (store ws we) (ev-expand-all (ev/events store) ws we))) (define ev-key-member? (fn (k keys) (cond ((empty? keys) false) ((= k (first keys)) true) (else (ev-key-member? k (rest keys)))))) ;; Occurrence keys `actor` has booked. (define ev/actor-keys (fn (store actor) (reduce (fn (acc b) (if (= (first b) actor) (cons (first (rest b)) acc) acc)) (list) (ev/bookings store)))) ;; The agenda restricted to occurrences `actor` is booked into, within window. (define ev/agenda-for (fn (store actor ws we) (let ((keys (ev/actor-keys store actor))) (filter (fn (o) (ev-key-member? (ev-occ-key o) keys)) (ev/agenda store ws we))))) ;; Build an availability db over occurrences expanded in [ws, we). (define ev/avail-window-db (fn (store ws we) (ev-avail-db (ev/agenda store ws we) (ev/bookings store)))) ;; Is `actor` free across [qs, qe)? Expands a window wide enough (back by the ;; longest event) to capture any occurrence that could overlap. (define ev/free? (fn (store actor qs qe) (ev-free? (ev/avail-window-db store (- qs (ev/store-max-duration store)) qe) actor qs qe))) ;; Earliest free slot of `duration` for `actor` in [after, horizon), or nil. (define ev/next-free (fn (store actor after duration horizon) (ev-next-free (ev/avail-window-db store (- after (ev/store-max-duration store)) horizon) actor after duration horizon))) ;; Overlapping double-bookings for `actor` among occurrences in [ws, we). (define ev/conflicts (fn (store actor ws we) (ev-conflicts (ev/avail-window-db store ws we) actor))) (define ev/has-conflict? (fn (store actor ws we) (> (len (ev/conflicts store actor ws we)) 0)))