events: next-free slot search + 6 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s

ev-next-free finds the earliest free slot >= after for a duration within a
horizon, probing 'after' + busy-interval ends via the busy_in rule (ev-free?).
Finds gaps, skips too-short gaps, half-open at edges. 59/59 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 00:49:42 +00:00
parent 540933bfca
commit 4674b797cb
5 changed files with 146 additions and 10 deletions

View File

@@ -16,7 +16,7 @@
;; adjacent slots (E == next start) do NOT conflict. Conflict pairs are
;; canonical (O1 < O2 by key) so each overlap is reported once. The same `busy`
;; rule answers "is A free in [QS,QE)?" (busy_in is empty) and feeds "when is A
;; next free?" — same rules, different bindings.
;; next free?" (ev-next-free probes candidate slots with the same rule).
;; A stable key for an occurrence dict {:id :start :end}.
(define ev-occ-key (fn (occ) (str (get occ :id) "@" (get occ :start))))
@@ -98,6 +98,16 @@
ev-sort-lists
(fn (xs) (reduce (fn (acc x) (ev-list-insert x acc)) (list) xs)))
(define
ev-dedup-sorted
(fn
(xs)
(cond
((empty? xs) xs)
((empty? (rest xs)) xs)
((= (first xs) (first (rest xs))) (ev-dedup-sorted (rest xs)))
(else (cons (first xs) (ev-dedup-sorted (rest xs)))))))
;; All busy intervals (list S E) for an actor, ascending by start.
(define
ev-busy
@@ -129,3 +139,39 @@
(let
((rows (dl-query db (list (quote busy_in) actor (quote QS) (quote QE)))))
(begin (dl-retract! db (ev-qwindow-fact qs qe)) (empty? rows))))))
;; ---- next-free slot search ----
;; The earliest start s >= `after` such that [s, s+duration) is entirely free
;; for `actor` and ends at or before `horizon`, or nil if none. The earliest
;; such slot must begin either at `after` or immediately after some busy
;; interval ends (classic interval packing), so those are the only candidates
;; we probe — each probe reuses the busy_in rule via ev-free?.
(define
ev-first-free
(fn
(db actor cands duration horizon)
(cond
((empty? cands) nil)
(else
(let
((s (first cands)))
(if
(and
(<= (+ s duration) horizon)
(ev-free? db actor s (+ s duration)))
s
(ev-first-free db actor (rest cands) duration horizon)))))))
(define
ev-next-free
(fn
(db actor after duration horizon)
(let
((ends (filter (fn (e) (>= e after)) (map (fn (iv) (first (rest iv))) (ev-busy db actor)))))
(ev-first-free
db
actor
(ev-dedup-sorted (sort (cons after ends)))
duration
horizon))))