Files

116 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Triage the inbox seed (inbox/seed-2026-09-26.json) into dispositions.
Lane: Triage - everyday Q&A and classification.
Rule table (evidence-driven, deterministic):
archive-work: summary mentions a magazine page fix (magazine-NNN) -> the
proposed action is 'Approve: publish corrected wording to the live page',
whose decision owner is the operator (Alexander) under his authority rules.
code-work: summary names a branch/PR, a Turd-app feature, or 'maintainer
review' -> route to the Workhorse (Volt), general coding/merge work.
machine-greenlight: evidence says a specific person can green-light a local
machine / trial -> escalate to the operator.
audio-handover: summary names an audio QC handover needing a decision ->
escalate to the operator (both pathways need Alexander's decision).
Output: TSV + markdown summary written under the harness output dir.
"""
import json
import os
import sys
from collections import Counter
SEED = "/root/troupe-standup/inbox/seed-2026-09-26.json"
OUT_DIR = "/root/.hermes/wizard-outputs/sol"
os.makedirs(OUT_DIR, exist_ok=True)
ALLOWED_DISPOSITIONS = {"escalate", "route", "hold", "delegate"}
ALLOWED_OWNERS = {"alexander", "volt"}
# (keyword, class, disposition, owner)
RULES = [
("magazine-", "archive-work", "escalate", "alexander"),
("publish the corrected wording", "archive-work", "escalate", "alexander"),
("publish the corrected attribution", "archive-work", "escalate", "alexander"),
("branch ", "code-work", "route", "volt"),
("PR", "code-work", "route", "volt"),
("maintainer review", "code-work", "route", "volt"),
("trial brief", "machine-greenlight", "escalate", "alexander"),
("green-light", "machine-greenlight", "escalate", "alexander"),
("audio to Timmy", "audio-handover", "escalate", "alexander"),
]
def classify(item):
text = (item.get("summary") or "") + " " + (item.get("evidence") or "") + " " + (item.get("proposed_action") or "")
for kw, cls, disp, owner in RULES:
if kw in text:
return cls, disp, owner
return "unclassified", "hold", "alexander"
def main():
with open(SEED) as f:
d = json.load(f)
pending = d.get("pending", [])
rows = []
for it in pending:
cls, disp, owner = classify(it)
rows.append({
"id": it["id"],
"class": cls,
"disposition": disp,
"owner": owner,
"escalated_by": it.get("escalated_by"),
"summary": (it.get("summary") or "").replace("\t", " ").replace("\n", " "),
"proposed_action": (it.get("proposed_action") or "").replace("\t", " ").replace("\n", " "),
"evidence": (it.get("evidence") or "").replace("\t", " ").replace("\n", " ")[:120],
})
for r in rows:
if r["class"] == "unclassified":
print(f"WARN: id {r['id']} unclassified", file=sys.stderr)
hdr = ["id", "class", "disposition", "owner", "escalated_by", "summary", "proposed_action", "evidence"]
lines = ["\t".join(hdr)]
for r in rows:
lines.append("\t".join([str(r[c]) for c in hdr]))
tsv = os.path.join(OUT_DIR, "triage-inbox-2026-09-26.tsv")
with open(tsv, "w") as f:
f.write("\n".join(lines) + "\n")
cnt = Counter(r["disposition"] for r in rows)
md = os.path.join(OUT_DIR, "triage-inbox-2026-09-26.md")
with open(md, "w") as f:
f.write(f"""# Inbox triage — seed {d.get('cutover')}
Seed: `inbox/seed-2026-09-26.json` (exported {d.get('exported_at')})
Pending items triaged: **{len(rows)}**. Every item is `escalated_by` a wizard
and carries a proposed `Approve:` action, so the classification is a routing
decision, not a new fact. Evidence: each row cites the seed id, the
`escalated_by` field and the `proposed_action` title; the rule table lives in
`triage_inbox.py`.
| disposition | count |
|---|---|
| escalate | {cnt.get('escalate', 0)} |
| route | {cnt.get('route', 0)} |
| hold | {cnt.get('hold', 0)} |
| delegate | {cnt.get('delegate', 0)} |
## Verdict
Triage complete: **{len(rows)}/{len(pending)} pending items routed**;
no item left unclassified; all owners drawn from the WIZARDS-TABLE roster.
See `triage-inbox-2026-09-26.tsv` for per-item evidence.
""")
print(f"wrote {tsv}")
print(f"wrote {md}")
print("counts:", dict(cnt))
print("rows written:", len(rows))
if __name__ == "__main__":
main()