94 lines
2.5 KiB
Python
94 lines
2.5 KiB
Python
# Rule-based triage classifier for support inquiries.
|
|
#
|
|
# Deterministic keyword scorer: every category carries a list of case-insensitive
|
|
# substrings (single words and multi-word phrases). classify() returns the
|
|
# category with the most matching keywords; ties are resolved by a fixed priority
|
|
# order. Texts with zero keyword hits fall back to "other".
|
|
#
|
|
# This is a dictionary-exact router (triage lane). Real-world accuracy is
|
|
# measured, not assumed: the gold set in gold.json and the assertion in
|
|
# test_triage.py pin the expected behaviour, and run_audit.py reports precision /
|
|
# recall / f1 per category.
|
|
|
|
CATEGORY_ORDER = [
|
|
"account",
|
|
"billing",
|
|
"technical_support",
|
|
"feature_request",
|
|
"other",
|
|
]
|
|
|
|
CATEGORY_META = {
|
|
"account": "Account access & identity",
|
|
"billing": "Billing & payments",
|
|
"technical_support": "Technical support",
|
|
"feature_request": "Feature request",
|
|
"other": "Other / general",
|
|
}
|
|
|
|
# Multi-word keywords are matched as substrings too, so "log in" matches
|
|
# "cannot log in" (which is purely account-side speech).
|
|
KEYWORDS = {
|
|
"account": [
|
|
"log in",
|
|
"password",
|
|
"2fa",
|
|
"sign up",
|
|
"signup",
|
|
"cannot log in",
|
|
"verification code",
|
|
"not received my code",
|
|
],
|
|
"billing": [
|
|
"invoice",
|
|
"charged",
|
|
"refund",
|
|
"subscription",
|
|
"credit card",
|
|
"payment method",
|
|
"amount due",
|
|
"billing statement",
|
|
],
|
|
"technical_support": [
|
|
"crash",
|
|
"500",
|
|
"slow",
|
|
"deploy",
|
|
"cannot connect",
|
|
"connection refused",
|
|
"database unreachable",
|
|
"page not found",
|
|
"timeout",
|
|
"install",
|
|
"uptime check",
|
|
"api error",
|
|
"experienced an error",
|
|
],
|
|
"feature_request": [
|
|
"feature",
|
|
"suggest",
|
|
"enhancement",
|
|
"request",
|
|
"would like",
|
|
"new feature",
|
|
"add ability",
|
|
],
|
|
}
|
|
|
|
|
|
def classify(text):
|
|
"""Return (category, scores_dict) for a single inquiry string."""
|
|
low = text.lower()
|
|
scores = {"other": 0}
|
|
scores.update({cat: sum(1 for k in kws if k in low) for cat, kws in KEYWORDS.items()})
|
|
best = max(scores.values())
|
|
if best == 0:
|
|
return "other", scores
|
|
winners = [c for c in CATEGORY_ORDER if scores[c] == best]
|
|
return winners[0], scores
|
|
|
|
|
|
def batch_classify(texts):
|
|
"""Classify a list of texts; result mirrors (text, category)."""
|
|
return [classify(t) for t in texts]
|