From 7201804a3131eaa209eeb9e6393a664e3bd2d3ca Mon Sep 17 00:00:00 2001 From: drjones Date: Mon, 3 Aug 2026 23:51:36 -0700 Subject: [PATCH] Evolution system: twice-daily agent sweep cron, evolution log, NOC evolution panel --- core/dashboard_error.log | 2 ++ core/evolution_log.json | 10 ++++++ core/evolution_log.py | 74 ++++++++++++++++++++++++++++++++++++++++ dashboard/noc.py | 48 ++++++++++++++++++++++++-- 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 core/evolution_log.json create mode 100644 core/evolution_log.py diff --git a/core/dashboard_error.log b/core/dashboard_error.log index ccc7fac..db75006 100644 --- a/core/dashboard_error.log +++ b/core/dashboard_error.log @@ -2,3 +2,5 @@ * Running on http://127.0.0.1:5106 Press CTRL+C to quit 127.0.0.1 - - [03/Aug/2026 21:35:50] "GET /health HTTP/1.1" 200 - +127.0.0.1 - - [03/Aug/2026 23:50:58] "GET / HTTP/1.1" 200 - +127.0.0.1 - - [03/Aug/2026 23:50:58] "GET /favicon.ico HTTP/1.1" 404 - diff --git a/core/evolution_log.json b/core/evolution_log.json new file mode 100644 index 0000000..554f4cc --- /dev/null +++ b/core/evolution_log.json @@ -0,0 +1,10 @@ +[ + { + "vertical": "all", + "agent": "hermes", + "feature": "NOC Dashboard v2 deployed", + "details": "Clean premium design with Inter font, per-site cards, metrics, evolution tracking panel", + "status": "completed", + "timestamp": "2026-08-03T23:51:15.624329" + } +] \ No newline at end of file diff --git a/core/evolution_log.py b/core/evolution_log.py new file mode 100644 index 0000000..bc18dc3 --- /dev/null +++ b/core/evolution_log.py @@ -0,0 +1,74 @@ +""" +Site Evolution Log — tracks all improvements made by evolution agents. +Appended to by sub-agents, displayed on NOC dashboard. +""" +import json, os +from datetime import datetime +from pathlib import Path + +LOG_PATH = Path(os.path.expanduser("~/auto-publisher/core/evolution_log.json")) + +def log_evolution(vertical: str, agent: str, feature: str, details: str, status: str = "completed"): + """Record an evolution event.""" + log = [] + if LOG_PATH.exists(): + log = json.loads(LOG_PATH.read_text()) + + log.append({ + "vertical": vertical, + "agent": agent, + "feature": feature, + "details": details[:500], + "status": status, + "timestamp": datetime.now().isoformat(), + }) + + # Keep last 200 entries + log = log[-200:] + LOG_PATH.write_text(json.dumps(log, indent=2)) + + +def get_recent_evolutions(limit: int = 20) -> list[dict]: + """Get recent evolution events.""" + if not LOG_PATH.exists(): + return [] + log = json.loads(LOG_PATH.read_text()) + return sorted(log, key=lambda x: x["timestamp"], reverse=True)[:limit] + + +def get_evolution_stats() -> dict: + """Aggregate evolution statistics.""" + if not LOG_PATH.exists(): + return {"total": 0, "by_vertical": {}, "recent": []} + + log = json.loads(LOG_PATH.read_text()) + by_v = {} + for entry in log: + v = entry["vertical"] + by_v[v] = by_v.get(v, 0) + 1 + + return { + "total": len(log), + "by_vertical": by_v, + "latest": log[-1]["timestamp"][:19] if log else None, + "recent": [e["feature"] for e in log[-5:]], + } + + +if __name__ == "__main__": + import argparse + ap = argparse.ArgumentParser() + ap.add_argument("--log", action="store_true") + ap.add_argument("--vertical", type=str, default="all") + ap.add_argument("--feature", type=str, default="test") + ap.add_argument("--details", type=str, default="") + ap.add_argument("--agent", type=str, default="manual") + ap.add_argument("--status", type=str, default="completed") + args = ap.parse_args() + + if args.log: + log_evolution(args.vertical, args.agent, args.feature, args.details, args.status) + print(f"Logged: {args.vertical} — {args.feature}") + else: + stats = get_evolution_stats() + print(json.dumps(stats, indent=2)) diff --git a/dashboard/noc.py b/dashboard/noc.py index d3c8f42..2f1eea8 100644 --- a/dashboard/noc.py +++ b/dashboard/noc.py @@ -1,10 +1,14 @@ """ NOC Dashboard — clean, modern fleet monitor for all 8 publisher sites. """ -import json, time, threading, requests +import json, time, threading, requests, sys, os from datetime import datetime from flask import Flask, jsonify +# Evolution log +sys.path.insert(0, os.path.expanduser("~/auto-publisher/core")) +from evolution_log import get_recent_evolutions, get_evolution_stats, log_evolution + app = Flask(__name__) SITES = { @@ -57,6 +61,9 @@ def home(): if not cache["ts"] or (datetime.now() - datetime.fromisoformat(cache["ts"])).seconds > 30: threading.Thread(target=refresh, daemon=True).start() data = cache["data"] or {} + evo_stats = get_evolution_stats() + evo_recent = get_recent_evolutions(6) + evo_html = build_evo_html(evo_recent) return HTML.format( alive=sum(1 for s in data.values() if s.get("alive")), total=len(data), @@ -65,6 +72,8 @@ def home(): today=sum(s.get("today", 0) for s in data.values()), subs=sum(s.get("subs", 0) for s in data.values()), updated=(cache["ts"] or "")[:19], + evo_total=evo_stats["total"], + evo_html=evo_html, cards=build_cards(data), ) @@ -115,10 +124,27 @@ def api_refresh(): refresh() return jsonify({"status": "ok"}) +@app.route("/api/evolution") +def api_evolution(): + return jsonify({ + "stats": get_evolution_stats(), + "recent": get_recent_evolutions(20), + }) + @app.route("/health") def health(): return jsonify({"status": "ok", "ts": cache["ts"]}) +def build_evo_html(events): + if not events: + return '
No evolution events yet. First sweep runs at midnight.
' + out = [] + for e in events: + ts = e["timestamp"][:16].replace("T", " ") + out.append(f'
{ts}{e["vertical"]}{e["feature"][:80]}{e["status"]}
') + return "\n".join(out) + + # ── Template ── HTML = """ @@ -177,7 +203,19 @@ body{{font-family:var(--f);background:var(--bg);color:var(--t);min-height:100vh; .ft{{text-align:center;padding:1.5rem;color:var(--t3);font-size:.7rem;border-top:1px solid var(--b)}} .ft a{{color:var(--t2);text-decoration:none}} .ft a:hover{{color:var(--t)}} -@media(max-width:800px){{.gr{{grid-template-columns:1fr;padding:0 1rem 1rem}}.sr{{grid-template-columns:repeat(3,1fr);padding:1rem}}.cm{{grid-template-columns:repeat(3,1fr)}}.tb{{padding:0 1rem}}}} +.ft a:hover{{color:var(--t)}} +.ev-panel{{max-width:1400px;margin:0 auto;padding:0 2rem 2rem}} +.ev-inner{{background:var(--s1);border:1px solid var(--b);border-radius:var(--r);padding:1.25rem}} +.ev-title{{font-weight:700;font-size:.95rem;margin-bottom:.75rem;display:flex;align-items:center;gap:.5rem}} +.ev-title span{{color:var(--t3);font-weight:400;font-size:.75rem}} +.ev-row{{display:flex;align-items:center;gap:.75rem;padding:.35rem 0;font-size:.75rem;border-bottom:1px solid var(--b)}} +.ev-row:last-child{{border-bottom:none}} +.ev-ts{{color:var(--t3);min-width:110px;font-variant-numeric:tabular-nums}} +.ev-v{{font-weight:600;min-width:55px;text-transform:capitalize}} +.ev-feat{{color:var(--t2);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}} +.ev-stat{{color:#22c55e;font-size:.65rem;text-transform:uppercase;font-weight:600}} +.ev-empty{{color:var(--t3);font-size:.8rem;font-style:italic;padding:.5rem 0}} +@media(max-width:800px){{.gr{{grid-template-columns:1fr;padding:0 1rem 1rem}}.sr{{grid-template-columns:repeat(3,1fr);padding:1rem}}.cm{{grid-template-columns:repeat(3,1fr)}}.tb{{padding:0 1rem}}.ev-panel{{padding:0 1rem 1rem}}}} @@ -198,6 +236,12 @@ body{{font-family:var(--f);background:var(--bg);color:var(--t);min-height:100vh;
{articles}
Network Total
{cards}
+
+
+
🔄 Site Evolution ({evo_total} improvements)
+ {evo_html} +
+
Publisher Fleet NOC · Auto-refresh 60s · Pipeline · Kalshi
"""