"""Feishu notification helper for Alibaba Cloud billing alerts.""" from __future__ import annotations import json from typing import Dict, List import requests class FeishuNotifier: """Format analysis results into an interactive Feishu card.""" def __init__(self, webhook_url: str, environment_name: str) -> None: self.webhook_url = webhook_url self.environment_name = environment_name def send(self, analysis_result: Dict, dry_run: bool = False) -> bool: card = self._build_card(analysis_result) if dry_run: print("=== Feishu notification (dry-run) ===") print(json.dumps(card, indent=2, ensure_ascii=False)) return True try: response = requests.post( self.webhook_url, json=card, headers={"Content-Type": "application/json"}, timeout=10, ) response.raise_for_status() payload = {} try: payload = response.json() except Exception: # pragma: no cover - best effort logging payload = {} if isinstance(payload, dict) and payload.get("code", 0) != 0: print(f"Feishu API returned error: {payload}") return False return True except Exception as exc: # pragma: no cover - external request print(f"Failed to send Feishu notification: {exc}") return False def _build_card(self, result: Dict) -> Dict: target_date = result.get("target_date") total_cost = result.get("total_cost", 0.0) currency = result.get("currency", "CNY") total_anomalies = result.get("total_anomalies", 0) header_template = "green" if total_anomalies == 0 else "red" header_title = f"Alibaba Cloud | {self.environment_name} | {target_date}" summary_lines = [ f"Total Cost: {total_cost:,.0f} {currency}", f"Alerts: {total_anomalies}", ] account_table = self._build_top_accounts_table(result) service_table = self._build_top_services_table(result) anomaly_blocks: List[Dict] = [] for finding in result.get("findings", []): account_line = ( f"{finding['account_name']} ({finding['account_id']}): " f"{finding['total_cost']:,.0f} {currency}" ) detail_lines: List[str] = [] account_anomaly = finding.get("account_anomaly") if account_anomaly: detail_lines.append( f"Account anomaly: {account_anomaly['severity']} " f"({account_anomaly['ratio']:.2f}x baseline {account_anomaly['baseline']:.2f})" ) for service in finding.get("service_anomalies", []): detail_lines.append( f"Service {service['service']}: {service['severity']} " f"{service['cost']:.2f} ({service['ratio']:.2f}x)" ) for new_service in finding.get("new_services", []): detail_lines.append( f"New service {new_service['service']}: {new_service['cost']:.2f}" ) if detail_lines: anomaly_blocks.append( { "tag": "div", "text": { "tag": "lark_md", "content": "**" + account_line + "**\n" + "\n".join(detail_lines), }, } ) elements: List[Dict] = [ { "tag": "div", "text": {"tag": "lark_md", "content": "\n".join(summary_lines)}, } ] if anomaly_blocks: elements.extend(anomaly_blocks) else: elements.append( { "tag": "div", "text": {"tag": "plain_text", "content": "No anomalies detected."}, } ) if account_table: elements.append({"tag": "hr"}) elements.append({"tag": "div", "text": {"tag": "lark_md", "content": "**Top Accounts by Cost**"}}) elements.append(account_table) if service_table: elements.append({"tag": "hr"}) elements.append({"tag": "div", "text": {"tag": "lark_md", "content": "**Top Services by Cost**"}}) elements.append(service_table) return { "msg_type": "interactive", "card": { "schema": "2.0", "config": {"wide_screen_mode": True}, "header": { "template": header_template, "title": {"tag": "plain_text", "content": header_title}, }, "body": { "elements": elements, }, }, } def _build_top_accounts_table(self, result: Dict) -> Dict: accounts = result.get("top_accounts") or [] if not accounts: return {} currency_symbol = self._currency_symbol(result.get("currency", "USD")) total_cost = result.get("total_cost", 0.0) rows = [] for entry in accounts[:5]: cost_value = float(entry.get("total_cost", 0.0)) percentage = ( f"{(cost_value / total_cost * 100):.1f}%" if total_cost > 0 else "0%" ) rows.append( { "account_name": str(entry.get("account_name") or entry.get("account_id") or "-"), "account_id": str(entry.get("account_id") or "-"), "cost": f"{currency_symbol}{cost_value:,.0f}", "percentage": percentage, } ) return { "tag": "table", "page_size": len(rows), "row_height": "auto", "columns": [ { "name": "account_name", "display_name": "Account Name", "data_type": "text", "horizontal_align": "left", }, { "name": "account_id", "display_name": "Account ID", "data_type": "text", "horizontal_align": "left", }, { "name": "cost", "display_name": "Cost", "data_type": "text", "horizontal_align": "right", }, { "name": "percentage", "display_name": "Percent", "data_type": "text", "horizontal_align": "right", }, ], "rows": rows, } def _build_top_services_table(self, result: Dict) -> Dict: services = result.get("top_services") or [] if not services: return {} currency_symbol = self._currency_symbol(result.get("currency", "USD")) total_cost = result.get("total_cost", 0.0) rows = [] for entry in services[:5]: cost_value = float(entry.get("total_cost", 0.0)) percentage = ( f"{(cost_value / total_cost * 100):.1f}%" if total_cost > 0 else "0%" ) rows.append( { "service": str(entry.get("service") or "-"), "cost": f"{currency_symbol}{cost_value:,.0f}", "percentage": percentage, } ) return { "tag": "table", "page_size": len(rows), "row_height": "auto", "columns": [ { "name": "service", "display_name": "Service", "data_type": "text", "horizontal_align": "left", }, { "name": "cost", "display_name": "Cost", "data_type": "text", "horizontal_align": "right", }, { "name": "percentage", "display_name": "Percent", "data_type": "text", "horizontal_align": "right", }, ], "rows": rows, } @staticmethod def _currency_symbol(code: str) -> str: mapping = { "USD": "$", "CNY": "¥", "EUR": "€", } return mapping.get((code or "").upper(), "")