mirror of
https://github.com/Sea-Haven-Industries/apm-wo-analysis.git
synced 2026-09-30 07:43:15 +00:00
Stand up the Phase 0 CDK scaffold for the daily APM work-order analysis pipeline: two-stack CDK app (pipeline + grafana), classifier and slack-post Lambda packages, dashboards-as-code, the local drop-folder uploader, and a classifier smoke-test placeholder. Wire CI/CD to the org reusable workflows: ci.yaml -> ci-python-sam (ruff + cdk synth) and deploy.yaml -> cd-cdk (OIDC, cdk deploy --all). Pin aws-cdk-lib==2.253.1; Lambdas target Python 3.12 / arm64. Rewrite .gitignore to the org Python-CDK standard so the source-of- truth files (CLAUDE.md, docs/, .claude/agents) are tracked while build artifacts (.venv, cdk.out, caches) stay ignored. Domain logic, stack resources, and dashboards are stubbed and filled in across Phases 1-5 (docs/BUILD.md). cdk synth is green for both stacks; ruff check/format pass.
76 lines
2.3 KiB
Python
76 lines
2.3 KiB
Python
"""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
|