Files
shaguabijia-app-server/app/services/compare_alert_format.py
T

116 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""AlertHit[] → 飞书群机器人消息。
提供两个格式化函数:
- format_alert_message: 纯文本(保留,已有集成测试依赖)。
- format_alert_post: 富文本 post(行式明细:时间|手机|版本|原因|trace 超链接)。
按触发类型分组,每类给计数 + 明细(trace/版本/原因)。两级截断防报警风暴:单类型超
max_detail_per_type 只列前 N + 「另有 M 条」;本期总量超 max_total 只给各类型计数、提示去分析库查。
标题含关键词「比价失败报警」——飞书自定义机器人用关键词验证,消息必须含它,否则被拒收。
"""
from __future__ import annotations
from app.services.compare_alert import ALERT_TYPE_LABELS, AlertHit
ALERT_KEYWORD = "比价失败报警"
_TYPE_ORDER = ("T1", "T6", "T2", "T5")
def _detail_line(h: AlertHit) -> str:
ver = h.app_version or "?"
return f" - trace {h.trace_id} | {ver} | {h.reason}"
def format_alert_message(
hits: list[AlertHit],
*,
window_label: str,
max_detail_per_type: int,
max_total: int,
) -> str:
total = len(hits)
header = f"🚨 {ALERT_KEYWORD} · {window_label} · 本期触发 {total}"
grouped: dict[str, list[AlertHit]] = {t: [] for t in _TYPE_ORDER}
for h in hits:
grouped.setdefault(h.alert_type, []).append(h)
lines = [header]
counts_only = total > max_total
for t in _TYPE_ORDER:
bucket = grouped.get(t) or []
if not bucket:
continue
lines.append(f"{ALERT_TYPE_LABELS[t]} {len(bucket)}")
if counts_only:
continue
shown = bucket[:max_detail_per_type]
lines.extend(_detail_line(h) for h in shown)
if len(bucket) > max_detail_per_type:
lines.append(f" …另有 {len(bucket) - max_detail_per_type}")
if counts_only:
lines.append(f"(本期命中超 {max_total} 条,仅列计数,明细见分析库 comparison_record)")
return "\n".join(lines)
def format_alert_post(
hits: list[AlertHit],
*,
window_label: str,
phone_map: dict[int, str],
max_detail_per_type: int,
max_total: int,
) -> tuple[str, list]:
"""行式富文本:返回 (title, content)。title 含 ALERT_KEYWORD(飞书关键词验证)。
content: 摘要段(各类型计数) + 表头段 + 明细行(每条「时间|手机|版本|原因|」+ trace 超链接 a 元素)。
phone_map: {user_id: phone};明细手机号取 phone_map.get(hit.user_id) or "-"
截断规则:总命中 > max_total → 只出摘要+各类型计数(不列明细);
否则明细最多列前 max_detail_per_type 条,超出加「…另有 N 条」。
"""
total = len(hits)
title = f"🚨 {ALERT_KEYWORD} · {window_label}"
grouped: dict[str, list[AlertHit]] = {t: [] for t in _TYPE_ORDER}
for h in hits:
grouped.setdefault(h.alert_type, []).append(h)
# 摘要段:各类型计数
summary_parts = [f"{ALERT_TYPE_LABELS[t]} {len(grouped[t])}" for t in _TYPE_ORDER if grouped.get(t)]
summary_text = f"合计 {total} 条:" + " ".join(summary_parts)
content: list[list[dict]] = [
[{"tag": "text", "text": summary_text}],
]
counts_only = total > max_total
if counts_only:
content.append([{"tag": "text", "text": f"(本期命中超 {max_total} 条,仅列计数,明细见分析库 comparison_record)"}])
return title, content
# 表头段
content.append([{"tag": "text", "text": "时间 | 手机号 | 版本 | 失败原因 | trace"}])
# 明细行(按类型顺序展开,每条一段)
shown_count = 0
for t in _TYPE_ORDER:
bucket = grouped.get(t) or []
if not bucket:
continue
for h in bucket[:max_detail_per_type]:
time_str = h.created_at.strftime("%m-%d %H:%M") if h.created_at else "-"
phone = phone_map.get(h.user_id) if h.user_id is not None else None
phone = phone or "-"
ver = h.app_version or "-"
row: list[dict] = [{"tag": "text", "text": f"{time_str} {phone} {ver} {h.reason} "}]
if h.trace_url:
row.append({"tag": "a", "text": "trace", "href": h.trace_url})
else:
row.append({"tag": "text", "text": h.trace_id[:16]})
content.append(row)
shown_count += 1
if len(bucket) > max_detail_per_type:
content.append([{"tag": "text", "text": f"…另有 {len(bucket) - max_detail_per_type}"}])
return title, content