Build a labeled sample of a few hundred examples per category. The arXiv benchmark used 1,247 alerts for one multi-stage attack scenario, per the arXiv paper. Have two experienced reviewers label independently, reconcile disagreements, and record the reconciled label as ground truth. If they cannot agree on a clear-cut case, the rubric is not tight enough and the category stays with humans.
Compute four numbers per category: precision of closure (of alerts the model closed, the share truly benign), recall of escalation (of suspicious alerts, the share escalated), false-negative rate, and coverage (the share of alerts the model was willing to decide at all). Precision protects the audit record. Recall protects you from the consequence you feared. Coverage tells you whether the economics work.
The threshold choice is a business decision framed by cost asymmetry. DataRobot's figures, $30 to $70 per review against $145 million average penalties for proven money laundering, show the direction clearly even though the penalty number is an average across proven cases, per DataRobot.
A worked example. A category generates 10,000 alerts a quarter, 2 percent genuinely suspicious. A model that closes 8,000 at 98 percent precision wrongly closes 160, which at that base rate is 160 of roughly 200 truly suspicious alerts. That is a recall disaster wearing a good precision number. The same model closing 3,000 at 99.5 percent precision wrongly closes 15 and pushes 7,000 to humans. Report precision, recall, and raw counts together, always.
To compare candidate models on the same sample without separate provider SDKs, one OpenAI-compatible endpoint such as Quanta Inference keeps the evaluation harness constant across text models.
from collections import defaultdict
def score(labeled_alerts):
# each alert: category, human_label ("benign"/"suspicious"),
# model_decision ("close"/"escalate"/"insufficient")
buckets = defaultdict(lambda: {"tp": 0, "fp": 0, "fn": 0, "tn": 0, "deferred": 0})
for a in labeled_alerts:
b = buckets[a["category"]]
if a["model_decision"] == "insufficient":
b["deferred"] += 1
continue
closed = a["model_decision"] == "close"
suspicious = a["human_label"] == "suspicious"
if closed and suspicious:
b["fn"] += 1
elif closed:
b["tn"] += 1
elif suspicious:
b["tp"] += 1
else:
b["fp"] += 1
out = {}
for cat, b in buckets.items():
decided = b["tp"] + b["fp"] + b["fn"] + b["tn"]
closed_total = b["tn"] + b["fn"]
out[cat] = {
"closure_precision": round(b["tn"] / closed_total, 4) if closed_total else None,
"escalation_recall": round(b["tp"] / (b["tp"] + b["fn"]), 4) if (b["tp"] + b["fn"]) else None,
"coverage": round(decided / (decided + b["deferred"]), 4) if (decided + b["deferred"]) else None,
"raw": dict(b),
}
return out
Read the raw counts per category. A 97 percent overall precision can hide one category at 70 percent.