Spaces:
Running
Running
Commit ·
ee82dfb
1
Parent(s): cb8a178
Add sponsor recognition for all 42 news sources
Browse files- refresh_task.py +2 -0
- src/analyzer.py +57 -0
- src/models.py +1 -0
- templates/index.html +6 -4
- webapp.py +2 -0
refresh_task.py
CHANGED
|
@@ -55,6 +55,7 @@ def serialize_clusters(clusters):
|
|
| 55 |
"summary": a.analysis.summary if a.analysis else "",
|
| 56 |
"topics": a.analysis.topics if a.analysis else [],
|
| 57 |
"trust": f"{a.analysis.trustworthiness_score:.0%}" if a.analysis else "",
|
|
|
|
| 58 |
"score": a.post.score,
|
| 59 |
"comments": a.post.num_comments,
|
| 60 |
"url": a.post.url,
|
|
@@ -84,6 +85,7 @@ def serialize_clusters(clusters):
|
|
| 84 |
"summary": a.analysis.summary if a.analysis else "",
|
| 85 |
"topics": a.analysis.topics if a.analysis else [],
|
| 86 |
"trust": round(a.analysis.trustworthiness_score, 2) if a.analysis else 0,
|
|
|
|
| 87 |
"score": a.post.score,
|
| 88 |
"comments": a.post.num_comments,
|
| 89 |
"image": a.article.image_url if a.article else a.post.image_url,
|
|
|
|
| 55 |
"summary": a.analysis.summary if a.analysis else "",
|
| 56 |
"topics": a.analysis.topics if a.analysis else [],
|
| 57 |
"trust": f"{a.analysis.trustworthiness_score:.0%}" if a.analysis else "",
|
| 58 |
+
"sponsor": a.analysis.sponsor if a.analysis else "",
|
| 59 |
"score": a.post.score,
|
| 60 |
"comments": a.post.num_comments,
|
| 61 |
"url": a.post.url,
|
|
|
|
| 85 |
"summary": a.analysis.summary if a.analysis else "",
|
| 86 |
"topics": a.analysis.topics if a.analysis else [],
|
| 87 |
"trust": round(a.analysis.trustworthiness_score, 2) if a.analysis else 0,
|
| 88 |
+
"sponsor": a.analysis.sponsor if a.analysis else "",
|
| 89 |
"score": a.post.score,
|
| 90 |
"comments": a.post.num_comments,
|
| 91 |
"image": a.article.image_url if a.article else a.post.image_url,
|
src/analyzer.py
CHANGED
|
@@ -20,6 +20,56 @@ UNRELIABLE_DOMAINS = {
|
|
| 20 |
"theonion.com": -0.25, "naturalnews.com": -0.30, "zerohedge.com": -0.15,
|
| 21 |
}
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
CLICKBAIT_PATTERNS = [
|
| 24 |
r"you won'?t believe", r"shocked?", r"gobsmacked",
|
| 25 |
r"this is what happens", r"number \d+ will",
|
|
@@ -199,6 +249,11 @@ class NewsAnalyzer:
|
|
| 199 |
except Exception as exc:
|
| 200 |
logger.warning("Model loading failed: %s — using rule-based", exc)
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
def analyze(self, article: Article) -> Analysis:
|
| 203 |
summary = self._summarise(article)
|
| 204 |
topics = self._classify_topics(article, summary)
|
|
@@ -207,6 +262,7 @@ class NewsAnalyzer:
|
|
| 207 |
leaning = self._detect_political_leaning(article.text or "")
|
| 208 |
|
| 209 |
category = self._map_category(topics)
|
|
|
|
| 210 |
|
| 211 |
return Analysis(
|
| 212 |
summary=summary,
|
|
@@ -215,6 +271,7 @@ class NewsAnalyzer:
|
|
| 215 |
is_opinion=is_opinion,
|
| 216 |
political_leaning=leaning,
|
| 217 |
category=category,
|
|
|
|
| 218 |
)
|
| 219 |
|
| 220 |
def _summarise(self, article: Article) -> str:
|
|
|
|
| 20 |
"theonion.com": -0.25, "naturalnews.com": -0.30, "zerohedge.com": -0.15,
|
| 21 |
}
|
| 22 |
|
| 23 |
+
SPONSORS: dict[str, str] = {
|
| 24 |
+
"bbc.com": "BBC (publicly funded)",
|
| 25 |
+
"bbc.co.uk": "BBC (publicly funded)",
|
| 26 |
+
"nytimes.com": "The New York Times Company",
|
| 27 |
+
"npr.org": "NPR (publicly funded)",
|
| 28 |
+
"aljazeera.com": "Al Jazeera Media Network (Qatar-funded)",
|
| 29 |
+
"theguardian.com": "Guardian Media Group",
|
| 30 |
+
"statnews.com": "Boston Globe Media",
|
| 31 |
+
"sciencedaily.com": "ScienceDaily LLC",
|
| 32 |
+
"techcrunch.com": "Yahoo (Apollo Global Management)",
|
| 33 |
+
"wired.com": "Condé Nast (Advance Publications)",
|
| 34 |
+
"theverge.com": "Vox Media",
|
| 35 |
+
"arstechnica.com": "Condé Nast (Advance Publications)",
|
| 36 |
+
"krebsonsecurity.com": "Independent (Brian Krebs)",
|
| 37 |
+
"bleepingcomputer.com": "Independent",
|
| 38 |
+
"threatpost.com": "Independent",
|
| 39 |
+
"therecord.media": "Recorded Future",
|
| 40 |
+
"theonion.com": "Global Tetrahedron",
|
| 41 |
+
"thedailymash.co.uk": "Independent",
|
| 42 |
+
"babylonbee.com": "Independent",
|
| 43 |
+
"ign.com": "Ziff Davis",
|
| 44 |
+
"eurogamer.net": "ReedPop (Gamer Network)",
|
| 45 |
+
"pcgamer.com": "Future plc",
|
| 46 |
+
"kotaku.com": "G/O Media",
|
| 47 |
+
"gamespot.com": "Fandom",
|
| 48 |
+
"polygon.com": "Vox Media",
|
| 49 |
+
"variety.com": "Penske Media Corporation",
|
| 50 |
+
"hollywoodreporter.com": "Penske Media Corporation",
|
| 51 |
+
"deadline.com": "Penske Media Corporation",
|
| 52 |
+
"screenrant.com": "Valnet Inc.",
|
| 53 |
+
"arabnews.com": "Saudi Research & Publishing",
|
| 54 |
+
"middleeasteye.net": "Independent (London-based)",
|
| 55 |
+
"newarab.com": "Fikra Publishing",
|
| 56 |
+
"france24.com": "France Médias Monde (government-funded)",
|
| 57 |
+
"tunisiaonlinenews.com": "Independent",
|
| 58 |
+
"northafricapost.com": "Independent",
|
| 59 |
+
"africanews.com": "Independent",
|
| 60 |
+
"reuters.com": "Thomson Reuters",
|
| 61 |
+
"apnews.com": "Associated Press (non-profit)",
|
| 62 |
+
"wsj.com": "Dow Jones (News Corp)",
|
| 63 |
+
"economist.com": "The Economist Group",
|
| 64 |
+
"nature.com": "Springer Nature",
|
| 65 |
+
"science.org": "American Association for the Advancement of Science",
|
| 66 |
+
"washingtonpost.com": "The Washington Post (Jeff Bezos)",
|
| 67 |
+
"ft.com": "Financial Times (Nikkei)",
|
| 68 |
+
"bloomberg.com": "Bloomberg L.P. (Michael Bloomberg)",
|
| 69 |
+
"thehackernews.com": "Independent (THN)",
|
| 70 |
+
"ign.com": "Ziff Davis",
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
CLICKBAIT_PATTERNS = [
|
| 74 |
r"you won'?t believe", r"shocked?", r"gobsmacked",
|
| 75 |
r"this is what happens", r"number \d+ will",
|
|
|
|
| 249 |
except Exception as exc:
|
| 250 |
logger.warning("Model loading failed: %s — using rule-based", exc)
|
| 251 |
|
| 252 |
+
@staticmethod
|
| 253 |
+
def _detect_sponsor(article: Article) -> str:
|
| 254 |
+
domain = re.sub(r"^www\.", "", (article.source_domain or ""))
|
| 255 |
+
return SPONSORS.get(domain, "")
|
| 256 |
+
|
| 257 |
def analyze(self, article: Article) -> Analysis:
|
| 258 |
summary = self._summarise(article)
|
| 259 |
topics = self._classify_topics(article, summary)
|
|
|
|
| 262 |
leaning = self._detect_political_leaning(article.text or "")
|
| 263 |
|
| 264 |
category = self._map_category(topics)
|
| 265 |
+
sponsor = self._detect_sponsor(article)
|
| 266 |
|
| 267 |
return Analysis(
|
| 268 |
summary=summary,
|
|
|
|
| 271 |
is_opinion=is_opinion,
|
| 272 |
political_leaning=leaning,
|
| 273 |
category=category,
|
| 274 |
+
sponsor=sponsor,
|
| 275 |
)
|
| 276 |
|
| 277 |
def _summarise(self, article: Article) -> str:
|
src/models.py
CHANGED
|
@@ -34,6 +34,7 @@ class Analysis:
|
|
| 34 |
is_opinion: bool
|
| 35 |
political_leaning: str = "centrist"
|
| 36 |
category: str = "General"
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
@dataclass
|
|
|
|
| 34 |
is_opinion: bool
|
| 35 |
political_leaning: str = "centrist"
|
| 36 |
category: str = "General"
|
| 37 |
+
sponsor: str = ""
|
| 38 |
|
| 39 |
|
| 40 |
@dataclass
|
templates/index.html
CHANGED
|
@@ -131,6 +131,7 @@ body {
|
|
| 131 |
}
|
| 132 |
.card-meta { display: flex; align-items: center; gap: 10px; font-size: 12px; color: #64748b; margin-bottom: 10px; flex-wrap: wrap; }
|
| 133 |
.card-meta .source { color: #94a3b8; }
|
|
|
|
| 134 |
.score-bar-wrap { height: 3px; background: #334155; border-radius: 3px; margin-bottom: 3px; }
|
| 135 |
.score-bar-fill { height: 100%; border-radius: 3px; background: linear-gradient(90deg, #60a5fa, #a78bfa); transition: width .5s; }
|
| 136 |
.score-label { font-size: 10px; color: #64748b; display: flex; justify-content: space-between; }
|
|
@@ -243,10 +244,11 @@ body {
|
|
| 243 |
{% endfor %}
|
| 244 |
</div>
|
| 245 |
<div class="card-meta">
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
|
|
|
| 250 |
<div class="score-bar-wrap">
|
| 251 |
<div class="score-bar-fill" style="width:{{ c.score * 100 }}%"></div>
|
| 252 |
</div>
|
|
|
|
| 131 |
}
|
| 132 |
.card-meta { display: flex; align-items: center; gap: 10px; font-size: 12px; color: #64748b; margin-bottom: 10px; flex-wrap: wrap; }
|
| 133 |
.card-meta .source { color: #94a3b8; }
|
| 134 |
+
.sponsor { font-size: 10px; color: #fbbf24; background: rgba(251, 191, 36, 0.1); padding: 2px 6px; border-radius: 4px; }
|
| 135 |
.score-bar-wrap { height: 3px; background: #334155; border-radius: 3px; margin-bottom: 3px; }
|
| 136 |
.score-bar-fill { height: 100%; border-radius: 3px; background: linear-gradient(90deg, #60a5fa, #a78bfa); transition: width .5s; }
|
| 137 |
.score-label { font-size: 10px; color: #64748b; display: flex; justify-content: space-between; }
|
|
|
|
| 244 |
{% endfor %}
|
| 245 |
</div>
|
| 246 |
<div class="card-meta">
|
| 247 |
+
<span class="source">{{ c.articles[0].domain }}</span>
|
| 248 |
+
<span>🛡️ {{ c.trust }}</span>
|
| 249 |
+
{% if c.coverage > 1 %}<span>📰 {{ c.coverage }}</span>{% endif %}
|
| 250 |
+
{% if c.articles[0].sponsor %}<span class="sponsor">🏢 {{ c.articles[0].sponsor }}</span>{% endif %}
|
| 251 |
+
</div>
|
| 252 |
<div class="score-bar-wrap">
|
| 253 |
<div class="score-bar-fill" style="width:{{ c.score * 100 }}%"></div>
|
| 254 |
</div>
|
webapp.py
CHANGED
|
@@ -55,6 +55,7 @@ def _cluster_to_html_dict(c):
|
|
| 55 |
"summary": a.analysis.summary if a.analysis else "",
|
| 56 |
"topics": a.analysis.topics if a.analysis else [],
|
| 57 |
"trust": f"{a.analysis.trustworthiness_score:.0%}" if a.analysis else "",
|
|
|
|
| 58 |
"score": a.post.score,
|
| 59 |
"comments": a.post.num_comments,
|
| 60 |
"url": a.post.url,
|
|
@@ -89,6 +90,7 @@ def _cluster_to_api_dict(c):
|
|
| 89 |
"summary": a.analysis.summary if a.analysis else "",
|
| 90 |
"topics": a.analysis.topics if a.analysis else [],
|
| 91 |
"trust": round(a.analysis.trustworthiness_score, 2) if a.analysis else 0,
|
|
|
|
| 92 |
"score": a.post.score,
|
| 93 |
"comments": a.post.num_comments,
|
| 94 |
"image": a.article.image_url if a.article else a.post.image_url,
|
|
|
|
| 55 |
"summary": a.analysis.summary if a.analysis else "",
|
| 56 |
"topics": a.analysis.topics if a.analysis else [],
|
| 57 |
"trust": f"{a.analysis.trustworthiness_score:.0%}" if a.analysis else "",
|
| 58 |
+
"sponsor": a.analysis.sponsor if a.analysis else "",
|
| 59 |
"score": a.post.score,
|
| 60 |
"comments": a.post.num_comments,
|
| 61 |
"url": a.post.url,
|
|
|
|
| 90 |
"summary": a.analysis.summary if a.analysis else "",
|
| 91 |
"topics": a.analysis.topics if a.analysis else [],
|
| 92 |
"trust": round(a.analysis.trustworthiness_score, 2) if a.analysis else 0,
|
| 93 |
+
"sponsor": a.analysis.sponsor if a.analysis else "",
|
| 94 |
"score": a.post.score,
|
| 95 |
"comments": a.post.num_comments,
|
| 96 |
"image": a.article.image_url if a.article else a.post.image_url,
|