"""Render Athena entries into a static news site.
Two-layer layout: Top News (fresh today) + aging Stack (everything else).
Read-only against oracle.db. Designed for a 20-min cron run.
Variant-aware: render_variant_html() builds themed pages from pre-filtered items.
"""
import argparse
import datetime
import html
import json
import os
import re
import sqlite3
from collections import OrderedDict
from typing import Optional
from oracle.clickability import fetch_items, compute_index, decay_index
from oracle.config import DB_PATH, WEBROOT, FALLBACK_WEBROOT, HALF_LIFE_H, TOP_N
def _clean_summary(raw):
"""Extract the most readable field from summary JSON."""
if not raw:
return ""
try:
d = json.loads(raw)
if isinstance(d, dict):
for k in ("one_liner", "key_technical_point", "potential_use_case"):
v = d.get(k)
if isinstance(v, str) and v.strip():
return re.sub(r"\\+|_|`", "", v).strip()
except Exception:
pass
return re.sub(r"\\+|_|`", "", raw).strip()
# ── Theme palette ──────────────────────────────────────────────────────────
THEMES = {
"dark": {
"--bg": "#0b0e14",
"--card": "#141925",
"--fg": "#e6e9ef",
"--mut": "#8b93a7",
"--border": "#1f2533",
},
"light": {
"--bg": "#f8f9fa",
"--card": "#ffffff",
"--fg": "#1a1a2e",
"--mut": "#6b7280",
"--border": "#e5e7eb",
},
"midnight": {
"--bg": "#0a0a1a",
"--card": "#111128",
"--fg": "#c8d0e0",
"--mut": "#5a6480",
"--border": "#1a1a3a",
},
}
def _theme_css(display: dict) -> str:
"""Generate CSS variables for a variant display config."""
theme_name = display.get("theme", "dark")
palette = THEMES.get(theme_name, THEMES["dark"])
accent = display.get("accent", "#5b8cff")
vars_list = ", ".join(f"{k}:{v}" for k, v in palette.items())
return f":root {{ {vars_list}; --acc:{accent}; }}"
def _fmt_time(first_seen):
if not first_seen:
return ""
try:
dt = datetime.datetime.strptime(first_seen, "%Y-%m-%dT%H:%M:%SZ")
return dt.strftime("%H:%M")
except Exception:
return ""
def _card(it, big=False):
title = html.escape(it["title"] or "(untitled)")
url = html.escape(it["url"] or "#")
src = html.escape(it["source"])
t = _fmt_time(it.get("first_seen"))
summary_raw = _clean_summary(it.get("summary") or "")
summary = html.escape(summary_raw[:200])
cls = "card big" if big else "card"
summary_html = ('
{0}
'.format(summary)) if (summary and big) else ""
return f"""
{src}
{t}
sig {it.get('signal_score') or 0:.1f}
\U0001f525 {it['clickability_decayed']:.2f}
{summary_html}
"""
def build_html(items):
"""Build the full HTML page from ranked items."""
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
ranked = sorted(items, key=lambda x: x["clickability_decayed"], reverse=True)
fresh = [it for it in ranked if it.get("fresh")]
top = fresh[:TOP_N]
stack = [it for it in ranked if it not in top]
by_day = OrderedDict()
for it in stack:
day = (it.get("first_seen") or "")[:10] or "unknown"
by_day.setdefault(day, []).append(it)
top_html = "".join(_card(it, big=True) for it in top)
stack_html = ""
for day, rows in by_day.items():
rows.sort(key=lambda x: x["clickability_decayed"], reverse=True)
cards = "".join(_card(it) for it in rows)
stack_html += f"""
\U0001f4c5 {html.escape(day)}
{cards}
"""
return f"""
Athena AI News — Ranked by Clickability
\U0001f534 Top News
{top_html}
\U0001f4f0 The Stack
{stack_html}
"""
def render(dry_run=False, webroot=None):
"""Run the full render pipeline.
Returns (output_path, item_count).
"""
conn = sqlite3.connect(str(DB_PATH))
items = fetch_items(conn)
conn.close()
items = compute_index(items)
items = decay_index(items, HALF_LIFE_H)
page = build_html(items)
if dry_run:
out = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_preview.html")
with open(out, "w") as f:
f.write(page)
fresh = [it for it in items if it.get("fresh")]
top = sorted(fresh, key=lambda x: x["clickability_decayed"], reverse=True)[:TOP_N]
print(f"[dry-run] wrote {out} ({len(items)} items, {len(fresh)} fresh today)")
print(f"TOP {TOP_N} FRESH (today only) by decayed clickability:")
for i, it in enumerate(top, 1):
print(f" {i}. [{it['clickability_decayed']:.2f} | age {it['age_hours']:.0f}h] {it['source']:10} {it['title'][:55]}")
return out, len(items)
target = webroot or (WEBROOT if os.path.isdir(WEBROOT) else str(FALLBACK_WEBROOT))
os.makedirs(target, exist_ok=True)
with open(os.path.join(target, "index.html"), "w") as f:
f.write(page)
with open(os.path.join(target, "feed.json"), "w") as f:
json.dump([
{"title": i["title"], "url": i["url"], "source": i["source"],
"clickability_decayed": i["clickability_decayed"], "age_hours": i["age_hours"],
"first_seen": i.get("first_seen")}
for i in sorted(items, key=lambda x: x["clickability_decayed"], reverse=True)
], f, indent=2)
where = "WEBROOT" if target == WEBROOT else "fallback(~oracle/site)"
print(f"[render] wrote {target}/index.html ({len(items)} items) -> {where}")
return os.path.join(target, "index.html"), len(items)
# ── Variant rendering ──────────────────────────────────────────────────────
def _variant_card(it: dict, display: dict, big: bool = False) -> str:
"""Build an HTML card for a variant edition."""
title = html.escape(it.get("title") or "(untitled)")
url = html.escape(it.get("url") or "#")
src = html.escape(it.get("source", ""))
t = _fmt_time(it.get("first_seen"))
summary_raw = _clean_summary(it.get("summary") or "")
summary = html.escape(summary_raw[:200])
score = it.get("signal_score")
tier = it.get("source_tier")
verdict = it.get("verdict", "")
cls = "card big" if big else "card"
summary_html = ('{}
'.format(summary)) if (summary and display.get("show_summary") and big) else ""
# Build meta badges
badges = f'{src}'
if t:
badges += f'{t}'
if display.get("show_score") and score is not None:
badges += f'sig {float(score):.1f}'
if display.get("show_tier") and tier is not None:
badges += f'T{tier}'
if display.get("show_verdict") and verdict:
badges += f'{verdict}'
return f"""
{badges}
{summary_html}
"""
def render_variant_html(items: list[dict], variant: dict) -> str:
"""Build the full HTML page for a variant edition."""
display = variant.get("display", {})
name = variant.get("name", "Athena")
desc = variant.get("description", "")
logo = display.get("logo", "🏛️")
top_n = display.get("top_n", 8)
half_life = variant.get("ranking", {}).get("half_life_h", 18)
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
top = items[:top_n]
stack = items[top_n:]
# Group stack by day
by_day = OrderedDict()
for it in stack:
day = (it.get("first_seen") or "")[:10] or "unknown"
by_day.setdefault(day, []).append(it)
top_html = "\n".join(_variant_card(it, display, big=True) for it in top)
stack_html = ""
for day, rows in by_day.items():
cards = "".join(_variant_card(it, display) for it in rows)
stack_html += f"""
🗒️ {html.escape(day)}
{cards}
"""
theme = _theme_css(display)
return f"""
{html.escape(name)}
🔥 Top News
{top_html}
{('🗄️ The Stack
' + stack_html) if stack else ''}
"""
def render_variant_json(items: list[dict], variant: dict, path: str) -> None:
"""Write a JSON feed for a variant edition."""
display = variant.get("display", {})
feed = []
for i in items:
entry = {
"title": i.get("title", ""),
"url": i.get("url", ""),
"source": i.get("source", ""),
"signal_score": float(i.get("signal_score") or 0),
"first_seen": i.get("first_seen", ""),
}
if display.get("show_tier"):
entry["source_tier"] = i.get("source_tier")
if display.get("show_verdict"):
entry["verdict"] = i.get("verdict", "")
feed.append(entry)
with open(path, "w") as f:
json.dump(feed, f, indent=2)