feat(analytics): 看板v2全平台口径——默认双渠道合计/拆分小字/双线趋势/跳转带query预选

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-07 12:20:18 +08:00
parent 9f2dff08d9
commit 1aadc80d6b
8 changed files with 395 additions and 117 deletions
@@ -344,3 +344,84 @@ async def test_action_counts(make_client, monkeypatch):
"sensitive_pending": 1}
await make_client(seed)
async def test_overview_all_channel(make_client, monkeypatch):
"""看板 v2 全平台口径(channel=None):卡片=两渠道合计、by_channel 拆分、
trend=合计、trend_by_channel 双序列(仅 all 时出现;单渠道不加键)。"""
monkeypatch.setattr(metrics, "today_key", lambda: _TODAY)
async def seed(session):
await _seed(session) # wechat 当日:t1 friend 15 + r1 room 7 = 22
# whatsapp 当日:w1 friend 6(全史首现=今日,首沟通落窗)
session.add(MsgDailyStats(channel="whatsapp", owner_account_id="wa1",
peer_kind="friend", peer_id="w1", day=20260904,
send_cnt=4, recv_cnt=2, last_ts=1788500000000,
last_is_send=True))
await session.commit()
out = await metrics.overview(session, channel=None, scope="today")
assert out["channel"] == "all"
# 合计卡片(peer 跨渠道不碰撞:活跃客户/首沟通直接相加)
assert out["cards"]["msg_total"] == 28 # 22 + 6
assert out["cards"]["active_customers"] == 2 # t1 + w1
assert out["cards"]["first_contact"] == 2
# 拆分小字数据源
assert out["by_channel"]["wechat"] == {
"msg_total": 22, "active_customers": 1, "first_contact": 1}
assert out["by_channel"]["whatsapp"] == {
"msg_total": 6, "active_customers": 1, "first_contact": 1}
# 趋势:合计单系列保持可用 + 双渠道序列(30 天窗末日有值)
assert len(out["trend"]) == 30
assert out["trend"][-1]["msg"] == 28
assert out["trend"][-1]["active_customers"] == 2
tbc = out["trend_by_channel"]
assert len(tbc["wechat"]) == 30 and len(tbc["whatsapp"]) == 30
assert tbc["wechat"][-1]["msg"] == 22
assert tbc["whatsapp"][-1]["msg"] == 6
# 通话量无渠道语义不拆:渠道序列 call_cnt 恒 0,总量走 trend
assert tbc["wechat"][-1]["call_cnt"] == 0
assert out["trend"][-1]["call_cnt"] == 1
# 单渠道:不加 by_channel/trend_by_channel 键(既有行为不破坏)
single = await metrics.overview(session, channel="wechat", scope="today")
assert single["cards"]["msg_total"] == 22
assert "by_channel" not in single
assert "trend_by_channel" not in single
await make_client(seed)
async def test_action_counts_all(make_client, monkeypatch):
"""all 口径行动计数:两渠道各一条未回 → unreplied_2h=2;sensitive_pending
同样跨渠道合计(只计 pending)。"""
monkeypatch.setattr(metrics, "_now_ms", lambda: 1756950000000)
async def seed(session):
now = 1756950000000
h3 = 3 * 3600 * 1000
for i, (ch, owner, peer) in enumerate(
[("wechat", "wx1", "t1"), ("whatsapp", "wa1", "w1")]
):
session.add(MsgDailyStats(channel=ch, owner_account_id=owner,
peer_kind="friend", peer_id=peer, day=20260904,
send_cnt=5, recv_cnt=3, last_ts=now - h3,
last_is_send=False))
session.add(Message(msg_id=f"m{i}", channel=ch, owner_account_id=owner,
talker_id=peer, timestamp=now - h3, is_send=False,
type="text", text="在吗",
fetched_at=dt.datetime(2026, 9, 4, 12, 0)))
w = SensitiveWord(word="测试词")
session.add(w)
await session.flush()
session.add(SensitiveHit(word_id=w.id, msg_id="s1", channel="wechat",
disposition="pending"))
session.add(SensitiveHit(word_id=w.id, msg_id="s2", channel="whatsapp",
disposition="pending"))
session.add(SensitiveHit(word_id=w.id, msg_id="s3", channel="whatsapp",
disposition="confirmed"))
await session.commit()
out = await metrics.action_counts(session, channel=None)
assert out == {"unreplied_2h": 2, "churn_real_30d": 0,
"sensitive_pending": 2}
await make_client(seed)