121 lines
4.7 KiB
Python
121 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Triage-2026-09-28: classify the 12 PENDING items in inbox/seed-2026-09-26.json.
|
|
|
|
Lane: Triage - everyday Q&A and classification.
|
|
This is a deterministic, marker-based classifier over the seed's own fields
|
|
(summary prefix + proposed_action). No editorial inference beyond the marker.
|
|
The CHECK file re-runs this exact classifier and diffs the result against the
|
|
manifest; exit 0 iff the manifest matches.
|
|
"""
|
|
import json
|
|
import os
|
|
import datetime
|
|
|
|
ROOT = "/root/troupe-standup"
|
|
SEED = os.path.join(ROOT, "inbox", "seed-2026-09-26.json")
|
|
OUT = os.path.join(ROOT, "wizard-work", "sol")
|
|
PRIMARY = "/root/.hermes/wizard-outputs/sol"
|
|
|
|
os.makedirs(OUT, exist_ok=True)
|
|
os.makedirs(PRIMARY, exist_ok=True)
|
|
|
|
seed = json.load(open(SEED))
|
|
assert set(seed.keys()) == {"cutover", "decided", "exported_at", "pending", "source"}
|
|
|
|
# Deterministic marker-based classifier. Category vocabulary is fixed and
|
|
# small; no free-form labels, so the result is a classification, not an essay.
|
|
def classify(item):
|
|
s = (item.get("summary") or "").strip()
|
|
if s.startswith("Archive fix:"):
|
|
return "archive_fix"
|
|
if s.startswith("Turd app:"):
|
|
return "app_pr"
|
|
if s.startswith("Trial by Timmy:") or s.startswith("Q9:"):
|
|
return "general"
|
|
return "other"
|
|
|
|
|
|
CAT_ORDER = ["archive_fix", "app_pr", "general", "other"]
|
|
DECISION_ORDER = ["approve", "reject", "table"]
|
|
|
|
items = []
|
|
for it in sorted(seed["pending"], key=lambda x: int(x["id"])):
|
|
assert set(it.keys()) >= {"id", "summary", "proposed_action", "evidence"}
|
|
cat = classify(it)
|
|
items.append({
|
|
"id": int(it["id"]),
|
|
"category": cat,
|
|
"status": "table", # pending/unresolved: not decided by anyone
|
|
"summary": (it["summary"] or "").strip(),
|
|
"proposed_action": (it["proposed_action"] or "").strip(),
|
|
"evidence_quote": (it["summary"] or "").strip(),
|
|
"classification_rule": "summary prefix marker",
|
|
})
|
|
|
|
record = {
|
|
"artifact": "Triage-2026-09-28-classify.json",
|
|
"seed": "inbox/seed-2026-09-26.json",
|
|
"generated_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"lane": "Triage - everyday Q&A and classification",
|
|
"summary": (
|
|
"Deterministic marker-based classification of the 12 PENDING items in the "
|
|
"inbox seed. Family is fixed by summary prefix; no editorial inference. "
|
|
"All 12 are 'table' (unresolved); only the seed's id 13 (decided) is recorded."
|
|
),
|
|
"decision_counts": {"approve": 0, "reject": 0, "table": 0},
|
|
"categories": {c: 0 for c in CAT_ORDER},
|
|
"items": items,
|
|
}
|
|
|
|
for it in items:
|
|
record["decision_counts"][it["status"]] += 1
|
|
record["categories"][it["category"]] += 1
|
|
|
|
json_path = os.path.join(OUT, "Triage-2026-09-28-classify.json")
|
|
with open(json_path, "w") as f:
|
|
json.dump(record, f, indent=2)
|
|
f.write("\n")
|
|
|
|
# Human-readable triage note.
|
|
lines = []
|
|
lines.append("# Triage - 2026-09-28")
|
|
lines.append("")
|
|
lines.append("## Decision")
|
|
lines.append("")
|
|
lines.append("Deterministic classification of the 12 PENDING items in `inbox/seed-2026-09-26.json`." )
|
|
lines.append("")
|
|
lines.append("| # | Category | Status | Evidence (summary)" )
|
|
lines.append("|---|---|---|---|")
|
|
for it in items:
|
|
lines.append(f"| {it['id']} | {it['category']} | {it['status']} | {(it['summary'] or '')[:120]} |")
|
|
lines.append("")
|
|
lines.append("## Counts")
|
|
lines.append("")
|
|
lines.append(f"- table (unresolved): {record['decision_counts']['table']}")
|
|
lines.append(f"- archive_fix: {record['categories']['archive_fix']}")
|
|
lines.append(f"- app_pr: {record['categories']['app_pr']}")
|
|
lines.append(f"- general: {record['categories']['general']}")
|
|
lines.append("")
|
|
lines.append("## Notes")
|
|
lines.append("")
|
|
lines.append("- Members: archive_fix ids 1,3,6,7,10,11,12; app_pr ids 2,4,5; general ids 8,9.")
|
|
lines.append("- Status 'table': none of the 12 has a recorded decision in the seed; each")
|
|
lines.append(" needs Alexander's (or maintainer's) decision before any action is taken.")
|
|
lines.append("- The seed's single decided item (id 13, magazine-091 Strategy) is NOT included")
|
|
lines.append(" among the pending; it is already approved and published.")
|
|
lines.append("- No live re-queries; classification is a reading of the seed's stated fields.")
|
|
lines.append("")
|
|
json_note = os.path.join(OUT, "TRIAGE-2026-09-28.md")
|
|
with open(json_note, "w") as f:
|
|
f.write("\n".join(lines) + "\n")
|
|
|
|
# Mirror to the primary wizard-outputs location as the finished artifact.
|
|
for src, dst in [(json_path, os.path.join(PRIMARY, "Triage-2026-09-28-classify.json")),
|
|
(json_note, os.path.join(PRIMARY, "TRIAGE-2026-09-28.md"))]:
|
|
import shutil
|
|
shutil.copy2(src, dst)
|
|
|
|
print("wrote", json_path)
|
|
print("wrote", json_note)
|
|
print("counts:", record["decision_counts"], record["categories"])
|