from __future__ import annotations from app.config import settings from app.models import NotificationDecision, ProcessorForwardEnvelope class NotificationFormatter: @staticmethod def format_matrix_message( envelope: ProcessorForwardEnvelope, decision: NotificationDecision, ) -> str: e = envelope.event ctx = e.zabbix_context or {} lines = [ f"[{decision.severity or 'UNKNOWN'}] {decision.event_phase.upper()}", f"Host: {e.host or 'unknown'}", f"Service: {e.service or 'unknown'}", f"Trigger: {e.trigger_name or 'unknown'}", f"Reason: {decision.reason}", f"Fingerprint: {decision.fingerprint or 'unknown'}", f"Repeat count: {decision.repeat_count}", ] if decision.triage_applied: lines.append( f"Triage: {decision.triage_verdict or 'unknown'}" f" ({decision.triage_classification or 'unknown'}, source={decision.triage_source or 'unknown'})" ) if decision.triage_reason: lines.append(f"Triage reason: {decision.triage_reason}") if decision.correlation_applied: lines.append( f"Correlation: role={decision.correlation_role or 'unknown'}" f", kind={decision.correlation_kind or 'unknown'}" ) if decision.root_cause_candidate: lines.append( f"RCA: root cause candidate (related alerts in window: {decision.correlated_event_count})" ) if decision.parent_event_id: lines.append(f"Parent event: {decision.parent_event_id}") if decision.correlation_reason: lines.append(f"Correlation reason: {decision.correlation_reason}") if decision.recovered_from_severity: lines.append(f"Recovered from severity: {decision.recovered_from_severity}") if decision.flap_detected and decision.flap_reason: lines.append(f"Flap: {decision.flap_reason}") if decision.suppressed and decision.suppress_reason: lines.append(f"Suppress: {decision.suppress_reason}") if ctx.get("last_value"): lines.append(f"Last value: {ctx['last_value']}") if ctx.get("item_key"): lines.append(f"Item key: {ctx['item_key']}") if ctx.get("resolved_opdata"): lines.append(f"OpData: {ctx['resolved_opdata']}") if decision.remediation_summary: lines.append(f"Remediation: {decision.remediation_summary}") if decision.remediation_steps: lines.append("Steps:") for idx, step in enumerate(decision.remediation_steps, start=1): lines.append(f"{idx}. {step}") if decision.remediation_commands: lines.append("Commands:") for cmd in decision.remediation_commands: lines.append(f"- {cmd}") if e.event_url: lines.append(f"Zabbix Event: {e.event_url}") elif e.zabbix_url: lines.append(f"Zabbix Event: {e.zabbix_url}") if e.graph_url: lines.append(f"Graph: {e.graph_url}") return "\n".join(lines) @staticmethod def format_mail_subject( envelope: ProcessorForwardEnvelope, decision: NotificationDecision, ) -> str: e = envelope.event phase = decision.event_phase.upper() severity = decision.severity or e.severity or "UNKNOWN" host = e.host or "unknown-host" trigger = e.trigger_name or "unknown-trigger" return f"{settings.mail_subject_prefix} [{severity}] {phase} {host} :: {trigger}" @staticmethod def format_mail_body( envelope: ProcessorForwardEnvelope, decision: NotificationDecision, ) -> str: e = envelope.event ctx = e.zabbix_context or {} lines = [ f"Severity: {decision.severity or e.severity or 'UNKNOWN'}", f"Phase: {decision.event_phase}", f"Host: {e.host or 'unknown'}", f"Service: {e.service or 'unknown'}", f"Trigger: {e.trigger_name or 'unknown'}", f"Reason: {decision.reason}", f"Fingerprint: {decision.fingerprint or 'unknown'}", f"Repeat count: {decision.repeat_count}", f"Correlation ID: {e.correlation_id}", f"Event ID: {e.event_id or 'unknown'}", ] if decision.triage_applied: lines.append( f"Triage: {decision.triage_verdict or 'unknown'}" f" ({decision.triage_classification or 'unknown'}, source={decision.triage_source or 'unknown'})" ) if decision.triage_reason: lines.append(f"Triage reason: {decision.triage_reason}") if decision.correlation_applied: lines.append( f"Correlation: role={decision.correlation_role or 'unknown'}" f", kind={decision.correlation_kind or 'unknown'}" ) if decision.root_cause_candidate: lines.append( f"RCA: root cause candidate (related alerts in window: {decision.correlated_event_count})" ) if decision.parent_event_id: lines.append(f"Parent event: {decision.parent_event_id}") if decision.correlation_reason: lines.append(f"Correlation reason: {decision.correlation_reason}") if decision.recovered_from_severity: lines.append(f"Recovered from severity: {decision.recovered_from_severity}") if decision.flap_detected and decision.flap_reason: lines.append(f"Flap reason: {decision.flap_reason}") if decision.suppressed and decision.suppress_reason: lines.append(f"Suppress reason: {decision.suppress_reason}") if ctx.get("last_value"): lines.append(f"Last value: {ctx['last_value']}") if ctx.get("item_name"): lines.append(f"Item name: {ctx['item_name']}") if ctx.get("item_key"): lines.append(f"Item key: {ctx['item_key']}") if ctx.get("resolved_opdata"): lines.append(f"OpData: {ctx['resolved_opdata']}") if ctx.get("trigger_comments"): lines.append(f"Trigger comments: {ctx['trigger_comments']}") if decision.remediation_summary: lines.append("") lines.append(f"Remediation: {decision.remediation_summary}") if decision.remediation_steps: lines.append("Steps:") for idx, step in enumerate(decision.remediation_steps, start=1): lines.append(f"{idx}. {step}") if decision.remediation_commands: lines.append("Commands:") for cmd in decision.remediation_commands: lines.append(f"- {cmd}") if decision.correlation_applied: lines.append( f"Correlation: role={decision.correlation_role or 'unknown'}" f", kind={decision.correlation_kind or 'unknown'}" f", source={decision.correlation_source or 'unknown'}" f", confidence={decision.correlation_confidence or 'unknown'}" ) if e.event_url: lines.append(f"Zabbix Event URL: {e.event_url}") elif e.zabbix_url: lines.append(f"Zabbix Event URL: {e.zabbix_url}") if e.graph_url: lines.append(f"Graph URL: {e.graph_url}") return "\n".join(lines)