feat(groups): chat_group 补 6 群主/建群列 + 复合PK迁移修复 + 存量回填

对齐云客员工聊天窗口·微信群 tab(2026-08-25 真机验证 raw_json 键):
- ChatGroup 加列 owner_name/owner_wxid/owner_alias/owner_avatar/
  friend_count/create_time(全 nullable)
- scraper _normalize_chat_group 同名提取;createTime 兼容 ms-int 与
  'YYYY-MM-DD HH:mm:ss'(smoke.db 实测 1105 行全为后者),复用
  _parse_add_time 归一 ms-epoch
- 迁移 099ac2097fa2:加 6 列 + 修复 Task1 评审遗留 drift——PK 由单列
  room_id 变更为复合 (room_id, channel, owner_account_id) 且
  owner_account_id NOT NULL(对齐模型 8a99940;smoke.db 1105 行无 NULL,
  未兜底编造数据);SQLite 走 batch 重建(copy_from 显式旧结构),
  MySQL 渲染为直接 ALTER(离线 --sql 已验证)
- 回填脚本 scripts/backfill_chat_group_fields.py(幂等,逐列只补空);
  smoke.db 实跑 backfilled 1105/1105,重跑 0/1105
- GroupOut 加 6 字段(model_validate 自动输出,路由无改)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-25 14:32:12 +08:00
parent 90c28f4c7b
commit 6ff288f1d3
7 changed files with 342 additions and 1 deletions
@@ -32,7 +32,7 @@ from typing import Any
import httpx
import respx
from app.scraper.wechat import scrape_wechat_groups
from app.scraper.wechat import _normalize_chat_group, scrape_wechat_groups
from app.session.yunke_client import YunkeClient
from tests.cassettes.wxFriends_fixture import make_friend_list_response
@@ -277,3 +277,50 @@ async def test_scrape_wechat_groups_stops_on_empty_first_page():
assert results == []
assert route.call_count == 1
# ---------------------------------------------------------------------------
# 群主/建群补充列提取(2026-08-25 对齐云客员工聊天窗口·微信群 tab)
# raw_json 键: ownerName/ownerWchatId/ownerAlias/ownerImage/friendCount/createTime
# smoke.db 实测: createTime 全部为 'YYYY-MM-DD HH:mm:ss' 字符串;ms int 直通。
# ---------------------------------------------------------------------------
def test_normalize_chat_group_extracts_owner_fields():
"""6 个补充键提取到同名列;date-str createTime 归一为 ms epoch。"""
record = {
"friendWxId": "123@chatroom",
"friendWxNickname": "测试群",
"friendNum": 25,
"wxId": "wxid_x",
"ownerName": "Q**e",
"ownerWchatId": "RL3*****108",
"ownerAlias": "Fr******er",
"ownerImage": "https://wx.qlogo.cn/o/0",
"friendCount": 22,
"createTime": "2025-10-27 13:30:55",
}
out = _normalize_chat_group(record)
assert out["owner_name"] == "Q**e"
assert out["owner_wxid"] == "RL3*****108"
assert out["owner_alias"] == "Fr******er"
assert out["owner_avatar"] == "https://wx.qlogo.cn/o/0"
assert out["friend_count"] == 22
# 'YYYY-MM-DD HH:mm:ss' -> ms epoch(naive UTC,同 addTime 约定)
assert out["create_time"] == 1761571855000
def test_normalize_chat_group_owner_fields_none_safe():
"""缺失全部 6 个键 -> 全 None(老 capture 不炸)。"""
out = _normalize_chat_group({"friendWxId": "r@chatroom"})
for key in ("owner_name", "owner_wxid", "owner_alias",
"owner_avatar", "friend_count", "create_time"):
assert out[key] is None
def test_normalize_chat_group_create_time_ms_passthrough():
"""ms-epoch int 直通(不做日期解析)。"""
out = _normalize_chat_group(
{"friendWxId": "r@chatroom", "createTime": 1740000000000}
)
assert out["create_time"] == 1740000000000