07c5f9a5c2
- New oracle/ package (11 modules) with unified CLI (python -m oracle) - Source tiers: Tier 1 (arxiv/github/hf), Tier 2 (rss/hn), Tier 3 (reddit) - Composite verdicts: PUBLISH/WATCH/ARCHIVE/DROP based on signal score + age - Content-hash dedup: SHA-256[:16] normalized, atomic at insert time - Multi-variant editions: 4 YAML configs (default/research/devops/brief) - Variant engine: filter → rank → render (HTML + JSON, themed) - Per-adapter timeout (10s) + threading fallback - Consolidated 12 root scripts → thin wrappers + oracle/ package - Archived stale scripts (_engagement, _live_compare, reddit_proof) - Updated .gitignore, README.md, schema.sql
333 lines
14 KiB
Python
333 lines
14 KiB
Python
"""Athena scoring engine — Sprint 1: Pure Rule-Based Bucket Classifier + Scorer.
|
|
|
|
DESIGN CONSTRAINT (founder directive, 2026-07-15):
|
|
Pure rules only. No embeddings, no semantic similarity, no LLM classification.
|
|
|
|
Pipeline position:
|
|
ingestion/dedup -> [attach_scoring] -> rendering
|
|
"""
|
|
import json
|
|
import re
|
|
import sqlite3
|
|
from typing import Optional
|
|
|
|
from oracle.config import DB_PATH
|
|
|
|
# ── Score weights ──────────────────────────────────────────────────────────
|
|
WEIGHTS = {
|
|
"shipping": 0.20,
|
|
"utility": 0.20,
|
|
"replication": 0.25,
|
|
"enthusiast": 0.20,
|
|
"novelty": 0.15,
|
|
}
|
|
HYPE_CAP = 0.45
|
|
|
|
# ── Keyword sets ───────────────────────────────────────────────────────────
|
|
KW_SHIPPING = [
|
|
"released", "launch", "v1.0", "v2.0", "v3.0", "shipping", "now available",
|
|
"open source", "open-source", "open weights", "weights released", "live now",
|
|
"beta", "public beta", "ga release", "general availability", "ships", "deployed",
|
|
"production", "now in", "available today", "download", "gradio", "demo", "playground",
|
|
]
|
|
KW_LOCAL_AI = [
|
|
"local llm", "local model", "local ai", "run locally", "run it locally", "on-device",
|
|
"on device", "ollama", "llama.cpp", "llamacpp", "gguf", "ggml", "lm studio",
|
|
"consumer hardware", "consumer gpu", "rtx", "your own gpu", "offline", "private ai",
|
|
"local-only", "self-host", "self-hosted", "home server", "edge device", "edge inference",
|
|
"quantized", "quantization", "q4", "q8", "int4", "fp16", "fine-tune at home",
|
|
"train at home", "local inference", "local deployment", "no api", "no cloud",
|
|
]
|
|
KW_PROBLEM_SOLVED = [
|
|
"how to", "how i", "solved", "fix", "fixed", "workaround", "benchmark", "improves",
|
|
"improved", "speedup", "speed-up", "reduces", "reduce", "cut", "cuts", "boost",
|
|
"optimize", "optimized", "optimisation", "faster", "3x", "10x", "2x", "latency",
|
|
"throughput", "roi", "cost", "cheaper", "save", "saves", "eliminate", "eliminated",
|
|
"from 117s to 30s", "p95", "memory usage", "vram", "token cost", "bottleneck",
|
|
"case study", "results", "we measured", "we tested", "showdown", "comparison",
|
|
]
|
|
KW_MODEL_RELEASE = [
|
|
"releases", "released", "unveils", "introduces", "new model", "new flagship",
|
|
"gpt-", "claude", "gemini", "llama", "mistral", "qwen", "deepseek", "grok",
|
|
"phi-", "command-r", "api access", "weights", "open model", "open-models",
|
|
"frontier", "checkpoint", "fine-tune", "finetune", "rl-trained", "rl train",
|
|
"trained", "post-training", "post training", "distilled", "distillation",
|
|
]
|
|
KW_RESEARCH = [
|
|
"paper", "arxiv", "preprint", "study", "research", "we propose", "we present",
|
|
"we introduce", "we show", "method", "framework", "theorem", "analysis of",
|
|
"survey", "benchmark", "dataset", "neural", "transformer", "diffusion",
|
|
"gradient", "ablation", "we find", "our approach", "novel", "state-of-the-art",
|
|
"sota", "cs.lg", "cs.cl", "cs.cv", "cs.ai",
|
|
]
|
|
KW_BUSINESS = [
|
|
"raises", "raised", "$", "valuation", "series a", "series b", "funding", "round",
|
|
"ipo", "acquisition", "acquires", "merger", "deal", "revenue", "layoff", "hiring",
|
|
"partnership", "invests", "investment", "market", "vc", "compute deal",
|
|
"billion", "million", "forecast", "miss", "earnings", "stock",
|
|
]
|
|
KW_INFRA = [
|
|
"gpu", "tpu", "data center", "datacenter", "data centre", "cluster", "cuda",
|
|
"rocm", "vllm", "tensorrt", "inference server", "serving", "kubernetes", "docker",
|
|
"pipeline", "mlops", "ci/cd", "rag", "vector db", "vector database", "agent",
|
|
"agents", "orchestration", "observability", "evaluation", "eval", "guardrail",
|
|
"safety", "red team", "jailbreak", "prompt injection", "fine-tuning stack",
|
|
]
|
|
KW_CULTURE = [
|
|
"says", "argues", "opinion", "essay", "think", "thinks", "the real", "why we",
|
|
"the future of", "dystopia", "utopia", "philosophy", "ethics", "regulation",
|
|
"policy", "ban", "lawsuit", "eu", "senate", "congress", "interview", "podcast",
|
|
"controversy", "controversial", "debate", "critic", "criticism",
|
|
"creepy", "creeping", "not sexy", "vibe", "hot take", "unpopular",
|
|
]
|
|
|
|
BUCKETS = {
|
|
"SHIPPING": {"kw": KW_SHIPPING, "source_whitelist": None, "order": 0},
|
|
"LOCAL AI": {"kw": KW_LOCAL_AI, "source_whitelist": None, "order": 1},
|
|
"PROBLEM SOLVED": {"kw": KW_PROBLEM_SOLVED, "source_whitelist": None, "order": 2},
|
|
"MODEL RELEASE": {"kw": KW_MODEL_RELEASE, "source_whitelist": None, "order": 3},
|
|
"RESEARCH": {"kw": KW_RESEARCH, "source_whitelist": ["arxiv"], "order": 4},
|
|
"BUSINESS": {"kw": KW_BUSINESS, "source_whitelist": None, "order": 5},
|
|
"INFRASTRUCTURE": {"kw": KW_INFRA, "source_whitelist": None, "order": 6},
|
|
"CULTURE": {"kw": KW_CULTURE, "source_whitelist": None, "order": 7},
|
|
}
|
|
BUCKET_ORDER = sorted(BUCKETS.keys(), key=lambda b: BUCKETS[b]["order"])
|
|
|
|
HYPE_TERMS = [
|
|
"revolutionary", "game-changing", "game changer", "breakthrough", "mind-blowing",
|
|
"insane", "crazy", "unbelievable", "shocking", "the future is here", "omg",
|
|
"you won't believe", "secret", "they don't want you to know", "leaked", "viral",
|
|
"hype", "buzzword", "disrupt", "disrupting everything", "ai will replace",
|
|
"will change everything", "paradigm shift", "godlike", "magic", "miracle",
|
|
]
|
|
|
|
ENTHUSIAST_SIGNALS = [
|
|
"github", "repo", "repository", "self-host", "local", "ollama", "llamacpp",
|
|
"hugging face", "huggingface", "colab", "notebook", "pip install", "docker",
|
|
"cli", "open source", "open-source", "diy", "build your own", "tutorial",
|
|
"how to", "implementation", "agent", "agents", "fine-tune", "finetune",
|
|
"quantiz", "vllm", "rtx", "gpu", "consumer", "homelab", "self-hosted",
|
|
"machine-learning", "machine learning", "deep learning", "python", "rust",
|
|
"benchmark", "reproduc", "weights", "gguf",
|
|
]
|
|
SOURCE_ENTHUSIAST_BONUS = {
|
|
"github": 0.20, "huggingface": 0.20, "arxiv": 0.10,
|
|
"hackernews": 0.10, "reddit": 0.05, "rss": 0.0,
|
|
}
|
|
|
|
NEW_COLUMNS = [
|
|
"bucket TEXT",
|
|
"shipping_score REAL DEFAULT 0",
|
|
"utility_score REAL DEFAULT 0",
|
|
"replication_score REAL DEFAULT 0",
|
|
"enthusiast_score REAL DEFAULT 0",
|
|
"novelty_score REAL DEFAULT 0",
|
|
"hype_penalty REAL DEFAULT 0",
|
|
"final_score REAL DEFAULT 0",
|
|
"actionability_score REAL DEFAULT 0",
|
|
"narrative_id TEXT",
|
|
"topic_id TEXT",
|
|
"relation_json TEXT",
|
|
]
|
|
|
|
|
|
# ── Helpers ────────────────────────────────────────────────────────────────
|
|
def _norm(text):
|
|
if not text:
|
|
return ""
|
|
if isinstance(text, bytes):
|
|
text = text.decode("utf-8", "replace")
|
|
return " " + re.sub(r"\s+", " ", text.lower()) + " "
|
|
|
|
|
|
def _summary_text(raw):
|
|
if not raw:
|
|
return ""
|
|
try:
|
|
d = json.loads(raw)
|
|
if isinstance(d, dict):
|
|
return " ".join(str(v) for v in d.values() if isinstance(v, str))
|
|
except Exception:
|
|
pass
|
|
return raw
|
|
|
|
|
|
# ── Classification ─────────────────────────────────────────────────────────
|
|
def classify(entry: dict) -> tuple[str, list[str]]:
|
|
"""Pure rule classification.
|
|
|
|
Returns (bucket, matched_list) where matched_list is human-readable proof.
|
|
"""
|
|
title = _norm(entry.get("title") or "")
|
|
summary = _norm(_summary_text(entry.get("summary")))
|
|
tags_raw = entry.get("category_tags") or ""
|
|
try:
|
|
tags = " ".join(json.loads(tags_raw)) if tags_raw else ""
|
|
except Exception:
|
|
tags = tags_raw
|
|
tags = _norm(tags)
|
|
source = (entry.get("source") or "").lower()
|
|
haystack = title + " " + summary + " " + tags
|
|
|
|
matched = [f"source={source}"]
|
|
best_bucket = "UNCATEGORIZED"
|
|
best_hits = 0
|
|
|
|
for bucket in BUCKET_ORDER:
|
|
spec = BUCKETS[bucket]
|
|
whitelist = spec["source_whitelist"]
|
|
if whitelist and source not in whitelist:
|
|
continue
|
|
hits = []
|
|
for kw in spec["kw"]:
|
|
if f" {kw.lower()} " in haystack:
|
|
hits.append(kw)
|
|
if hits:
|
|
matched.extend(f"kw:{h}" for h in hits[:8])
|
|
if len(hits) > best_hits:
|
|
best_hits = len(hits)
|
|
best_bucket = bucket
|
|
|
|
if best_bucket == "UNCATEGORIZED":
|
|
matched.append("(no rule fired)")
|
|
|
|
return best_bucket, matched
|
|
|
|
|
|
# ── Scoring ────────────────────────────────────────────────────────────────
|
|
def score_entry(bucket: str, matched: list, entry: dict) -> dict:
|
|
"""Return dict of component scores (0..1) + final (0..1)."""
|
|
source = (entry.get("source") or "").lower()
|
|
haystack = _norm(entry.get("title") or "") + " " + _norm(_summary_text(entry.get("summary")))
|
|
|
|
tags_raw = entry.get("category_tags") or ""
|
|
try:
|
|
tags = " ".join(json.loads(tags_raw)) if tags_raw else ""
|
|
except Exception:
|
|
tags = tags_raw
|
|
haystack += _norm(tags)
|
|
|
|
# Enthusiast score
|
|
ent_hits = sum(1 for s in ENTHUSIAST_SIGNALS if f" {s} " in haystack)
|
|
enthusiast = min(ent_hits / 5.0 + SOURCE_ENTHUSIAST_BONUS.get(source, 0.0), 1.0)
|
|
|
|
# Shipping score
|
|
ship_kw = [k for k in KW_SHIPPING if f" {k} " in haystack]
|
|
shipping = 0.0
|
|
if bucket == "SHIPPING":
|
|
shipping = 0.9
|
|
elif ship_kw:
|
|
shipping = min(0.4 + 0.1 * len(ship_kw), 0.8)
|
|
if source in ("github", "huggingface"):
|
|
shipping = max(shipping, 0.7)
|
|
|
|
# Utility score
|
|
util_kw = [k for k in KW_PROBLEM_SOLVED if f" {k} " in haystack]
|
|
utility = 0.0
|
|
if bucket == "PROBLEM SOLVED":
|
|
utility = 0.85
|
|
elif util_kw:
|
|
utility = min(0.4 + 0.1 * len(util_kw), 0.8)
|
|
if any(s in haystack for s in [" github ", " huggingface ", " demo "]):
|
|
utility = max(utility, 0.6)
|
|
|
|
# Replication score
|
|
repl_kw = [k for k in KW_LOCAL_AI if f" {k} " in haystack]
|
|
replication = 0.0
|
|
if bucket == "LOCAL AI":
|
|
replication = 1.0
|
|
elif repl_kw:
|
|
replication = min(0.5 + 0.1 * len(repl_kw), 0.9)
|
|
if source in ("github", "huggingface"):
|
|
replication = max(replication, 0.7)
|
|
if any(s in haystack for s in [" open source ", " open-source ", " weights "]):
|
|
replication = max(replication, 0.6)
|
|
|
|
# Novelty score
|
|
novelty = 0.0
|
|
if bucket in ("RESEARCH", "MODEL RELEASE"):
|
|
novelty = 0.6
|
|
nov_kw = ["new", "novel", "first", "breakthrough-method", "we propose",
|
|
"we introduce", "we present", "state-of-the-art", "sota", "unveils"]
|
|
if any(f" {k} " in haystack for k in nov_kw):
|
|
novelty = min(novelty + 0.2, 0.9)
|
|
if bucket == "CULTURE":
|
|
novelty = min(novelty, 0.3)
|
|
|
|
# Hype penalty
|
|
hype_penalty = min(0.1 * sum(1 for t in HYPE_TERMS if f" {t} " in haystack), 0.6)
|
|
|
|
# Final score
|
|
raw = (
|
|
WEIGHTS["shipping"] * shipping
|
|
+ WEIGHTS["utility"] * utility
|
|
+ WEIGHTS["replication"] * replication
|
|
+ WEIGHTS["enthusiast"] * enthusiast
|
|
+ WEIGHTS["novelty"] * novelty
|
|
)
|
|
final = min(max(raw - hype_penalty, 0.0), 1.0)
|
|
|
|
return {
|
|
"shipping_score": round(shipping, 3),
|
|
"utility_score": round(utility, 3),
|
|
"replication_score": round(replication, 3),
|
|
"enthusiast_score": round(enthusiast, 3),
|
|
"novelty_score": round(novelty, 3),
|
|
"hype_penalty": round(hype_penalty, 3),
|
|
"final_score": round(final, 4),
|
|
}
|
|
|
|
|
|
# ── DB Operations ──────────────────────────────────────────────────────────
|
|
def migrate(db_path: Optional[str] = None) -> list[str]:
|
|
"""Idempotent schema migration — only adds missing columns."""
|
|
conn = sqlite3.connect(db_path or str(DB_PATH))
|
|
cur = conn.cursor()
|
|
cur.execute("PRAGMA table_info(entries)")
|
|
existing = {row[1] for row in cur.fetchall()}
|
|
added = []
|
|
for col in NEW_COLUMNS:
|
|
name = col.split(" ")[0]
|
|
if name not in existing:
|
|
cur.execute(f"ALTER TABLE entries ADD COLUMN {col}")
|
|
added.append(name)
|
|
conn.commit()
|
|
conn.close()
|
|
print(f"[migrate] added columns: {', '.join(added) if added else 'none (already present)'}")
|
|
return added
|
|
|
|
|
|
def fetch_unscored(conn: sqlite3.Connection) -> list[dict]:
|
|
cur = conn.cursor()
|
|
cur.execute("""
|
|
SELECT id, source, source_id, url, title, summary, category_tags, raw_metadata
|
|
FROM entries WHERE bucket IS NULL OR bucket = ''
|
|
""")
|
|
cols = ["id", "source", "source_id", "url", "title", "summary", "category_tags", "raw_metadata"]
|
|
return [dict(zip(cols, row)) for row in cur.fetchall()]
|
|
|
|
|
|
def attach_scoring(db_path: Optional[str] = None, dry_run: bool = False) -> None:
|
|
"""Score every unscored entry. Call after ingestion/dedup, before render."""
|
|
conn = sqlite3.connect(db_path or str(DB_PATH))
|
|
rows = fetch_unscored(conn)
|
|
print(f"[attach] scoring {len(rows)} unscored entries")
|
|
for e in rows:
|
|
bucket, matched = classify(e)
|
|
scores = score_entry(bucket, matched, e)
|
|
if not dry_run:
|
|
conn.execute(
|
|
"""UPDATE entries SET bucket=?, shipping_score=?, utility_score=?,
|
|
replication_score=?, enthusiast_score=?, novelty_score=?,
|
|
hype_penalty=?, final_score=?, actionability_score=?,
|
|
narrative_id=?, topic_id=?, relation_json=? WHERE id=?""",
|
|
(bucket, scores["shipping_score"], scores["utility_score"],
|
|
scores["replication_score"], scores["enthusiast_score"],
|
|
scores["novelty_score"], scores["hype_penalty"], scores["final_score"],
|
|
0.0, None, None, json.dumps({"matched_rules": matched}), e["id"]),
|
|
)
|
|
if not dry_run:
|
|
conn.commit()
|
|
conn.close()
|
|
print("[attach] done.")
|