Add query-type-aware source tiering and scoring
Detect query type (product/concept/opinion/how_to/comparison/breaking_news/ prediction) via lightweight regex patterns and use it for: 1. Source selection: each query type has tier-1 (always run) and tier-2 (run if available) sources. Unlisted sources are opt-in only. Truth Social is always opt-in regardless of query type. 2. WebSearch penalty: varies by query type instead of flat -15pt. Concept queries get 0 penalty (web docs are authoritative), how_to gets 5pt, breaking_news gets 10pt, product/opinion get 15pt. 3. Tiebreaker ordering: source priority varies by query type. YouTube ranks first for how_to, Polymarket for prediction, HN for concept queries, X for breaking news. All changes are backward-compatible: callers that don't pass query_type get the original behavior (15pt penalty, Reddit > X > YouTube tiebreaker).
This commit is contained in:
+32
-28
@@ -4,6 +4,7 @@ import math
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from . import dates, schema
|
||||
from .query_type import QueryType, WEBSEARCH_PENALTY_BY_TYPE, TIEBREAKER_BY_TYPE
|
||||
|
||||
# Score weights for Reddit/X (has engagement)
|
||||
WEIGHT_RELEVANCE = 0.45
|
||||
@@ -642,19 +643,17 @@ def score_polymarket_items(items: List[schema.PolymarketItem]) -> List[schema.Po
|
||||
return items
|
||||
|
||||
|
||||
def score_websearch_items(items: List[schema.WebSearchItem]) -> List[schema.WebSearchItem]:
|
||||
def score_websearch_items(items: List[schema.WebSearchItem], query_type: QueryType = None) -> List[schema.WebSearchItem]:
|
||||
"""Compute scores for WebSearch items WITHOUT engagement metrics.
|
||||
|
||||
Uses reweighted formula: 55% relevance + 45% recency - 15pt source penalty.
|
||||
This ensures WebSearch items rank below comparable Reddit/X items.
|
||||
|
||||
Date confidence adjustments:
|
||||
- High confidence (URL-verified date): +10 bonus
|
||||
- Med confidence (snippet-extracted date): no change
|
||||
- Low confidence (no date signals): -20 penalty
|
||||
Uses reweighted formula: 55% relevance + 45% recency - penalty.
|
||||
Penalty varies by query type: concept queries get 0 penalty (web docs
|
||||
are authoritative), while product/opinion queries get full 15pt penalty
|
||||
(social discussion is more valuable).
|
||||
|
||||
Args:
|
||||
items: List of WebSearch items
|
||||
query_type: Query classification for penalty adjustment
|
||||
|
||||
Returns:
|
||||
Items with updated scores
|
||||
@@ -682,8 +681,9 @@ def score_websearch_items(items: List[schema.WebSearchItem]) -> List[schema.WebS
|
||||
WEBSEARCH_WEIGHT_RECENCY * rec_score
|
||||
)
|
||||
|
||||
# Apply source penalty (WebSearch < Reddit/X for same relevance/recency)
|
||||
overall -= WEBSEARCH_SOURCE_PENALTY
|
||||
# Apply source penalty (varies by query type)
|
||||
penalty = WEBSEARCH_PENALTY_BY_TYPE.get(query_type, WEBSEARCH_SOURCE_PENALTY) if query_type else WEBSEARCH_SOURCE_PENALTY
|
||||
overall -= penalty
|
||||
|
||||
# Apply date confidence adjustments
|
||||
# High confidence (URL-verified): reward with bonus
|
||||
@@ -699,15 +699,33 @@ def score_websearch_items(items: List[schema.WebSearchItem]) -> List[schema.WebS
|
||||
return items
|
||||
|
||||
|
||||
def sort_items(items: List[Union[schema.RedditItem, schema.XItem, schema.WebSearchItem, schema.YouTubeItem, schema.TikTokItem, schema.InstagramItem, schema.HackerNewsItem, schema.PolymarketItem]]) -> List:
|
||||
_ITEM_SOURCE_MAP = {
|
||||
schema.RedditItem: "reddit",
|
||||
schema.XItem: "x",
|
||||
schema.YouTubeItem: "youtube",
|
||||
schema.TikTokItem: "tiktok",
|
||||
schema.InstagramItem: "instagram",
|
||||
schema.HackerNewsItem: "hn",
|
||||
schema.PolymarketItem: "polymarket",
|
||||
}
|
||||
_DEFAULT_TIEBREAKER = {"reddit": 0, "x": 1, "youtube": 2, "tiktok": 3, "instagram": 4, "hn": 5, "polymarket": 6, "web": 7}
|
||||
|
||||
|
||||
def sort_items(items: List[Union[schema.RedditItem, schema.XItem, schema.WebSearchItem, schema.YouTubeItem, schema.TikTokItem, schema.InstagramItem, schema.HackerNewsItem, schema.PolymarketItem]], query_type: QueryType = None) -> List:
|
||||
"""Sort items by score (descending), then date, then source priority.
|
||||
|
||||
Source priority varies by query type: YouTube ranks first for how_to,
|
||||
X ranks first for breaking_news, Polymarket ranks first for prediction.
|
||||
|
||||
Args:
|
||||
items: List of items to sort
|
||||
query_type: Query classification for tiebreaker adjustment
|
||||
|
||||
Returns:
|
||||
Sorted items
|
||||
"""
|
||||
tiebreaker = TIEBREAKER_BY_TYPE.get(query_type, _DEFAULT_TIEBREAKER) if query_type else _DEFAULT_TIEBREAKER
|
||||
|
||||
def sort_key(item):
|
||||
# Primary: score descending (negate for descending)
|
||||
score = -item.score
|
||||
@@ -716,23 +734,9 @@ def sort_items(items: List[Union[schema.RedditItem, schema.XItem, schema.WebSear
|
||||
date = item.date or "0000-00-00"
|
||||
date_key = -int(date.replace("-", ""))
|
||||
|
||||
# Tertiary: source priority (Reddit > X > YouTube > TikTok > HN > Polymarket > WebSearch)
|
||||
if isinstance(item, schema.RedditItem):
|
||||
source_priority = 0
|
||||
elif isinstance(item, schema.XItem):
|
||||
source_priority = 1
|
||||
elif isinstance(item, schema.YouTubeItem):
|
||||
source_priority = 2
|
||||
elif isinstance(item, schema.TikTokItem):
|
||||
source_priority = 3
|
||||
elif isinstance(item, schema.InstagramItem):
|
||||
source_priority = 4
|
||||
elif isinstance(item, schema.HackerNewsItem):
|
||||
source_priority = 5
|
||||
elif isinstance(item, schema.PolymarketItem):
|
||||
source_priority = 6
|
||||
else: # WebSearchItem
|
||||
source_priority = 7
|
||||
# Tertiary: query-type-aware source priority
|
||||
source_name = _ITEM_SOURCE_MAP.get(type(item), "web")
|
||||
source_priority = tiebreaker.get(source_name, 99)
|
||||
|
||||
# Quaternary: title/text for stability
|
||||
text = getattr(item, "title", "") or getattr(item, "text", "")
|
||||
|
||||
Reference in New Issue
Block a user