fix(calls+scraper): 语音群scope服务端过滤+对话发现缺口补全+抓取韧性三连修

- /api/calls 加 scope=friend|group(@chatroom后缀判群,与isRoom实测等价120=120),CallList去掉「全部」tab改服务端查
- 对话发现:全部customer pair ∪ 语音pair(weChatId,talker),未扫过的排最前——修复纯语音关系客户聊天记录永远空(35%语音客户pair从未扫过)
- voice-calls改浏览器直发(isRoom=0/1双趟+WA callSource=4,拦截方式已两次失效)
- 断连恢复重试30×2min(云客约3h一次断连,已实战扛过多次)
- WA messages upsert改整列表直调(_upsert_all逐条传dict被迭代成key字符串→TypeError)
- 405测试绿含scope新增3个

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-19 16:48:22 +08:00
parent f9ed7dd821
commit ab5a918a3b
5 changed files with 293 additions and 80 deletions
+11 -2
View File
@@ -9,7 +9,7 @@ Ordered by ``start_call_time desc`` (nulls last) — most recent calls first.
from __future__ import annotations
from typing import Annotated
from typing import Annotated, Literal
import sqlalchemy as sa
from fastapi import APIRouter, Depends, Query
@@ -35,13 +35,22 @@ async def list_calls(
user_id: Annotated[str | None, Query()] = None,
start: Annotated[int | None, Query(ge=0)] = None,
end: Annotated[int | None, Query(ge=0)] = None,
scope: Annotated[Literal["friend", "group"] | None, Query()] = None,
) -> Page[PhoneCallOut]:
"""List phone/voice calls with optional filters, newest first."""
"""List phone/voice calls with optional filters, newest first.
``scope`` filters voice calls by peer kind: ``group`` keeps only group
calls (``call_number`` ends ``@chatroom``), ``friend`` excludes them —
verified exactly equivalent to the CRM's ``isRoom`` flag (2026-08-18).
"""
conditions = []
if channel is not None:
conditions.append(PhoneCall.channel == channel)
if user_id is not None:
conditions.append(PhoneCall.user_id == user_id)
if scope is not None:
is_room = PhoneCall.call_number.endswith("@chatroom")
conditions.append(is_room if scope == "group" else ~is_room)
if start is not None:
conditions.append(PhoneCall.start_call_time >= start)
if end is not None: