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
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
"""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.")
|
||||
Reference in New Issue
Block a user