Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Reports gain an :at tick (deterministic, supplied). mod/decide-temporal counts reports about a subject within [now-window, now], asserts burst_count/2, and a (:burst-at-least K) rule fires only on a real burst. 3 reports at 10/11/12 → hide; 3 at 1/2/12 (window 5) → keep, while the plain count rule escalates both. Fifth report field threaded through rebuild helpers, non-breaking. +15 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
2.1 KiB
Plaintext
63 lines
2.1 KiB
Plaintext
;; lib/mod/temporal.sx — burst detection over a time window.
|
|
;;
|
|
;; A plain report count can't tell a burst (N reports in minutes) from slow
|
|
;; accumulation (N reports over months). mod/decide-temporal takes a `now` tick
|
|
;; and a `window`, counts reports about the subject with :at within [now-window,
|
|
;; now], asserts it as burst_count/2, and lets a `(:burst-at-least K)` rule fire
|
|
;; only on a genuine burst. Time is supplied (deterministic), never clock-read.
|
|
|
|
(define
|
|
mod/window-count
|
|
(fn
|
|
(subject reports now window)
|
|
(reduce
|
|
(fn
|
|
(acc r)
|
|
(if
|
|
(if
|
|
(= (mod/report-about r) subject)
|
|
(<= (- now window) (mod/report-at r))
|
|
false)
|
|
(+ acc 1)
|
|
acc))
|
|
0
|
|
reports)))
|
|
|
|
(define
|
|
mod/build-temporal-program
|
|
(fn
|
|
(r count bcount rules)
|
|
(str
|
|
(mod/report-facts r count)
|
|
"\n"
|
|
"burst_count("
|
|
(mod/pl-quote (mod/report-about r))
|
|
", "
|
|
bcount
|
|
").\n"
|
|
(mod/rules->program rules))))
|
|
|
|
(define
|
|
mod/decide-temporal
|
|
(fn
|
|
(r reports rules now window)
|
|
(let
|
|
((about (mod/report-about r))
|
|
(id (mod/report-id r))
|
|
(kinds (mod/classify-keywords r)))
|
|
(let
|
|
((count (mod/report-count about reports))
|
|
(bcount (mod/window-count about reports now window)))
|
|
(let
|
|
((program (mod/build-temporal-program r count bcount rules)))
|
|
(let
|
|
((db (pl-load program)))
|
|
(let
|
|
((sol (pl-query-one db (str "policy_action(" id ", Action, Rule)"))))
|
|
(if
|
|
(nil? sol)
|
|
{:action "keep" :proof {:burst bcount :goals (list) :evidence kinds :conditions (list) :rule "none" :count count} :report-id id :rule "none" :strategy "temporal"}
|
|
(let
|
|
((rule (mod/find-rule rules (dict-get sol "Rule"))))
|
|
{:action (mod/rule-action rule) :proof {:burst bcount :goals (mod/proof-goals db id (mod/rule-when rule)) :evidence kinds :conditions (mod/rule-when rule) :rule (mod/rule-name rule) :count count} :report-id id :rule (mod/rule-name rule) :strategy "temporal"})))))))))
|