From af2b9bc9963d1281abc2af534ca1e98b69319cac Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Fri, 3 Apr 2026 15:26:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E6=97=A5=E6=8A=A5?= =?UTF-8?q?=E6=94=B9=E5=9B=9E=E5=8D=95=E6=9D=A1=E7=B2=BE=E6=8E=92markdown?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E6=8E=89textcard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit textcard排版控制太弱,指标密集型日报挤成一坨。 改为单条markdown,分三个区块: 1. 核心数字(新增/完成/待处理/误报率) 2. 响应效率(首响/完结时长) 3. 风险分布(告警类型/区域/摄像头,仅有数据时展示) 4. 超时未处理(仅有遗留时展示) 去掉 textcard 和 news 相关代码,简化发送逻辑。 --- app/services/daily_report_service.py | 109 ++++++++++++--------------- 1 file changed, 48 insertions(+), 61 deletions(-) diff --git a/app/services/daily_report_service.py b/app/services/daily_report_service.py index edf427c..2aa9b73 100644 --- a/app/services/daily_report_service.py +++ b/app/services/daily_report_service.py @@ -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">昨日新增:{summary['yesterday_total']} 条({report['change_str']})", - f">昨日完成:{summary['completed_count']} 条 | 当前待处理:{summary['backlog_count']} 条", - f">安保:{summary['security_count']} 条 | 保洁:{summary['clean_count']} 条", - f">平均首响:{summary['avg_resp']} | 平均完结:{summary['avg_close']}", - f">误报率:{summary['false_alarm_rate']} | 遗留待处理:{summary['carry_over_count']} 条", + # ── 核心数字 ── + f">昨日新增 {s['yesterday_total']} 条({report['change_str']})", + f">安保 {s['security_count']}|" + f"保洁 {s['clean_count']}|" + f"完成 {s['completed_count']}|" + f"取消 {s['cancelled_count']}|" + f"误报率 {s['false_alarm_rate']}", + f">当前待处理 {s['backlog_count']} 条" + f"(遗留 {s['carry_over_count']})", "", - "**重点风险**", + # ── 效率指标 ── + f"**响应效率**", + f">平均首响 {s['avg_resp']}|" + f"平均完结 {s['avg_close']}", ] - 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"**超时未处理 {s['carry_over_count']} 条**") + 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"
{report['subtitle']}
", - f"
昨日新增 {summary['yesterday_total']} 条({report['change_str']})
", - f"
昨日完成 {summary['completed_count']} 条|当前待处理 {summary['backlog_count']}
", - f"
安保 {summary['security_count']} 条|保洁 {summary['clean_count']} 条
", - f"
平均首响 {summary['avg_resp']}|平均完结 {summary['avg_close']}
", - f"
误报率 {summary['false_alarm_rate']}|遗留待处理 {summary['carry_over_count']} 条
", - "
重点风险
", - f"
安保高发:{report['tops']['alarm_types']}
", - f"
高发区域:{report['tops']['areas']}
", - f"
高发摄像头:{report['tops']['cameras']}
", - ] - if report["top_overdue"]: - description_lines.append(f"
优先跟进:{report['top_overdue'][0]}
") - 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("日报生成或发送异常")