Add Sport/Canada/US/AI/Cloud/HomeLab pills; drop Finance
- New Sport pill: detect sports by clear headline terms or majority sports-outlet sources, and exclude sports (and F1) from All/Tech/regions so World Cup/soccer no longer pollutes Finance/Tech. - New client keyword pills: Canada, US, AI, Cloud, HomeLab (headline-matched). - Remove the Finance pill; keep Tech (backend topic). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -190,6 +190,7 @@ struct Interest: Identifiable, Hashable {
|
||||
var topicSlug: String? = nil
|
||||
func matches(_ s: StorySummary) -> Bool {
|
||||
if id == "f1" { return Interest.isFormula1(s) }
|
||||
if id == "sport" { return Interest.isSports(s) }
|
||||
if let topicSlug {
|
||||
return s.topic.caseInsensitiveCompare(topicSlug) == .orderedSame
|
||||
}
|
||||
@@ -217,6 +218,26 @@ struct Interest: Identifiable, Hashable {
|
||||
return Double(n) / Double(srcs.count) >= 0.5
|
||||
}
|
||||
|
||||
/// Sport (non-F1) = a clear sports headline, or a majority of sports-outlet
|
||||
/// sources. The backend mis-tags World Cup / soccer into finance & tech, so
|
||||
/// this pulls them out. F1 has its own pill and is excluded here.
|
||||
static func isSports(_ s: StorySummary) -> Bool {
|
||||
if isFormula1(s) { return false }
|
||||
let h = s.headline.lowercased()
|
||||
let terms = ["world cup", "fifa", "premier league", "la liga", "champions league",
|
||||
"uefa", "ballon d'or", "knockout stage", "quarterfinal", "semifinal",
|
||||
"nba ", "nfl ", "nhl ", "mlb ", "super bowl", "playoff", "wimbledon",
|
||||
"grand slam", "olympic", "test match", "six nations", "messi", "ronaldo",
|
||||
"striker", "midfielder", "goalkeeper", "world cup match"]
|
||||
if terms.contains(where: { h.contains($0) }) { return true }
|
||||
let feeds = ["the athletic", "fox sports", "sportsnet", "espn", "sky sports",
|
||||
"bbc sport", "goal", "bleacher report", "sports illustrated", "sporting news"]
|
||||
let srcs = s.sources
|
||||
guard !srcs.isEmpty else { return false }
|
||||
let n = srcs.filter { src in feeds.contains { src.name.lowercased().contains($0) } }.count
|
||||
return Double(n) / Double(srcs.count) >= 0.5
|
||||
}
|
||||
|
||||
// Region keywords are place/person names found in the story text — never
|
||||
// outlet names (which are unreliable due to backend over-clustering).
|
||||
private static let ugandaKeywords = ["uganda", "ugandan", "kampala", "museveni",
|
||||
@@ -225,15 +246,41 @@ struct Interest: Identifiable, Hashable {
|
||||
"cape town", "pretoria", "durban", "ramaphosa",
|
||||
"eskom", "gauteng", "western cape", "soweto",
|
||||
"drakensberg", "mbalula", "zuma"]
|
||||
private static let canadaKeywords = ["canada", "canadian", "toronto", "ottawa", "vancouver",
|
||||
"montreal", "quebec", "alberta", "ontario", "manitoba",
|
||||
"calgary", "winnipeg", "trudeau", "carney"]
|
||||
private static let usKeywords = ["united states", "u.s.", "americans", "trump", "white house",
|
||||
"washington", "congress", "pentagon", "biden", "kamala",
|
||||
"harris", "wall street", "new york", "california", "texas",
|
||||
"florida", "supreme court", "senate", "capitol"]
|
||||
// Content topics — strong, unambiguous terms (no bare "ai"/"aws" substring traps).
|
||||
private static let aiKeywords = ["artificial intelligence", "machine learning", "deep learning",
|
||||
"generative ai", "chatgpt", "openai", "anthropic", "claude",
|
||||
"llm", "gpt", "gemini", "hugging face", "nvidia", "copilot",
|
||||
"agentic", "midjourney", "a.i."]
|
||||
private static let cloudKeywords = ["cloud", "azure", "google cloud", "kubernetes", "docker",
|
||||
"serverless", "cloudflare", "data center", "datacenter",
|
||||
"devops", "openstack", "terraform"]
|
||||
private static let homelabKeywords = ["homelab", "home lab", "self-host", "self-hosted",
|
||||
"selfhosted", "proxmox", "raspberry pi", "truenas",
|
||||
"unraid", "synology", "jellyfin", "home server", "nas ",
|
||||
"home assistant", "docker compose", "tailscale", "k3s",
|
||||
"mini pc"]
|
||||
|
||||
static let f1 = Interest(id: "f1", label: "F1") // matching handled by isFormula1
|
||||
static let f1 = Interest(id: "f1", label: "F1") // matching via isFormula1
|
||||
static let sport = Interest(id: "sport", label: "Sport") // matching via isSports
|
||||
|
||||
/// Pinned filter pills, in order.
|
||||
static let pinned: [Interest] = [
|
||||
f1,
|
||||
sport,
|
||||
Interest(id: "uganda", label: "Uganda", keywords: ugandaKeywords),
|
||||
Interest(id: "south-africa", label: "South Africa", keywords: southAfricaKeywords),
|
||||
Interest(id: "finance", label: "Finance", topicSlug: "finance"),
|
||||
Interest(id: "canada", label: "Canada", keywords: canadaKeywords),
|
||||
Interest(id: "us", label: "US", keywords: usKeywords),
|
||||
Interest(id: "ai", label: "AI", keywords: aiKeywords),
|
||||
Interest(id: "cloud", label: "Cloud", keywords: cloudKeywords),
|
||||
Interest(id: "homelab", label: "HomeLab", keywords: homelabKeywords),
|
||||
Interest(id: "tech", label: "Tech", topicSlug: "tech"),
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user