; Phase 2 — fanout via outer product + dedupe. (feed-test name got expected) ; ---------- graph ---------- ; edges: (follower followee). bob,carol follow alice; carol,dave follow bob. (define G (feed/follow-graph (list (list "bob" "alice") (list "carol" "alice") (list "carol" "bob") (list "dave" "bob")))) (feed-test "followers alice" (feed/followers G "alice") (list "bob" "carol")) (feed-test "followers bob" (feed/followers G "bob") (list "carol" "dave")) (feed-test "followers unknown" (feed/followers G "zzz") (list)) (feed-test "audience distinct" (feed/audience G) (list "bob" "carol" "dave")) ; ---------- fanout ---------- (define S (feed/stream (list (feed/activity "alice" "post" "p1" 10 (list)) (feed/activity "alice" "post" "p2" 20 (list)) (feed/activity "bob" "like" "p1" 30 (list))))) (define IB (feed/fanout S G)) (feed-test "fanout total edges" (feed/count IB) 6) (feed-test "inbox bob count" (feed/count (feed/inbox-for IB "bob")) 2) (feed-test "inbox carol count" (feed/count (feed/inbox-for IB "carol")) 3) (feed-test "inbox dave count" (feed/count (feed/inbox-for IB "dave")) 1) (feed-test "inbox alice (follows none)" (feed/count (feed/inbox-for IB "alice")) 0) (feed-test "recipients order" (feed/recipients IB) (list "bob" "carol" "dave")) (feed-test "bob inbox objects" (map (fn (a) (get a :object)) (feed/inbox-activities IB "bob")) (list "p1" "p2")) (feed-test "dave inbox objects" (map (fn (a) (get a :object)) (feed/inbox-activities IB "dave")) (list "p1")) (feed-test "dave inbox verb" (map (fn (a) (get a :verb)) (feed/inbox-activities IB "dave")) (list "like")) ; empty graph → no audience → no edges (feed-test "empty graph fanout" (feed/count (feed/fanout S {})) 0) ; actor nobody follows produces no edges (define Sghost (feed/stream (list (feed/activity "ghost" "post" "g1" 5 (list))))) (feed-test "unfollowed actor fanout" (feed/count (feed/fanout Sghost G)) 0) ; ---------- high fanout (popular actor) ---------- (define Gstar (feed/follow-graph (list (list "u1" "star") (list "u2" "star") (list "u3" "star") (list "u4" "star") (list "u5" "star")))) (define Sstar (feed/stream (list (feed/activity "star" "post" "s1" 1 (list))))) (feed-test "star fanout count" (feed/count (feed/fanout Sstar Gstar)) 5) (feed-test "star audience size" (len (feed/audience Gstar)) 5) ; ---------- mutual follow ---------- (define Gmut (feed/follow-graph (list (list "a" "b") (list "b" "a")))) (define Smut (feed/stream (list (feed/activity "a" "post" "pa" 1 (list)) (feed/activity "b" "post" "pb" 2 (list))))) (define IBmut (feed/fanout Smut Gmut)) (feed-test "mutual total" (feed/count IBmut) 2) (feed-test "mutual a gets pb" (map (fn (x) (get x :object)) (feed/inbox-activities IBmut "a")) (list "pb")) (feed-test "mutual b gets pa" (map (fn (x) (get x :object)) (feed/inbox-activities IBmut "b")) (list "pa")) ; ---------- dedupe ---------- (define Sdup2 (feed/stream (list (feed/activity "alice" "post" "p1" 1 (list)) (feed/activity "alice" "post" "p1" 9 (list)) (feed/activity "alice" "post" "p2" 2 (list))))) (feed-test "dedupe-activities collapses dup" (feed/count (feed/dedupe-activities Sdup2)) 2) (feed-test "dedupe-activities keeps distinct" (map (fn (a) (get a :object)) (feed/items (feed/dedupe-activities Sdup2))) (list "p1" "p2")) (define Slikes (feed/stream (list (feed/activity "alice" "like" "X" 1 (list)) (feed/activity "bob" "like" "X" 2 (list)) (feed/activity "carol" "like" "Y" 3 (list))))) (feed-test "collapse cross-actor likes" (feed/count (feed/dedupe-collapse Slikes)) 2) (feed-test "collapse keeps distinct objects" (map (fn (a) (get a :object)) (feed/items (feed/dedupe-collapse Slikes))) (list "X" "Y")) (feed-test "activity-key shape" (feed/activity-key (feed/activity "a" "post" "o" 0 (list))) (list "a" "post" "o")) (feed-test "collapse-key shape" (feed/collapse-key (feed/activity "a" "like" "o" 0 (list))) (list "like" "o")) ; cross-post: alice posts p1 twice → bob's inbox has it twice → dedupe-inbox → once (define Scross (feed/stream (list (feed/activity "alice" "post" "p1" 1 (list)) (feed/activity "alice" "post" "p1" 5 (list))))) (define IBcross (feed/fanout Scross G)) (feed-test "cross-post raw bob count" (feed/count (feed/inbox-for IBcross "bob")) 2) (feed-test "cross-post deduped bob count" (feed/count (feed/inbox-for (feed/dedupe-inbox IBcross) "bob")) 1) (feed-test "dedupe-inbox keeps distinct receivers" (feed/count (feed/dedupe-inbox IBcross)) 2)