Stage 188: escalation recommendation + urgency boost
Browse files- core/decisions.py +48 -0
- core/pipeline.py +14 -1
core/decisions.py
CHANGED
|
@@ -17,8 +17,39 @@ class DecisionItem:
|
|
| 17 |
status: str = "open"
|
| 18 |
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
def recommendation_for_issue(entity_type: str, evidence: List[Dict]) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
labels = {e.get("label") for e in evidence}
|
|
|
|
|
|
|
|
|
|
| 22 |
if "inventory_availability_drop" in labels:
|
| 23 |
return "Audit inventory replenishment and check local stock allocation within 7 days."
|
| 24 |
if "backlog_increase" in labels:
|
|
@@ -26,3 +57,20 @@ def recommendation_for_issue(entity_type: str, evidence: List[Dict]) -> str:
|
|
| 26 |
if "sla_response_increase" in labels:
|
| 27 |
return "Review service team workload and escalation policy within 72 hours."
|
| 28 |
return "Assign an operations owner to review the detected drift and validate root cause."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
status: str = "open"
|
| 18 |
|
| 19 |
|
| 20 |
+
# --- recommendation tiers --------------------------------------------
|
| 21 |
+
#
|
| 22 |
+
# Stage 188 — when the α (acceleration) signal fires, the recommendation
|
| 23 |
+
# is a different KIND of action, not a louder version of the same
|
| 24 |
+
# action. A steady decliner needs to be reviewed in the next planning
|
| 25 |
+
# cycle; an accelerating decliner needs escalation to a senior owner
|
| 26 |
+
# THIS WEEK or the trajectory gets unrecoverable. So `acceleration`
|
| 27 |
+
# in the evidence trail short-circuits the metric-specific suggestion
|
| 28 |
+
# and returns the escalation tier instead.
|
| 29 |
+
|
| 30 |
+
_ESCALATION_RECOMMENDATION = (
|
| 31 |
+
"Escalate to a senior owner now: the rate of degradation is itself "
|
| 32 |
+
"accelerating. Schedule a same-week intervention; do not defer "
|
| 33 |
+
"to the next planning cycle."
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
def recommendation_for_issue(entity_type: str, evidence: List[Dict]) -> str:
|
| 38 |
+
"""Produce a recommendation string for the issue based on its
|
| 39 |
+
evidence trail. The text is English-only; the dashboard's
|
| 40 |
+
i18n.translateRecommendation maps it to Hebrew at render time.
|
| 41 |
+
|
| 42 |
+
Stage 188: when the ``acceleration`` evidence label is present,
|
| 43 |
+
the escalation-tier text takes precedence over any metric-
|
| 44 |
+
specific recommendation. This applies regardless of the metric
|
| 45 |
+
that's accelerating — the engine math doesn't know which metric
|
| 46 |
+
triggered α, only that the second derivative crossed the
|
| 47 |
+
threshold.
|
| 48 |
+
"""
|
| 49 |
labels = {e.get("label") for e in evidence}
|
| 50 |
+
# Acceleration trumps everything — the "kind" of action differs.
|
| 51 |
+
if "acceleration" in labels:
|
| 52 |
+
return _ESCALATION_RECOMMENDATION
|
| 53 |
if "inventory_availability_drop" in labels:
|
| 54 |
return "Audit inventory replenishment and check local stock allocation within 7 days."
|
| 55 |
if "backlog_increase" in labels:
|
|
|
|
| 57 |
if "sla_response_increase" in labels:
|
| 58 |
return "Review service team workload and escalation policy within 72 hours."
|
| 59 |
return "Assign an operations owner to review the detected drift and validate root cause."
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def is_escalation(decision_or_evidence) -> bool:
|
| 63 |
+
"""Return True if the given evidence trail (or DecisionItem)
|
| 64 |
+
contains the acceleration label — i.e. this is the escalation
|
| 65 |
+
tier. Used by the dashboard to render an "escalation required"
|
| 66 |
+
badge without having to re-parse the recommendation string."""
|
| 67 |
+
if isinstance(decision_or_evidence, DecisionItem):
|
| 68 |
+
evidence = decision_or_evidence.evidence
|
| 69 |
+
else:
|
| 70 |
+
evidence = decision_or_evidence
|
| 71 |
+
if not evidence:
|
| 72 |
+
return False
|
| 73 |
+
for e in evidence:
|
| 74 |
+
if e.get("label") == "acceleration":
|
| 75 |
+
return True
|
| 76 |
+
return False
|
core/pipeline.py
CHANGED
|
@@ -304,13 +304,26 @@ def run_pipeline(observations: List[Observation],
|
|
| 304 |
evidence=evidence,
|
| 305 |
)
|
| 306 |
result.issues.append(issue)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
result.decisions.append(DecisionItem(
|
| 308 |
decision_id=f"decision_{idx}",
|
| 309 |
issue_id=issue.issue_id,
|
| 310 |
entity_id=state["entity_id"],
|
| 311 |
created_at=state["day"],
|
| 312 |
title=f"Review {state['entity_id']} drift",
|
| 313 |
-
urgency=
|
| 314 |
recommendation=recommendation_for_issue(state["entity_type"], evidence),
|
| 315 |
confidence=state["confidence"],
|
| 316 |
evidence=evidence,
|
|
|
|
| 304 |
evidence=evidence,
|
| 305 |
)
|
| 306 |
result.issues.append(issue)
|
| 307 |
+
# Stage 188 — α-flagged issues are escalation-tier. Their
|
| 308 |
+
# urgency gets boosted so they sort to the top of the queue
|
| 309 |
+
# ahead of equal-severity steady decliners. Multiplier 1.5
|
| 310 |
+
# (vs 1.25 for non-accelerating) keeps it bounded in [0,1]
|
| 311 |
+
# while making the ordering decisive. An entity with
|
| 312 |
+
# drift_score 0.6 + α evidence (urgency 0.9) outranks an
|
| 313 |
+
# entity with drift_score 0.7 + no acceleration (urgency
|
| 314 |
+
# 0.875) — which mirrors the human intuition that an
|
| 315 |
+
# accelerating decliner deserves attention before a steadier
|
| 316 |
+
# but slightly-worse one.
|
| 317 |
+
alpha_value = state["signals"].get("alpha", 0.0)
|
| 318 |
+
alpha_boost = 1.5 if alpha_value >= 0.5 else 1.25
|
| 319 |
+
urgency = round(min(1.0, state["drift_score"] * alpha_boost), 4)
|
| 320 |
result.decisions.append(DecisionItem(
|
| 321 |
decision_id=f"decision_{idx}",
|
| 322 |
issue_id=issue.issue_id,
|
| 323 |
entity_id=state["entity_id"],
|
| 324 |
created_at=state["day"],
|
| 325 |
title=f"Review {state['entity_id']} drift",
|
| 326 |
+
urgency=urgency,
|
| 327 |
recommendation=recommendation_for_issue(state["entity_type"], evidence),
|
| 328 |
confidence=state["confidence"],
|
| 329 |
evidence=evidence,
|