优化:日报改回单条精排markdown,去掉textcard
textcard排版控制太弱,指标密集型日报挤成一坨。 改为单条markdown,分三个区块: 1. 核心数字(新增/完成/待处理/误报率) 2. 响应效率(首响/完结时长) 3. 风险分布(告警类型/区域/摄像头,仅有数据时展示) 4. 超时未处理(仅有遗留时展示) 去掉 textcard 和 news 相关代码,简化发送逻辑。
This commit is contained in:
@@ -287,74 +287,71 @@ async def _build_daily_report_data() -> Optional[Dict]:
|
||||
return report
|
||||
|
||||
|
||||
def _build_preview_text(report: Dict) -> str:
|
||||
def _build_markdown(report: Dict) -> str:
|
||||
"""构建单条企微 markdown 日报"""
|
||||
if report.get("empty"):
|
||||
return (
|
||||
f"**{report['title']}**\n\n"
|
||||
f">昨日新增:0 条\n"
|
||||
f">当前待处理:0 条\n"
|
||||
f">系统运行平稳"
|
||||
f">系统运行平稳,昨日无新增工单\n"
|
||||
f">当前无待处理工单"
|
||||
)
|
||||
|
||||
summary = report["summary"]
|
||||
s = report["summary"]
|
||||
|
||||
lines = [
|
||||
f"**{report['title']}**",
|
||||
"",
|
||||
f">昨日新增:<font color=\"warning\">{summary['yesterday_total']}</font> 条({report['change_str']})",
|
||||
f">昨日完成:{summary['completed_count']} 条 | 当前待处理:<font color=\"warning\">{summary['backlog_count']}</font> 条",
|
||||
f">安保:{summary['security_count']} 条 | 保洁:{summary['clean_count']} 条",
|
||||
f">平均首响:<font color=\"info\">{summary['avg_resp']}</font> | 平均完结:<font color=\"info\">{summary['avg_close']}</font>",
|
||||
f">误报率:{summary['false_alarm_rate']} | 遗留待处理:{summary['carry_over_count']} 条",
|
||||
# ── 核心数字 ──
|
||||
f">昨日新增 <font color=\"warning\">{s['yesterday_total']}</font> 条({report['change_str']})",
|
||||
f">安保 <font color=\"warning\">{s['security_count']}</font>|"
|
||||
f"保洁 <font color=\"warning\">{s['clean_count']}</font>|"
|
||||
f"完成 {s['completed_count']}|"
|
||||
f"取消 {s['cancelled_count']}|"
|
||||
f"误报率 {s['false_alarm_rate']}",
|
||||
f">当前待处理 <font color=\"warning\">{s['backlog_count']}</font> 条"
|
||||
f"(遗留 {s['carry_over_count']})",
|
||||
"",
|
||||
"**重点风险**",
|
||||
# ── 效率指标 ──
|
||||
f"**响应效率**",
|
||||
f">平均首响 <font color=\"info\">{s['avg_resp']}</font>|"
|
||||
f"平均完结 <font color=\"info\">{s['avg_close']}</font>",
|
||||
]
|
||||
lines.extend(f">{line}" for line in report["risk_lines"])
|
||||
|
||||
# ── 风险分布 ──
|
||||
tops = report["tops"]
|
||||
has_risk = any(v != "暂无数据" for v in tops.values())
|
||||
if has_risk:
|
||||
lines.append("")
|
||||
lines.append("**风险分布**")
|
||||
if tops["alarm_types"] != "暂无数据":
|
||||
lines.append(f">告警类型:{tops['alarm_types']}")
|
||||
if tops["cleaning_types"] != "暂无数据":
|
||||
lines.append(f">保洁类型:{tops['cleaning_types']}")
|
||||
if tops["areas"] != "暂无数据":
|
||||
lines.append(f">高发区域:{tops['areas']}")
|
||||
if tops["cameras"] != "暂无数据":
|
||||
lines.append(f">高发摄像头:{tops['cameras']}")
|
||||
|
||||
# ── 超时跟进 ──
|
||||
if report["top_overdue"]:
|
||||
lines.append("")
|
||||
lines.append("**需优先跟进**")
|
||||
lines.extend(f">{idx}. {item}" for idx, item in enumerate(report["top_overdue"], start=1))
|
||||
lines.append(f"**超时未处理 <font color=\"warning\">{s['carry_over_count']}</font> 条**")
|
||||
for idx, item in enumerate(report["top_overdue"], 1):
|
||||
lines.append(f">{idx}. {item}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _build_report_textcard(report: Dict) -> Dict:
|
||||
summary = report["summary"]
|
||||
click_url = settings.wechat.service_base_url or "https://work.weixin.qq.com"
|
||||
description_lines = [
|
||||
f"<div class=\"gray\">{report['subtitle']}</div>",
|
||||
f"<div class=\"normal\">昨日新增 <font color=\"warning\">{summary['yesterday_total']}</font> 条({report['change_str']})</div>",
|
||||
f"<div class=\"normal\">昨日完成 {summary['completed_count']} 条|当前待处理 <font color=\"warning\">{summary['backlog_count']}</font> 条</div>",
|
||||
f"<div class=\"normal\">安保 {summary['security_count']} 条|保洁 {summary['clean_count']} 条</div>",
|
||||
f"<div class=\"normal\">平均首响 <font color=\"info\">{summary['avg_resp']}</font>|平均完结 <font color=\"info\">{summary['avg_close']}</font></div>",
|
||||
f"<div class=\"normal\">误报率 {summary['false_alarm_rate']}|遗留待处理 {summary['carry_over_count']} 条</div>",
|
||||
"<div class=\"gray\">重点风险</div>",
|
||||
f"<div class=\"normal\">安保高发:{report['tops']['alarm_types']}</div>",
|
||||
f"<div class=\"normal\">高发区域:{report['tops']['areas']}</div>",
|
||||
f"<div class=\"normal\">高发摄像头:{report['tops']['cameras']}</div>",
|
||||
]
|
||||
if report["top_overdue"]:
|
||||
description_lines.append(f"<div class=\"normal\">优先跟进:{report['top_overdue'][0]}</div>")
|
||||
return {
|
||||
"title": report["title"],
|
||||
"description": "".join(description_lines),
|
||||
"url": click_url,
|
||||
}
|
||||
|
||||
|
||||
async def generate_daily_report() -> Optional[str]:
|
||||
"""生成日报 markdown 内容(供预览和发送)"""
|
||||
report = await _build_daily_report_data()
|
||||
if not report:
|
||||
return None
|
||||
return _build_preview_text(report)
|
||||
|
||||
|
||||
async def generate_daily_report_textcard() -> Optional[Dict]:
|
||||
report = await _build_daily_report_data()
|
||||
if not report:
|
||||
return None
|
||||
return _build_report_textcard(report)
|
||||
return _build_markdown(report)
|
||||
|
||||
|
||||
async def _send_daily_report():
|
||||
"""生成并发送单条 markdown 日报到企微群聊"""
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
|
||||
chat_id = settings.wechat.group_chat_id
|
||||
@@ -363,27 +360,17 @@ async def _send_daily_report():
|
||||
return
|
||||
|
||||
try:
|
||||
card = await generate_daily_report_textcard()
|
||||
preview = await generate_daily_report()
|
||||
if not card or not preview:
|
||||
content = await generate_daily_report()
|
||||
if not content:
|
||||
logger.info("日报生成内容为空,跳过发送")
|
||||
return
|
||||
|
||||
wechat_svc = get_wechat_service()
|
||||
ok = await wechat_svc.send_group_textcard(
|
||||
chat_id=chat_id,
|
||||
title=card["title"],
|
||||
description=card["description"],
|
||||
url=card["url"],
|
||||
)
|
||||
ok = await wechat_svc.send_group_markdown(chat_id, content)
|
||||
if ok:
|
||||
logger.info("日报文本卡片已发送到企微群聊")
|
||||
logger.info("日报已发送到企微群聊")
|
||||
else:
|
||||
fallback_ok = await wechat_svc.send_group_markdown(chat_id, preview)
|
||||
if fallback_ok:
|
||||
logger.info("日报文本卡片发送失败,已降级为markdown发送")
|
||||
else:
|
||||
logger.error("日报发送失败")
|
||||
logger.error("日报发送失败")
|
||||
except Exception:
|
||||
logger.exception("日报生成或发送异常")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user