feed: trending objects/actors by recent activity window, deterministic tiebreak + 11 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SUITES=(basic fanout rank integration content notify home dedupe)
|
SUITES=(basic fanout rank integration content notify home dedupe trending)
|
||||||
|
|
||||||
OUT_JSON="lib/feed/scoreboard.json"
|
OUT_JSON="lib/feed/scoreboard.json"
|
||||||
OUT_MD="lib/feed/scoreboard.md"
|
OUT_MD="lib/feed/scoreboard.md"
|
||||||
@@ -40,6 +40,7 @@ run_suite() {
|
|||||||
(load "lib/feed/content.sx")
|
(load "lib/feed/content.sx")
|
||||||
(load "lib/feed/notify.sx")
|
(load "lib/feed/notify.sx")
|
||||||
(load "lib/feed/home.sx")
|
(load "lib/feed/home.sx")
|
||||||
|
(load "lib/feed/trending.sx")
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(eval "(define feed-test-pass 0)")
|
(eval "(define feed-test-pass 0)")
|
||||||
(eval "(define feed-test-fail 0)")
|
(eval "(define feed-test-fail 0)")
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
"content": {"pass": 15, "fail": 0},
|
"content": {"pass": 15, "fail": 0},
|
||||||
"notify": {"pass": 8, "fail": 0},
|
"notify": {"pass": 8, "fail": 0},
|
||||||
"home": {"pass": 6, "fail": 0},
|
"home": {"pass": 6, "fail": 0},
|
||||||
"dedupe": {"pass": 9, "fail": 0}
|
"dedupe": {"pass": 9, "fail": 0},
|
||||||
|
"trending": {"pass": 11, "fail": 0}
|
||||||
},
|
},
|
||||||
"total_pass": 143,
|
"total_pass": 154,
|
||||||
"total_fail": 0,
|
"total_fail": 0,
|
||||||
"total": 143
|
"total": 154
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ _Generated by `lib/feed/conformance.sh`_
|
|||||||
| notify | 8 | 0 | 8 |
|
| notify | 8 | 0 | 8 |
|
||||||
| home | 6 | 0 | 6 |
|
| home | 6 | 0 | 6 |
|
||||||
| dedupe | 9 | 0 | 9 |
|
| dedupe | 9 | 0 | 9 |
|
||||||
| **Total** | **143** | **0** | **143** |
|
| trending | 11 | 0 | 11 |
|
||||||
|
| **Total** | **154** | **0** | **154** |
|
||||||
|
|||||||
82
lib/feed/tests/trending.sx
Normal file
82
lib/feed/tests/trending.sx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
; Follow-up — trending objects/actors by recent activity. (feed-test name got expected)
|
||||||
|
|
||||||
|
; window (50,100]: X@60,X@70 (a), Y@80 (b), Z@90 (c); W@40 is too old
|
||||||
|
(define
|
||||||
|
S
|
||||||
|
(feed/stream
|
||||||
|
(list
|
||||||
|
(feed/activity "a" "post" "X" 60 (list))
|
||||||
|
(feed/activity "a" "post" "X" 70 (list))
|
||||||
|
(feed/activity "b" "post" "Y" 80 (list))
|
||||||
|
(feed/activity "c" "post" "Z" 90 (list))
|
||||||
|
(feed/activity "d" "post" "W" 40 (list)))))
|
||||||
|
|
||||||
|
; ---------- trending objects ----------
|
||||||
|
|
||||||
|
(feed-test
|
||||||
|
"trending count (3 in window)"
|
||||||
|
(len (feed/trending S 100 50 10))
|
||||||
|
3)
|
||||||
|
(feed-test
|
||||||
|
"trending top object"
|
||||||
|
(get
|
||||||
|
(nth (feed/trending S 100 50 10) 0)
|
||||||
|
:object)
|
||||||
|
"X")
|
||||||
|
(feed-test
|
||||||
|
"trending top count"
|
||||||
|
(get
|
||||||
|
(nth (feed/trending S 100 50 10) 0)
|
||||||
|
:count)
|
||||||
|
2)
|
||||||
|
(feed-test
|
||||||
|
"trending order (count desc, key asc tiebreak)"
|
||||||
|
(map
|
||||||
|
(fn (e) (get e :object))
|
||||||
|
(feed/trending S 100 50 10))
|
||||||
|
(list "X" "Y" "Z"))
|
||||||
|
(feed-test
|
||||||
|
"trending top-2"
|
||||||
|
(map
|
||||||
|
(fn (e) (get e :object))
|
||||||
|
(feed/trending S 100 50 2))
|
||||||
|
(list "X" "Y"))
|
||||||
|
(feed-test
|
||||||
|
"old object W excluded"
|
||||||
|
(feed/-elem?
|
||||||
|
"W"
|
||||||
|
(map
|
||||||
|
(fn (e) (get e :object))
|
||||||
|
(feed/trending S 100 50 10)))
|
||||||
|
false)
|
||||||
|
(feed-test
|
||||||
|
"narrow window keeps only newest"
|
||||||
|
(map
|
||||||
|
(fn (e) (get e :object))
|
||||||
|
(feed/trending S 100 15 10))
|
||||||
|
(list "Z"))
|
||||||
|
(feed-test
|
||||||
|
"empty window -> nothing"
|
||||||
|
(feed/trending S 100 5 10)
|
||||||
|
(list))
|
||||||
|
|
||||||
|
; ---------- trending actors ----------
|
||||||
|
|
||||||
|
(feed-test
|
||||||
|
"trending actor top"
|
||||||
|
(get
|
||||||
|
(nth (feed/trending-actors S 100 50 10) 0)
|
||||||
|
:actor)
|
||||||
|
"a")
|
||||||
|
(feed-test
|
||||||
|
"trending actor count"
|
||||||
|
(get
|
||||||
|
(nth (feed/trending-actors S 100 50 10) 0)
|
||||||
|
:count)
|
||||||
|
2)
|
||||||
|
(feed-test
|
||||||
|
"trending actors order"
|
||||||
|
(map
|
||||||
|
(fn (e) (get e :actor))
|
||||||
|
(feed/trending-actors S 100 50 10))
|
||||||
|
(list "a" "b" "c"))
|
||||||
42
lib/feed/trending.sx
Normal file
42
lib/feed/trending.sx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
; feed/trending — what's hot right now: objects (or actors) ranked by activity
|
||||||
|
; count within a recency window. Deterministic: count descending, ties broken by
|
||||||
|
; key ascending (entries are pre-sorted by key, then stable grade-down by count).
|
||||||
|
;
|
||||||
|
; Requires: lib/feed/stream.sx, lib/feed/aggregate.sx (object/actor-counts),
|
||||||
|
; lib/feed/rank.sx (feed/-desc-by).
|
||||||
|
|
||||||
|
; activities within (now-window, now]
|
||||||
|
(define
|
||||||
|
feed/-recent
|
||||||
|
(fn
|
||||||
|
(stream now window)
|
||||||
|
(feed/filter
|
||||||
|
stream
|
||||||
|
(fn (a) (and (<= (get a :at) now) (> (get a :at) (- now window)))))))
|
||||||
|
|
||||||
|
; counts dict -> top-N entries {label key, :count n}, count desc, key asc
|
||||||
|
(define
|
||||||
|
feed/-top-counts
|
||||||
|
(fn
|
||||||
|
(counts label n)
|
||||||
|
(let
|
||||||
|
((entries (map (fn (k) (assoc {:count (get counts k)} label k)) (sort (keys counts)))))
|
||||||
|
(take (feed/-desc-by entries (fn (e) (get e :count))) n))))
|
||||||
|
|
||||||
|
; top-N trending objects in the window
|
||||||
|
(define
|
||||||
|
feed/trending
|
||||||
|
(fn
|
||||||
|
(stream now window n)
|
||||||
|
(feed/-top-counts
|
||||||
|
(feed/object-counts (feed/-recent stream now window))
|
||||||
|
:object n)))
|
||||||
|
|
||||||
|
; top-N most active actors in the window
|
||||||
|
(define
|
||||||
|
feed/trending-actors
|
||||||
|
(fn
|
||||||
|
(stream now window n)
|
||||||
|
(feed/-top-counts
|
||||||
|
(feed/actor-counts (feed/-recent stream now window))
|
||||||
|
:actor n)))
|
||||||
@@ -14,7 +14,7 @@ APL, ACL visibility filtering via `lib/acl/`, federation via fed-sx.
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/feed/conformance.sh` → **143/143** (Phases 1–4 + TF-IDF + notifications + home + smart-dedupe)
|
`bash lib/feed/conformance.sh` → **154/154** (Phases 1–4 + TF-IDF + notifications + home + smart-dedupe + trending)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -151,6 +151,9 @@ are function parameters. Real acl-sx / fed-sx wire in at the call site unchanged
|
|||||||
`feed/smart-key`: reactions (like/follow/boost/...) collapse cross-actor on
|
`feed/smart-key`: reactions (like/follow/boost/...) collapse cross-actor on
|
||||||
(verb,object); posts stay distinct per actor. `feed/collapse-verbs` is
|
(verb,object); posts stay distinct per actor. `feed/collapse-verbs` is
|
||||||
rebindable policy; 9 tests. (143/143 total.)
|
rebindable policy; 9 tests. (143/143 total.)
|
||||||
|
- [x] Trending — `feed/trending` / `feed/trending-actors`: objects/actors ranked
|
||||||
|
by activity count in a recency window, count-desc with key-asc tiebreak
|
||||||
|
(`trending.sx`); 11 tests. (154/154 total.)
|
||||||
|
|
||||||
(none)
|
(none)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user