;; lib/mod/link.sx — report linking + deduplication. ;; ;; Reports about the same subject form a cluster; identical reports (same ;; reporter + subject + reason) are duplicates. Linking is Prolog-backed: all ;; report facts are loaded and related ids are found by unification — the same ;; relational substrate the policy engine uses, here for retrieval rather than ;; decision. Dedup is pure SX over a normalized link key. (define mod/link-key (fn (r) (str (mod/report-by r) "|" (mod/report-about r) "|" (downcase (mod/report-reason r))))) (define mod/dedup-reports (fn (reports) (reduce (fn (acc r) (if (mod/any? (fn (x) (= (mod/link-key x) (mod/link-key r))) acc) acc (append acc (list r)))) (list) reports))) (define mod/duplicate-count (fn (reports) (- (len reports) (len (mod/dedup-reports reports))))) ;; ── Prolog-backed relational retrieval ── (define mod/report-rel-facts (fn (reports) (mod/join-with "\n" (map (fn (r) (str "report(" (mod/report-id r) ", " (mod/pl-quote (mod/report-by r)) ", " (mod/pl-quote (mod/report-about r)) ").")) reports)))) (define mod/related-ids (fn (subject reports) (let ((db (pl-load (mod/report-rel-facts reports)))) (map (fn (sol) (dict-get sol "Id")) (pl-query-all db (str "report(Id, _, " (mod/pl-quote subject) ")")))))) (define mod/reporters-of (fn (subject reports) (let ((db (pl-load (mod/report-rel-facts reports)))) (map (fn (sol) (dict-get sol "By")) (pl-query-all db (str "report(_, By, " (mod/pl-quote subject) ")")))))) (define mod/distinct (fn (items) (reduce (fn (acc x) (if (mod/any? (fn (y) (= y x)) acc) acc (append acc (list x)))) (list) items))) (define mod/distinct-reporters-of (fn (subject reports) (mod/distinct (mod/reporters-of subject reports))))