Initial commit: twitter-cli v0.1.0
This commit is contained in:
90
twitter_cli/filter.py
Normal file
90
twitter_cli/filter.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Tweet filtering and engagement scoring.
|
||||
|
||||
Scores tweets by a weighted engagement formula and filters by
|
||||
configurable rules (topN, min score, language, etc.).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import Dict, List
|
||||
|
||||
from .models import Tweet
|
||||
|
||||
|
||||
# Type alias for filter weights dict
|
||||
FilterWeights = Dict[str, float]
|
||||
|
||||
DEFAULT_WEIGHTS = {
|
||||
"likes": 1.0,
|
||||
"retweets": 3.0,
|
||||
"replies": 2.0,
|
||||
"bookmarks": 5.0,
|
||||
"views_log": 0.5,
|
||||
}
|
||||
|
||||
|
||||
def score_tweet(tweet, weights=None):
|
||||
# type: (Tweet, FilterWeights) -> float
|
||||
"""Calculate engagement score for a single tweet.
|
||||
|
||||
Formula:
|
||||
score = w_likes × likes
|
||||
+ w_retweets × retweets
|
||||
+ w_replies × replies
|
||||
+ w_bookmarks × bookmarks
|
||||
+ w_views_log × log10(views)
|
||||
"""
|
||||
if weights is None:
|
||||
weights = DEFAULT_WEIGHTS
|
||||
m = tweet.metrics
|
||||
return (
|
||||
weights.get("likes", 1.0) * m.likes
|
||||
+ weights.get("retweets", 3.0) * m.retweets
|
||||
+ weights.get("replies", 2.0) * m.replies
|
||||
+ weights.get("bookmarks", 5.0) * m.bookmarks
|
||||
+ weights.get("views_log", 0.5) * math.log10(max(m.views, 1))
|
||||
)
|
||||
|
||||
|
||||
def filter_tweets(tweets, config):
|
||||
# type: (List[Tweet], dict) -> List[Tweet]
|
||||
"""Filter and rank tweets according to config.
|
||||
|
||||
Config keys:
|
||||
mode: "topN" | "score" | "all"
|
||||
topN: int
|
||||
minScore: float
|
||||
lang: list[str] (empty = no filter)
|
||||
excludeRetweets: bool
|
||||
weights: dict
|
||||
"""
|
||||
filtered = list(tweets)
|
||||
|
||||
# 1. Language filter
|
||||
lang_filter = config.get("lang", [])
|
||||
if lang_filter:
|
||||
filtered = [t for t in filtered if t.lang in lang_filter]
|
||||
|
||||
# 2. Exclude retweets
|
||||
if config.get("excludeRetweets", False):
|
||||
filtered = [t for t in filtered if not t.is_retweet]
|
||||
|
||||
# 3. Score all tweets
|
||||
weights = config.get("weights", DEFAULT_WEIGHTS)
|
||||
for t in filtered:
|
||||
t.score = round(score_tweet(t, weights), 1)
|
||||
|
||||
# 4. Sort by score (descending)
|
||||
filtered.sort(key=lambda t: t.score, reverse=True)
|
||||
|
||||
# 5. Apply filter mode
|
||||
mode = config.get("mode", "topN")
|
||||
if mode == "topN":
|
||||
top_n = config.get("topN", 20)
|
||||
return filtered[:top_n]
|
||||
elif mode == "score":
|
||||
min_score = config.get("minScore", 50)
|
||||
return [t for t in filtered if t.score >= min_score]
|
||||
else:
|
||||
return filtered
|
||||
Reference in New Issue
Block a user