fix(alerts): 補齊處置結果與人工通知契約
This commit is contained in:
@@ -39,6 +39,7 @@ from opentelemetry import trace
|
||||
|
||||
from src.core.config import settings
|
||||
from src.core.redis_client import get_redis
|
||||
from src.services.operator_outcome import build_operator_outcome
|
||||
from src.services.security_interceptor import (
|
||||
NonceReplayError,
|
||||
UserNotWhitelistedError,
|
||||
@@ -169,6 +170,42 @@ def _format_automation_quality_lines(quality: dict[str, object] | None) -> list[
|
||||
return lines
|
||||
|
||||
|
||||
def _operator_outcome_from_blocks(
|
||||
*,
|
||||
truth_status: dict[str, object] | None,
|
||||
quality: dict[str, object] | None,
|
||||
remediation_state: str | None = None,
|
||||
source_id: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
if isinstance(quality, dict) and isinstance(quality.get("operator_outcome"), dict):
|
||||
return dict(quality["operator_outcome"])
|
||||
return build_operator_outcome(
|
||||
truth_status=truth_status if isinstance(truth_status, dict) else {},
|
||||
automation_quality=quality if isinstance(quality, dict) else {},
|
||||
remediation_state=remediation_state,
|
||||
source_id=source_id,
|
||||
)
|
||||
|
||||
|
||||
def _format_operator_outcome_lines(outcome: dict[str, object] | None) -> list[str]:
|
||||
if not outcome:
|
||||
return []
|
||||
notification = (
|
||||
outcome.get("notification")
|
||||
if isinstance(outcome.get("notification"), dict)
|
||||
else {}
|
||||
)
|
||||
channels = notification.get("channels") if isinstance(notification.get("channels"), list) else []
|
||||
return [
|
||||
"👤 <b>處置結論</b>",
|
||||
f"├ 結果:<b>{html.escape(str(outcome.get('summary_zh') or '尚未形成明確結論'))}</b>",
|
||||
f"├ 狀態:<code>{html.escape(str(outcome.get('state') or 'unknown'))}</code>",
|
||||
f"├ 人工:<code>{'yes' if outcome.get('needs_human') else 'no'}</code> | "
|
||||
f"通知:<code>{html.escape(','.join(str(item) for item in channels) or 'none')}</code>",
|
||||
f"└ 下一步:<code>{html.escape(str(outcome.get('next_action') or '--'))}</code>",
|
||||
]
|
||||
|
||||
|
||||
def _format_remediation_history_lines(history: dict[str, object] | None) -> list[str]:
|
||||
if not history or int(history.get("total") or 0) <= 0:
|
||||
return []
|
||||
@@ -413,6 +450,14 @@ def _format_awooop_status_chain_lines(
|
||||
and repair_state != "auto_repaired_verified"
|
||||
):
|
||||
needs_human = True
|
||||
outcome = _operator_outcome_from_blocks(
|
||||
truth_status=truth_status,
|
||||
quality=quality,
|
||||
remediation_state=remediation_state,
|
||||
)
|
||||
if outcome:
|
||||
needs_human = bool(needs_human or outcome.get("needs_human"))
|
||||
next_step = str(outcome.get("next_action") or next_step)
|
||||
|
||||
lines = [
|
||||
"",
|
||||
@@ -450,6 +495,9 @@ def _format_awooop_status_chain_lines(
|
||||
),
|
||||
f"下一步: <code>{html.escape(next_step)}</code>",
|
||||
]
|
||||
outcome_lines = _format_operator_outcome_lines(outcome)
|
||||
if outcome_lines:
|
||||
lines.extend(["", *outcome_lines])
|
||||
|
||||
blockers = [str(item) for item in [*truth_blockers, *quality_blockers] if item]
|
||||
if blockers:
|
||||
@@ -1485,6 +1533,15 @@ def _callback_reply_awooop_status_chain_snapshot(
|
||||
and repair_state != "auto_repaired_verified"
|
||||
):
|
||||
needs_human = True
|
||||
outcome = _operator_outcome_from_blocks(
|
||||
truth_status=truth_status,
|
||||
quality=quality,
|
||||
remediation_state=remediation_state,
|
||||
source_id=incident_id,
|
||||
)
|
||||
if outcome:
|
||||
needs_human = bool(needs_human or outcome.get("needs_human"))
|
||||
next_step = str(outcome.get("next_action") or next_step)
|
||||
|
||||
truth_blockers = (
|
||||
truth_status.get("blockers") if isinstance(truth_status.get("blockers"), list) else []
|
||||
@@ -1511,6 +1568,7 @@ def _callback_reply_awooop_status_chain_snapshot(
|
||||
"verification": str(verification),
|
||||
"needs_human": needs_human,
|
||||
"next_step": next_step,
|
||||
"operator_outcome": outcome,
|
||||
"blockers": blockers[:8],
|
||||
"evidence": {
|
||||
"auto_repair_records": auto_repair_records,
|
||||
@@ -1782,6 +1840,13 @@ class TelegramMessage:
|
||||
text = f"{self.root_cause} {self.suggested_action}".lower()
|
||||
state = (self.automation_state or "").lower()
|
||||
quality = self.automation_quality or {}
|
||||
outcome = (
|
||||
quality.get("operator_outcome")
|
||||
if isinstance(quality.get("operator_outcome"), dict)
|
||||
else None
|
||||
)
|
||||
if outcome and outcome.get("summary_zh"):
|
||||
return str(outcome["summary_zh"])
|
||||
facts = quality.get("facts") if isinstance(quality.get("facts"), dict) else {}
|
||||
verdict = str(quality.get("verdict") or "")
|
||||
has_repair_execution = _has_repair_execution_evidence(facts)
|
||||
@@ -1828,6 +1893,25 @@ class TelegramMessage:
|
||||
return "🟡 AI 已提出修復建議,等待人工批准"
|
||||
return "🟡 安全閘門待審批"
|
||||
|
||||
def _operator_outcome(self) -> dict[str, object] | None:
|
||||
quality = self.automation_quality if isinstance(self.automation_quality, dict) else {}
|
||||
if isinstance(quality.get("operator_outcome"), dict):
|
||||
return dict(quality["operator_outcome"])
|
||||
if not quality:
|
||||
return None
|
||||
return build_operator_outcome(
|
||||
truth_status=None,
|
||||
automation_quality=quality,
|
||||
remediation_state=_remediation_evidence_state(self.remediation_summary),
|
||||
source_id=self.incident_id or self.approval_id,
|
||||
)
|
||||
|
||||
def _format_operator_outcome_block(self) -> str:
|
||||
lines = _format_operator_outcome_lines(self._operator_outcome())
|
||||
if not lines:
|
||||
return ""
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def _format_automation_block(self) -> str:
|
||||
"""Visible AI automation chain for every ACTION REQUIRED card.
|
||||
2026-05-04 ogt: 加入 Token 用量 + 具體 Ollama 伺服器顯示
|
||||
@@ -2080,6 +2164,7 @@ class TelegramMessage:
|
||||
playbook_line = f"📖 Playbook:<code>{html.escape(self.playbook_name)}</code>\n"
|
||||
remediation_evidence_block = self._format_remediation_evidence_block()
|
||||
flow_progress_block = self._format_flow_progress_block()
|
||||
operator_outcome_block = self._format_operator_outcome_block()
|
||||
automation_block = self._format_automation_block()
|
||||
|
||||
# ADR-075 TYPE-3 格式組裝
|
||||
@@ -2092,6 +2177,7 @@ class TelegramMessage:
|
||||
f"🧭 處置狀態:<b>{safe_automation_summary}</b>\n"
|
||||
f"{remediation_evidence_block}\n"
|
||||
f"{flow_progress_block}\n"
|
||||
f"{operator_outcome_block}"
|
||||
f"{automation_block}"
|
||||
f"\n"
|
||||
f"🧠 <b>AI 深度診斷</b>\n"
|
||||
|
||||
Reference in New Issue
Block a user