Files
troupe-standup/wizard-work/sol/triage_gen_daily.py
T

167 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""Sol lane — daily-ledger triage (corrected, reads both surfaces).
The previous generator only read the legacy flat series BASE/<lane>-*.txt,
whose newest files are 2026-10-16-1446.txt (age ~185h) and so printed all
seven lanes STALE. Since 2026-10-16 the lanes write inside BASE/<lane>/.
This version reads BOTH surfaces and reports the newest artefact per lane
across them, naming the surface that supplied the newest file.
Newest artefact per lane:
- if BASE/<lane>/ holds files, those (newest) win, surface named "subdir"
- else the legacy flat file wins, surface named "legacy-flat"
Negative control: a lane with NO files on either surface still reads STALE
(never inherits another surface's age) and is named "none".
Output paths carry the run's own date; no hardcoded dates.
AGE_CLASSIFY rule: age <= 24h -> ACTIVE; age <= 168h -> WATCH; else STALE.
"""
import glob
import json
import os
from datetime import datetime, timezone
BASE = "/root/.hermes/wizard-outputs"
# Output lives in a dedicated directory, separate from the raw logs the lanes
# write into BASE/<lane>/, so generation never pollutes a lane's own scan.
OUT_DIR = os.path.join(BASE, "sol", "_triage")
os.makedirs(OUT_DIR, exist_ok=True)
RUN_DATE = datetime.now(timezone.utc).strftime("%Y-%m-%d")
OUT_JSON = os.path.join(OUT_DIR, "daily-ledger-triage-%s.json" % RUN_DATE)
OUT_MD = os.path.join(OUT_DIR, "daily-ledger-triage-%s.md" % RUN_DATE)
# The seven primary daily-ledger lanes.
PRIMARY = ["forge", "ledger", "null", "quill", "sphinx", "sol", "spark"]
# A lane with no files on either surface: negative control, must stay STALE.
NEGATIVE_CONTROL = "phantom"
def to_utc(ts):
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def classify(age_sec):
age_h = age_sec / 3600.0
if age_h <= 24.0:
return "ACTIVE"
if age_h <= 168.0:
return "WATCH"
return "STALE"
def surfaces(pid):
"""Return (flat_files, subdir_files) for a lane.
Legacy flat series: BASE/<lane>-YYYY-MM-DD-HHMM.txt at the top of BASE/.
Modern series: BASE/<lane>/* at the top of BASE/.
"""
flat = sorted(glob.glob(os.path.join(BASE, pid + "-*.txt")))
subdir = os.path.join(BASE, pid)
sub = []
if os.path.isdir(subdir):
sub = sorted(
os.path.join(subdir, f)
for f in os.listdir(subdir)
if os.path.isfile(os.path.join(subdir, f))
)
return flat, sub
def newest_for_pid(pid):
"""Return (latest_path, source_surface, file_count, age_hours, status).
source_surface is one of "subdir", "legacy-flat", "none".
"""
flat, sub = surfaces(pid)
all_files = flat + sub
if not all_files:
# Negative control: empty on both surfaces -> classic STALE.
return None, "none", 0, 0.0, "STALE"
latest = max(all_files, key=os.path.getmtime)
mtime = os.path.getmtime(latest)
source = "subdir" if latest in sub else "legacy-flat"
age_sec = (datetime.now(timezone.utc) - datetime.fromtimestamp(
mtime, tz=timezone.utc)).total_seconds()
count = len(all_files)
return latest, source, count, age_sec / 3600.0, classify(age_sec)
def main():
now = datetime.now(timezone.utc)
lanes = []
for pid in PRIMARY + [NEGATIVE_CONTROL]:
latest, source, count, age_h, status = newest_for_pid(pid)
if latest is None:
lanes.append({
"id": pid,
"surface": "none",
"latest_log": None,
"latest_mtime_utc": None,
"daily_log_count": 0,
"age_hours": 0.0,
"status": status,
})
else:
lanes.append({
"id": pid,
"surface": source,
"latest_log": os.path.basename(latest),
"latest_mtime_utc": to_utc(os.path.getmtime(latest)),
"daily_log_count": count,
"age_hours": round(age_h, 1),
"status": status,
})
artifact = {
"generated_utc": now.strftime("%Y-%m-%dT%H:%M:%SZ"),
"run_date": RUN_DATE,
"classify_rule": "AGE_CLASSIFY: ACTIVE <=24h, WATCH <=168h, else STALE",
"base": BASE,
"surfaces": ["legacy-flat", "subdir"],
"negative_control_lane": NEGATIVE_CONTROL,
"lanes": lanes,
}
with open(OUT_JSON, "w", encoding="utf-8") as fh:
json.dump(artifact, fh, indent=2)
fh.write("\n")
lines = []
lines.append("# Daily-ledger triage — sol — %s" % RUN_DATE)
lines.append("")
lines.append("Recomputed from raw logs under %s at %s." % (BASE, now.strftime("%Y-%m-%dT%H:%M:%SZ")))
lines.append("")
lines.append("| lane | surface | latest log | latest mtime (UTC) | logs | age (h) | status |")
lines.append("|---|---|---|---|---|---|---|")
for e in sorted(lanes, key=lambda x: x["id"]):
lines.append(
"| %s | %s | %s | %s | %d | %.1f | %s |" % (
e["id"], e["surface"],
e["latest_log"] or "—", e["latest_mtime_utc"] or "—",
e["daily_log_count"], e["age_hours"], e["status"]))
lines.append("")
lines.append("## Summary")
lines.append("")
counts = {}
for e in lanes:
counts[e["status"]] = counts.get(e["status"], 0) + 1
lines.append("- **%d** lanes total" % len(lanes))
for s in ("ACTIVE", "WATCH", "STALE"):
lines.append("- **%d** %s" % (counts.get(s, 0), s))
lines.append("- **Negative control** `%s`: surface none, STALE (no inheritance)." % NEGATIVE_CONTROL)
lines.append("")
with open(OUT_MD, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines))
print("WROTE", OUT_JSON)
print("WROTE", OUT_MD)
for e in sorted(lanes, key=lambda x: x["id"]):
print(" %-6s surface=%-8s %-24s count=%-4d age=%-6.1fh %s"
% (e["id"], e["surface"],
e["latest_log"] or "none", e["daily_log_count"],
e["age_hours"], e["status"]))
if __name__ == "__main__":
main()