feat: 接入企微智能机器人——异常报警/数据核验日报/设备健康日报

- flows/shared/wecom.py: WSClient 按需连接(连接→认证→发送→断开),全容错,超长分段
- flows/monitor/alert_failure.py: Prefect 事件触发(Failed/Crashed→run deployment),发群报警
- flows/monitor/daily_report.py: 每日 18:30 近7天 RIIN/SDS 核验(复用 verify)+商品完整性+OCR+失败回顾
- flows/monitor/device_check.py: 每日 09:00 内存/磁盘/GPU(80%阈值)+Docker 容器异常+服务探活
- db.py: 新增 get_goods_integrity/ping; compose worker 挂载 docker.sock+/:/host:ro+GPU
- scripts/wecom_capture_chatid.py: 一次性捕获群 chat_id 工具
This commit is contained in:
yeuimu
2026-08-27 09:40:53 +00:00
parent b9e0bc42be
commit 1d0da2a00b
12 changed files with 836 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
"""数据核验日报(每日 18:30):近 N 天 RIIN/SDS 订单+商品数据准确性核验并上报企微群。
对应 daily-report deployment,cron 30 18 * * *。内容:
1. 复用 riin-verify / sds-verify 逻辑(subflow 调用):远程 vs 本地逐天逐状态计数比对,
有差异自动拉取修复——与早 6/7 点的每日核验互补,二次确认白天同步质量。
2. 商品数据完整性:窗口内零商品订单 / 商品无名称 / 全库孤儿商品(本地 SQL)。
3. OCR 处理状态与当日失败 flow runs 回顾(事件报警的兜底汇总)。
"""
from __future__ import annotations
from datetime import datetime, timedelta
from prefect import flow
from flows.monitor.device_check import check_prefect_failures
from flows.riin.verify import riin_verify
from flows.sds.verify import sds_verify
from flows.shared import SyncDB, emit_sync_summary, send_markdown, start_of_day
def _verify_section(name: str, report: dict) -> tuple[str, list[str], bool]:
"""把 verify flow 返回的 verifyReport 压缩成结论行 + 差异日明细行。"""
days = (report.get("verifyReport") or {}).get("days") or []
summary = (report.get("verifyReport") or {}).get("summary") or {}
diff_days = [d for d in days if not d.get("ok")]
total, n_diff = summary.get("totalDays", len(days)), summary.get("daysWithDiff", len(diff_days))
head = f"**{name}**: {total} 天中 {total - n_diff} 天一致" + (" ✅" if not diff_days else f", {n_diff} 天有差异已修复")
detail = [
f"- {d['date']}: 本地 {d.get('local', '—')} / 远程 {d.get('remote', '—')},"
f"差异 {d.get('diff') or '—'},已补拉 订单 {d.get('orders', 0)} / 商品 {d.get('goods', 0)}"
for d in diff_days
]
totals = {k: v for k, v in summary.items() if k not in ("totalDays", "daysWithDiff")}
if any(totals.values()):
detail.append(f"- 修复量合计: {totals}")
return head, detail, bool(diff_days)
@flow(name="daily-report", log_prints=True)
def daily_report(days: int = 7) -> dict:
"""近 N 天数据核验 + 商品完整性 + OCR/失败回顾,发企微群日报。"""
today = start_of_day(datetime.now())
window_start = today - timedelta(days=days)
window_end = today # 与 verify 的 recent 约定一致:近 N 天、不含今天
riin = riin_verify(mode="recent", recent_days=days)
sds = sds_verify(mode="recent", recent_days=days)
db = SyncDB()
goods = {sys: db.get_goods_integrity(sys, window_start, window_end) for sys in ("RIIN", "SDS")}
ocr = db.count_ocr_status()
failures = check_prefect_failures(hours=24)
lines = [f"📊 **数据核验日报** {datetime.now().strftime('%Y-%m-%d %H:%M')}"
f"(近 {days} 天,{window_start:%m-%d} ~ {window_end:%m-%d})", ""]
attention = False
for name, report in (("RIIN", riin), ("SDS", sds)):
try:
head, detail, had_diff = _verify_section(name, report)
lines.append(head)
lines.extend(detail)
attention = attention or had_diff
except Exception as err: # noqa: BLE001
lines.append(f"**{name}**: 核验结果解析失败: {err}")
attention = True
lines.append("**商品完整性**:")
lines.append("| 系统 | 订单 | 商品行 | 零商品订单 | 商品无名称 | 孤儿商品(全库) |")
lines.append("| --- | --- | --- | --- | --- | --- |")
for sys in ("RIIN", "SDS"):
g = goods[sys]
flag = " ⚠️" if g["zeroGoodsOrders"] > 0 else ""
lines.append(f"| {sys} | {g['orders']} | {g['goods']} | {g['zeroGoodsOrders']}{flag} "
f"| {g['goodsNoName']} | {g['orphanGoods']} |")
if g["zeroGoodsOrders"] > 0:
attention = True
lines.append(f"**OCR(SDS)**: 完成 {ocr.get('done', 0)} / 失败 {ocr.get('error', 0)} / "
f"不支持 {ocr.get('unsupported', 0)} / 待处理 {ocr.get('pending', 0)}")
lines.append(f"**近24h 失败 flow runs**: {len(failures)} 个" + ("" if failures else " ✅"))
for f in failures[:8]:
lines.append(f"- 🔴 {f['name']}({f['state']}) {f['start']} {f['message'][:60]}")
if len(failures) > 8:
lines.append(f"- ...共 {len(failures)} 个,详见 Prefect UI")
if failures:
attention = True
lines += ["", ("⚠️ **存在需关注项,见上**" if attention else "✅ **数据全部一致,无异常**")]
sent = send_markdown("\n".join(lines))
emit_sync_summary("数据核验日报", [
("核验窗口", f"近{days}天"), ("需关注", "是" if attention else "否"),
("RIIN 零商品订单", goods["RIIN"]["zeroGoodsOrders"]),
("SDS 零商品订单", goods["SDS"]["zeroGoodsOrders"]),
("OCR 待处理", ocr.get("pending", 0)), ("失败 runs(24h)", len(failures)),
("已发企微", "是" if sent else "否"),
])
return {"attention": attention, "sent": sent,
"goods": goods, "ocr": ocr, "failures24h": len(failures)}
if __name__ == "__main__":
daily_report.serve(name="daily-report", cron="30 18 * * *")