Files
athena-oracle/oracle/scoring.py
T
Epictetus 07c5f9a5c2 Sprint 0+1: Package restructure, source tiers, verdicts, multi-variant editions
- 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
2026-07-22 13:32:15 +00:00

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.")