96 lines
2.9 KiB
Python
96 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Fresh, self-contained triage classifier for mailbox messages.
|
|
|
|
Task : sol lane "Triage - everyday Q&A and classification".
|
|
Input: a directory of raw message files (subject:/body:).
|
|
Output: a TSV (file<TAB>lane) and a JSON with per-lane counts.
|
|
|
|
Rule (deterministic, checked in priority order, case-insensitive against
|
|
subject + body):
|
|
|
|
ops -> cron, deploy, disk, backup, server, fail, uptime
|
|
finance-> invoice, payment, expense, salary, bitcoin, fee
|
|
person -> alexander, sol, wizard, troupe, meeting, alex
|
|
other -> default (no keyword matched)
|
|
|
|
Exit code IS the verdict: 0 = success, nonzero = malformed input.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
LANES = [
|
|
("ops", ["cron", "deploy", "disk", "backup", "server", "fail", "uptime"]),
|
|
("finance", ["invoice", "payment", "expense", "salary", "bitcoin", "fee"]),
|
|
("person", ["alexander", "sol", "wizard", "troupe", "meeting", "alex"]),
|
|
]
|
|
ORDER = ["ops", "finance", "person", "other"]
|
|
|
|
|
|
def classify(subject, body):
|
|
text = " ".join([subject, body]).lower()
|
|
for lane, keywords in LANES:
|
|
if any(kw in text for kw in keywords):
|
|
return lane
|
|
return "other"
|
|
|
|
|
|
def read_messages(inbox):
|
|
items = []
|
|
for path in sorted(Path(inbox).glob("*.txt")):
|
|
subject, body = "", ""
|
|
for raw in path.read_text(encoding="utf-8").splitlines():
|
|
if raw.startswith("subject:"):
|
|
subject = raw.split(":", 1)[1].strip()
|
|
elif raw.startswith("body:"):
|
|
body = raw.split(":", 1)[1].strip()
|
|
items.append({"file": path.name, "subject": subject, "body": body})
|
|
return items
|
|
|
|
|
|
def main(argv):
|
|
if len(argv) != 4:
|
|
print("usage: classify-messages.py INBOX OUT_JSON OUT_TSV", file=sys.stderr)
|
|
return 2
|
|
inbox, out_json, out_tsv = [Path(a) for a in argv[1:4]]
|
|
if not inbox.is_dir():
|
|
print("FAIL: inbox is not a directory: %s" % inbox, file=sys.stderr)
|
|
return 1
|
|
|
|
items = read_messages(inbox)
|
|
for it in items:
|
|
it["lane"] = classify(it["subject"], it["body"])
|
|
|
|
counts = {lane: 0 for lane in ORDER}
|
|
for it in items:
|
|
counts[it["lane"]] += 1
|
|
|
|
out_json.write_text(
|
|
json.dumps(
|
|
{
|
|
"tool": "sol-l030-classify-messages.py",
|
|
"inbox": str(inbox),
|
|
"total": len(items),
|
|
"counts": counts,
|
|
"lanes": ORDER,
|
|
"items": [{"file": i["file"], "lane": i["lane"]} for i in items],
|
|
},
|
|
indent=2,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
with out_tsv.open("w", encoding="utf-8") as fh:
|
|
fh.write("file\tlane\n")
|
|
for it in items:
|
|
fh.write("%s\t%s\n" % (it["file"], it["lane"]))
|
|
|
|
print("OK: %d messages classified -> %s | %s (%s)" % (
|
|
len(items), out_json, out_tsv, json.dumps(counts)))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv))
|