From 793bffed8c91684c1defefbb9e21e68b638c35d2 Mon Sep 17 00:00:00 2001 From: highperfocused Date: Sun, 23 Feb 2025 23:14:10 +0100 Subject: [PATCH 1/3] WIP --- relay/main.go | 2 +- relay/trending/README.md | 15 ++++++ relay/trending/{kinds.go => basic.go} | 2 +- relay/trending/score.go | 78 +++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 relay/trending/README.md rename relay/trending/{kinds.go => basic.go} (97%) create mode 100644 relay/trending/score.go diff --git a/relay/main.go b/relay/main.go index e4c92f4..11e9135 100644 --- a/relay/main.go +++ b/relay/main.go @@ -252,7 +252,7 @@ func main() { mux.HandleFunc("/api/trending/kind20", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - trendingPosts, err := trending.GetTrendingKind20(db.DB.DB) + trendingPosts, err := trending.GetTrendingScoreKind20(db.DB.DB) if err != nil { http.Error(w, fmt.Sprintf("Error getting trending posts: %v", err), http.StatusInternalServerError) return diff --git a/relay/trending/README.md b/relay/trending/README.md new file mode 100644 index 0000000..123de7d --- /dev/null +++ b/relay/trending/README.md @@ -0,0 +1,15 @@ +## Basic + +A trending algo over simplified based on reactions + +## Score + +Add a trending score calculation that considers time decay and multiple engagement types. + +Key improvements: + +Considers multiple engagement types with different weights +Implements time decay using a power law function +Adds a base time offset (7200 seconds = 2 hours) to smooth out early scoring +More comprehensive trending score instead of just raw counts +The time decay formula is similar to HackerNews' algorithm but adjusted for shorter-term trending content. You might want to tune the weights and decay factors based on your specific needs. \ No newline at end of file diff --git a/relay/trending/kinds.go b/relay/trending/basic.go similarity index 97% rename from relay/trending/kinds.go rename to relay/trending/basic.go index ca394f9..4ad7785 100644 --- a/relay/trending/kinds.go +++ b/relay/trending/basic.go @@ -24,7 +24,7 @@ var ( ) // GetTrendingKind20 returns the top 20 trending posts of kind 20 from the last 24 hours -func GetTrendingKind20(db *sql.DB) ([]Post, error) { +func GetTrendingBasicKind20(db *sql.DB) ([]Post, error) { if cached, ok := trendingCache.Get("trending_kind_20"); ok { return cached.([]Post), nil } diff --git a/relay/trending/score.go b/relay/trending/score.go new file mode 100644 index 0000000..7bdbc87 --- /dev/null +++ b/relay/trending/score.go @@ -0,0 +1,78 @@ +package trending + +import ( + "database/sql" + "encoding/json" +) + +// GetTrendingKind20 returns the top 20 trending posts of kind 20 from the last 24 hours +func GetTrendingScoreKind20(db *sql.DB) ([]Post, error) { + if cached, ok := trendingCache.Get("trending_kind_20"); ok { + return cached.([]Post), nil + } + + query := ` + WITH engagement AS ( + SELECT + tags_expanded.value->1 #>> '{}' AS original_event_id, + e.kind::text, + extract(epoch from now()) - e.created_at as seconds_ago, + COUNT(*) as count + FROM event e + CROSS JOIN LATERAL jsonb_array_elements(tags) as tags_expanded(value) + WHERE e.kind::text IN ('1', '6', '7') -- replies, reposts, reactions + AND e.created_at >= extract(epoch from now() - interval '24 hours')::bigint + AND tags_expanded.value->0 #>> '{}' = 'e' + GROUP BY tags_expanded.value->1 #>> '{}', e.kind::text, e.created_at + ), + scores AS ( + SELECT + original_event_id, + SUM( + CASE + WHEN kind = '7' THEN count * 1.0 -- reactions weight + WHEN kind = '6' THEN count * 2.0 -- reposts weight + WHEN kind = '1' THEN count * 1.5 -- replies weight + END / power((seconds_ago + 7200) / 3600, 1.8) -- time decay + ) as trending_score + FROM engagement + GROUP BY original_event_id + ) + SELECT + e.id, + e.pubkey, + to_timestamp(e.created_at) as created_at, + e.kind, + e.content, + e.tags, + COALESCE(s.trending_score, 0) as reaction_count + FROM event e + LEFT JOIN scores s ON e.id = s.original_event_id + WHERE e.kind::text = '20' + AND e.created_at >= extract(epoch from now() - interval '24 hours')::bigint + ORDER BY reaction_count DESC, e.created_at DESC + LIMIT 20 + ` + + rows, err := db.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + + var trendingPosts []Post + for rows.Next() { + var post Post + var tagsJSON []byte + if err := rows.Scan(&post.ID, &post.PubKey, &post.CreatedAt, &post.Kind, &post.Content, &tagsJSON, &post.ReactionCount); err != nil { + return nil, err + } + if err := json.Unmarshal(tagsJSON, &post.Tags); err != nil { + return nil, err + } + trendingPosts = append(trendingPosts, post) + } + + trendingCache.Set("trending_kind_20", trendingPosts, cacheDuration) + return trendingPosts, nil +} -- 2.47.2 From 7c2b5a49554c6a27edec30dbde7fdc6059c29dda Mon Sep 17 00:00:00 2001 From: highperfocused Date: Sun, 23 Feb 2025 23:22:28 +0100 Subject: [PATCH 2/3] refactor trending functions and introduce Post type for better structure --- relay/trending/basic.go | 12 +----------- relay/trending/score.go | 4 ++-- relay/trending/types.go | 13 +++++++++++++ 3 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 relay/trending/types.go diff --git a/relay/trending/basic.go b/relay/trending/basic.go index 4ad7785..6db2554 100644 --- a/relay/trending/basic.go +++ b/relay/trending/basic.go @@ -8,22 +8,12 @@ import ( "git.highperfocused.tech/highperfocused/lumina-relay/relay/cache" ) -type Post struct { - ID string `json:"id"` - PubKey string `json:"pubkey"` - CreatedAt time.Time `json:"created_at"` - Kind string `json:"kind"` - Content string `json:"content"` - Tags [][]string `json:"tags"` - ReactionCount int `json:"reaction_count"` -} - var ( trendingCache = cache.New() cacheDuration = 5 * time.Minute ) -// GetTrendingKind20 returns the top 20 trending posts of kind 20 from the last 24 hours +// GetTrendingBasicKind20 returns the top 20 trending posts of kind 20 from the last 24 hours func GetTrendingBasicKind20(db *sql.DB) ([]Post, error) { if cached, ok := trendingCache.Get("trending_kind_20"); ok { return cached.([]Post), nil diff --git a/relay/trending/score.go b/relay/trending/score.go index 7bdbc87..5a23fda 100644 --- a/relay/trending/score.go +++ b/relay/trending/score.go @@ -5,7 +5,7 @@ import ( "encoding/json" ) -// GetTrendingKind20 returns the top 20 trending posts of kind 20 from the last 24 hours +// GetTrendingScoreKind20 returns the top 20 trending posts of kind 20 from the last 24 hours func GetTrendingScoreKind20(db *sql.DB) ([]Post, error) { if cached, ok := trendingCache.Get("trending_kind_20"); ok { return cached.([]Post), nil @@ -45,7 +45,7 @@ func GetTrendingScoreKind20(db *sql.DB) ([]Post, error) { e.kind, e.content, e.tags, - COALESCE(s.trending_score, 0) as reaction_count + COALESCE(s.trending_score, 0.0) as reaction_count -- Cast to float FROM event e LEFT JOIN scores s ON e.id = s.original_event_id WHERE e.kind::text = '20' diff --git a/relay/trending/types.go b/relay/trending/types.go new file mode 100644 index 0000000..3b3b5a7 --- /dev/null +++ b/relay/trending/types.go @@ -0,0 +1,13 @@ +package trending + +import "time" + +type Post struct { + ID string `json:"id"` + PubKey string `json:"pubkey"` + CreatedAt time.Time `json:"created_at"` + Kind int `json:"kind"` + Content string `json:"content"` + Tags [][]string `json:"tags"` + ReactionCount float64 `json:"reaction_count"` +} -- 2.47.2 From a0d44e862ade353fdd9b7dd0cf8c3f9f0eec70bf Mon Sep 17 00:00:00 2001 From: highperfocused Date: Mon, 24 Feb 2025 00:01:19 +0100 Subject: [PATCH 3/3] add zaps weight to score algo --- relay/trending/score.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/relay/trending/score.go b/relay/trending/score.go index 5a23fda..6008eb8 100644 --- a/relay/trending/score.go +++ b/relay/trending/score.go @@ -20,7 +20,7 @@ func GetTrendingScoreKind20(db *sql.DB) ([]Post, error) { COUNT(*) as count FROM event e CROSS JOIN LATERAL jsonb_array_elements(tags) as tags_expanded(value) - WHERE e.kind::text IN ('1', '6', '7') -- replies, reposts, reactions + WHERE e.kind::text IN ('1', '6', '7','9735') -- replies, reposts, reactions, zaps AND e.created_at >= extract(epoch from now() - interval '24 hours')::bigint AND tags_expanded.value->0 #>> '{}' = 'e' GROUP BY tags_expanded.value->1 #>> '{}', e.kind::text, e.created_at @@ -33,6 +33,7 @@ func GetTrendingScoreKind20(db *sql.DB) ([]Post, error) { WHEN kind = '7' THEN count * 1.0 -- reactions weight WHEN kind = '6' THEN count * 2.0 -- reposts weight WHEN kind = '1' THEN count * 1.5 -- replies weight + WHEN kind = '9735' THEN count * 1.3 -- zaps weight END / power((seconds_ago + 7200) / 3600, 1.8) -- time decay ) as trending_score FROM engagement -- 2.47.2