"""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}

{title}

{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

Athena AI News

Auto-ranked by Clickability Index · decays with age so the stack flows top → bottom · generated {now} · {len(items)} stories

\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}

{title}

{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)}

{logo} {html.escape(name)}

{html.escape(desc)} · generated {now} · {len(items)} stories

🔥 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)