apm-wo-analysis/lambdas/classifier/classify.py

77 lines
2.3 KiB
Python
Raw Normal View History

"""Two-axis APM work-order classifier — the core domain logic.
Axis 1 — comment intent: regex over the HTML-stripped ``Last Comment``,
most-specific first.
Axis 2 — structured state: ``Hold Reason`` + ``WO Status``.
Resolution: comment intent wins when confident, else structured state, else
``Other``. A Claude Haiku fallback (Secrets Manager
``apm-wo-analysis/anthropic-api-key``) is reserved strictly for ambiguous
free-text with no structured signal. A mismatch detector flags when comment
intent contradicts structured state — surface, never suppress.
The authoritative spec is CLAUDE.md ("The classification model"). This module
is owned by the classifier-engineer agent; the rule ladder and Haiku fallback
are implemented in Phase 2 (docs/BUILD.md) and smoke-tested against a real
export before merge.
"""
from __future__ import annotations
# Axis 2 — Hold Reason → category.
HOLD_TO_CATEGORY = {
"SCHEDULING": "Awaiting Scheduling",
"REPORT": "Report / Docs Needed",
"VENDOR": "Awaiting Vendor / Parts",
"PARTS": "Awaiting Vendor / Parts",
"ORDER": "Awaiting Vendor / Parts",
"VERIFY": "Verification Needed",
"RESOURCE": "Resource Hold",
"NOEQUIP": "No Equipment",
}
# Axis 2 — WO Status signals.
WO_STATUS_CANCELLED = "RCAN" # → Cancelled
WO_STATUS_HOLD = "H" # corroborates On Hold
WO_STATUS_IN_FLIGHT = frozenset({"IP", "R", "RR"})
ESCALATION_CATEGORIES = frozenset(
{
"1st Escalation",
"2nd Escalation",
"3rd Escalation",
"SIM Ticket",
"Other Escalation",
}
)
ACTION_NEEDED_CATEGORIES = ESCALATION_CATEGORIES | frozenset(
{
"Awaiting Scheduling",
"Report / Docs Needed",
"Awaiting Report / Invoice",
"Awaiting Vendor / Parts",
"Status Inquiry",
"Vendor No-Show",
}
)
def strip_html(comment: str | None) -> str:
"""Unwrap the HTML-wrapped Last Comment and decode entities. (Phase 2)"""
raise NotImplementedError
def comment_intent(comment: str) -> str | None:
"""Ordered, most-specific-first rule ladder; returns a bucket or None. (Phase 2)"""
raise NotImplementedError
def classify(
wo_status: str | None,
hold_reason: str | None,
last_comment: str | None,
) -> tuple[str, str | None]:
"""Resolve to ``(final_category, mismatch_reason | None)``. (Phase 2)"""
raise NotImplementedError