# 交付通知未送达诊断（2026-07-09 职业卫生+舜珙血压计）

## 现象
Doro说"有几份合同没收到交付通知"。任务交付目录有文件，tracker状态=delivered。

## 根因
今日凌晨01:27-02:15企微WebSocket连接中断（errcode 846609: aibot websocket not subscribed）。
Workflow的final_review步骤内部调用`_send_wecom(extra, 'doro', msg)`发送通知，但此时WS已断，通知静默失败。

## 时间线
- 01:27:23 — 首次846609错误
- 01:28:01 — 职业卫生合同thread end（final_review完成，通知发送失败）
- 01:30:41 — WebSocket closed (attempt 6)
- 02:12:29 — 舜珙血压计thread end（final_review完成，通知发送失败）
- 02:15:50 — WebSocket closed (attempt 7)
- 02:18:30 — Doro发"hi"，WS恢复

## 诊断命令
```bash
# 1. 找tracker中delivered状态的合同
python3 -c "import json; t=json.load(open('~/.hermes/data/contract-tracker.json')); [print(c['delivered_filename']) for c in t['contracts'] if c.get('status')=='delivered']"

# 2. 查gateway.log确认WS断连时段
grep '846609\|WebSocket error\|WebSocket closed' ~/.hermes/logs/gateway.log | grep '2026-07-09'

# 3. 确认thread完成了final_review
uwf step list <thread_id>  # 看是否有final_revi步骤且有时长

# 4. 确认文件在任务交付目录
sudo docker exec nextcloud-nextcloud-1 stat "/var/www/html/data/doro/files/Doro合同审查任务/任务交付/【修】XXX.docx"
```

## 补发方法
```bash
python3 ~/.hermes/scripts/wecom_dm.py --to doro --text "合同审查完成通知（补发）:

1️⃣ 合同名称
顾问单位: XXX
修订: N处插入、M处删除
主要修订: ...

（此通知因XX时段企微WebSocket连接中断未能实时送达，现补发）"
```

## 修订内容提取方法（用于补发摘要）
```python
import zipfile
from lxml import etree
W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'

with zipfile.ZipFile(path) as z:
    xml = z.read('word/document.xml')
root = etree.fromstring(xml)

# Count WB modifications
wb_ins = [ins for ins in root.findall(f'.//{{{W}}}ins') if ins.get(f'{{{W}}}author') == 'WB']
wb_del = [d for d in root.findall(f'.//{{{W}}}del') if d.get(f'{{{W}}}author') == 'WB']
print(f"WB修订: {len(wb_ins)} ins, {len(wb_del)} del")

# Get INS text summaries
for ins in wb_ins[:10]:
    texts = [t.text for t in ins.iter(f'{{{W}}}t') if t.text]
    text = ''.join(texts).strip()
    if text and len(text) > 2:
        print(f"  + {text[:80]}")
```

## 预防措施
- auto_notify_watchdog cron每5分钟检查WS连接
- gateway WS重连机制（attempt 6-8自动重连）
- 但final_review内的`_send_wecom`调用没有重试机制——WS断了就直接失败
- **待改进**：final_review的通知步骤应增加重试逻辑或失败后写入pending_notifications队列
