02e100849b
Co-Authored-By: Claude Code <noreply@anthropic.com>
121 lines
6.0 KiB
Python
121 lines
6.0 KiB
Python
"""A1 总览指标:卡片/首沟通口径/环比(仅周月)/异常警示。"""
|
|
from __future__ import annotations
|
|
|
|
from app.analytics import metrics
|
|
from app.db.models import (
|
|
MsgDailyStats,
|
|
PhoneCall,
|
|
StatDailyStaff,
|
|
WechatAccount,
|
|
)
|
|
|
|
_TODAY = 20260904 # 测试统一用固定 day 键,monkeypatch today_key
|
|
|
|
|
|
async def _seed(session):
|
|
session.add(WechatAccount(wx_id="wx1", user_id="u1", user_name="张三"))
|
|
# 昨日(20260903):u1 90 条;前 7 日均值(20260827-0902):每天 200 → 偏离 -55%>50% 触发
|
|
# (阈值是严格 >0.5——正好 -50% 不触发,seed 用 90)
|
|
for day, sent in [(20260827, 200), (20260828, 200), (20260829, 200),
|
|
(20260830, 200), (20260831, 200), (20260901, 200),
|
|
(20260902, 200), (20260903, 90)]:
|
|
session.add(StatDailyStaff(day=day, channel="wechat", user_id="u1",
|
|
msg_sent=sent, msg_recv=0,
|
|
active_friends=5, active_rooms=1))
|
|
# 当日 rollup 行(active_staff 卡片读 stat_daily_staff,必须有当日行)
|
|
session.add(StatDailyStaff(day=20260904, channel="wechat", user_id="u1",
|
|
msg_sent=15, msg_recv=7,
|
|
active_friends=1, active_rooms=1))
|
|
# 当日 + 首沟通:t1 当日首现(首沟通=1),t2 早在 20260827 就有(不算)
|
|
session.add(MsgDailyStats(channel="wechat", owner_account_id="wx1",
|
|
peer_kind="friend", peer_id="t1", day=20260904,
|
|
send_cnt=10, recv_cnt=5, last_ts=1788500000000,
|
|
last_is_send=True))
|
|
session.add(MsgDailyStats(channel="wechat", owner_account_id="wx1",
|
|
peer_kind="friend", peer_id="t2", day=20260827,
|
|
send_cnt=3, recv_cnt=0, last_ts=1787800000000,
|
|
last_is_send=True))
|
|
session.add(MsgDailyStats(channel="wechat", owner_account_id="wx1",
|
|
peer_kind="room", peer_id="r1", day=20260904,
|
|
send_cnt=7, recv_cnt=0, last_ts=1788500000000,
|
|
last_is_send=True))
|
|
# 通话时间戳必须是 2026-09-04 当日(1756950000000 是 2025 年——坑)
|
|
session.add(PhoneCall(call_id="c1", channel="voice_call",
|
|
start_call_time=1788500000000, call_duration=60))
|
|
await session.commit()
|
|
|
|
|
|
async def test_overview_today(make_client, monkeypatch):
|
|
monkeypatch.setattr(metrics, "today_key", lambda: _TODAY)
|
|
async def seed(session):
|
|
await _seed(session)
|
|
out = await metrics.overview(session, channel="wechat", scope="today")
|
|
assert out["cards"]["msg_total"] == 22 # (10+5)+(7+0)
|
|
assert out["cards"]["active_customers"] == 1 # peer_kind=friend 去重,群不计
|
|
assert out["cards"]["active_staff"] == 1
|
|
assert out["cards"]["call_cnt"] == 1
|
|
assert out["cards"]["first_contact"] == 1 # t1 全史 MIN(day)=今日;t2 不是
|
|
assert "msg_total_delta" not in out["cards"] # 今日无环比
|
|
assert "prev_msg_total" not in out["cards"]
|
|
|
|
await make_client(seed)
|
|
|
|
|
|
async def test_anomaly_yesterday_vs_prev7(make_client, monkeypatch):
|
|
monkeypatch.setattr(metrics, "today_key", lambda: _TODAY)
|
|
async def seed(session):
|
|
await _seed(session)
|
|
a = await metrics.anomaly(session, channel="wechat")
|
|
assert a is not None
|
|
assert a["total_delta_pct"] <= -0.5 # 90 vs 均值 200 = -55%(严格 >50% 触发)
|
|
staff = {s["user_id"]: s for s in a["staff"]}
|
|
assert staff["u1"]["yesterday"] == 90
|
|
assert staff["u1"]["prev7_mean"] == 200.0
|
|
|
|
await make_client(seed)
|
|
|
|
|
|
async def test_overview_fallback_when_rollup_empty(make_client, monkeypatch):
|
|
"""stat_daily_staff 空表 → 员工卡片回退实时聚合 msg_daily_stats(部署即用)。"""
|
|
monkeypatch.setattr(metrics, "today_key", lambda: _TODAY)
|
|
async def seed(session):
|
|
session.add(WechatAccount(wx_id="wx1", user_id="u1"))
|
|
session.add(MsgDailyStats(channel="wechat", owner_account_id="wx1",
|
|
peer_kind="friend", peer_id="t1", day=20260904,
|
|
send_cnt=2, recv_cnt=1, last_ts=1788500000000,
|
|
last_is_send=True))
|
|
await session.commit()
|
|
out = await metrics.overview(session, channel="wechat", scope="today")
|
|
assert out["cards"]["active_staff"] == 1
|
|
assert out["cards"]["msg_total"] == 3
|
|
|
|
await make_client(seed)
|
|
|
|
|
|
async def test_anomaly_fallback_multi_account(make_client, monkeypatch):
|
|
"""回退路径同人多账号累加:wx1/wx2 同映射 u1,昨日量必须相加而非覆盖。"""
|
|
monkeypatch.setattr(metrics, "today_key", lambda: _TODAY)
|
|
async def seed(session):
|
|
session.add(WechatAccount(wx_id="wx1", user_id="u1"))
|
|
session.add(WechatAccount(wx_id="wx2", user_id="u1"))
|
|
# 不 seed StatDailyStaff → 走 msg_daily_stats 回退路径。
|
|
# 昨日(20260903)各 100(合计 200);prev7 每天各 50(日均 100)→ 偏离 +100% 触发
|
|
for day in [20260827, 20260828, 20260829, 20260830,
|
|
20260831, 20260901, 20260902, 20260903]:
|
|
sent = 100 if day == 20260903 else 50
|
|
for wx in ("wx1", "wx2"):
|
|
session.add(MsgDailyStats(channel="wechat", owner_account_id=wx,
|
|
peer_kind="friend", peer_id="t1", day=day,
|
|
send_cnt=sent, recv_cnt=0,
|
|
last_ts=1788500000000,
|
|
last_is_send=True))
|
|
await session.commit()
|
|
a = await metrics.anomaly(session, channel="wechat")
|
|
assert a is not None
|
|
staff = {s["user_id"]: s for s in a["staff"]}
|
|
# 修复前:后写账号覆盖前写,u1 昨日只剩 100;修复后两账号累加 = 200
|
|
assert staff["u1"]["yesterday"] == 200
|
|
assert staff["u1"]["prev7_mean"] == 100.0
|
|
|
|
await make_client(seed)
|