;; lib/commerce/window.sx — time-windowed promotions. ;; ;; A promo's validity window is kept SEPARATE from the promo tuple (so promo.sx ;; is untouched): a windowed promo is (list promo from until) with inclusive ;; integer timestamps (same time model as the ledger `at`). nil from = no lower ;; bound; nil until = open-ended. ;; ;; `active-ruleset` filters a windowed ruleset to the plain promos live at a ;; given time, which feeds straight into promo/stack/quote — so a datetime-aware ;; quote is just the existing pipeline over the active set. Deterministic: the ;; quote is a pure function of (ctx, cart, windowed-ruleset, exclusions, at). (define windowed-promo (fn (promo from until) (list promo from until))) (define wp-promo (fn (wp) (nth wp 0))) (define wp-from (fn (wp) (nth wp 1))) (define wp-until (fn (wp) (nth wp 2))) (define wp-active? (fn (wp at) (let ((from (wp-from wp)) (until (wp-until wp))) (and (or (nil? from) (>= at from)) (or (nil? until) (<= at until)))))) ;; Plain promo tuples live at time `at` — feed into cart-quote / best-promo-*. (define active-ruleset (fn (windowed at) (map wp-promo (filter (fn (wp) (wp-active? wp at)) windowed)))) ;; Relation: which promo codes are active at `at`? (backward query) (define active-promoo (fn (windowed at code) (fresh (wp) (membero wp windowed) (project (wp) (if (wp-active? wp at) (== code (promo-code (wp-promo wp))) fail))))) (define active-codes (fn (windowed at) (run* code (active-promoo windowed at code)))) ;; Datetime-aware quote: the existing pipeline over the time-active ruleset. (define windowed-quote (fn (ctx cart windowed exclusions at) (cart-quote ctx cart (active-ruleset windowed at) exclusions)))