a38170d205
Co-Authored-By: Claude Code <noreply@anthropic.com>
96 lines
4.2 KiB
Python
96 lines
4.2 KiB
Python
"""employee_call_daily / employee_metric_daily 生成判别测试(批次A Task5/6)。
|
|
|
|
判据(v2 §1.1 #2/#5、§1.2 #9、§1.3 #11):
|
|
- 通话无渠道维,独立小表;接通=duration>0;followup=24h 内任一账号有消息;
|
|
- 幽灵员工(owner 映射不上/不在 staff)过滤;
|
|
- 宽表消息计数 friend-only;串类与 fact 同源。
|
|
"""
|
|
import datetime as dt
|
|
from zoneinfo import ZoneInfo
|
|
|
|
import sqlalchemy as sa
|
|
|
|
from app.db.models import (
|
|
EmployeeCallDaily,
|
|
Message,
|
|
PhoneCall,
|
|
Staff,
|
|
WechatAccount,
|
|
WhatsappAccount,
|
|
)
|
|
from app.storage.employee_metrics import recompute_call_days
|
|
|
|
_SH = ZoneInfo("Asia/Shanghai")
|
|
|
|
|
|
def _ts(*args) -> int:
|
|
return int(dt.datetime(*args, tzinfo=_SH).timestamp() * 1000)
|
|
|
|
|
|
async def _seed_org(session):
|
|
session.add(Staff(user_id="u1", name="张三", is_on_job=True))
|
|
session.add(WechatAccount(wx_id="wx1", user_id="u1"))
|
|
# 第二个微信号:验证 followup「任一账号」语义(owner-IN 而非单账号)
|
|
session.add(WechatAccount(wx_id="wx2", user_id="u1"))
|
|
# WA 账号:验证 wa_voice_call 渠道收窄正例
|
|
session.add(WhatsappAccount(uid="wa1", user_id="u1"))
|
|
await session.commit()
|
|
|
|
|
|
async def test_call_daily_aggregates(make_session):
|
|
s = make_session
|
|
await _seed_org(s)
|
|
s.add(PhoneCall(call_id="c1", channel="voice_call", user_id="u1",
|
|
call_number="p1", start_call_time=_ts(2026, 9, 1, 10, 0),
|
|
call_duration=120))
|
|
s.add(PhoneCall(call_id="c2", channel="voice_call", user_id="u1",
|
|
call_number="p2", start_call_time=_ts(2026, 9, 1, 15, 0),
|
|
call_duration=0)) # 未接通
|
|
# c3:WA 语音,接通,窗口内有 WA 消息 → followup 正例(渠道收窄匹配)
|
|
s.add(PhoneCall(call_id="c3", channel="wa_voice_call", user_id="u1",
|
|
call_number="p3", start_call_time=_ts(2026, 9, 1, 10, 0),
|
|
call_duration=60))
|
|
# c4:微信语音,接通,但窗口内只有 WA 消息 → followup 反例(渠道收窄)
|
|
s.add(PhoneCall(call_id="c4", channel="voice_call", user_id="u1",
|
|
call_number="p4", start_call_time=_ts(2026, 9, 1, 10, 0),
|
|
call_duration=90))
|
|
# c1 后 2h 员工**另一微信号 wx2** 有跟进消息(任一账号语义:owner-IN)
|
|
s.add(Message(msg_id="f1", channel="wechat", owner_account_id="wx2",
|
|
talker_id="p1", timestamp=_ts(2026, 9, 1, 12, 0),
|
|
is_send=True, type="text", text="跟进"))
|
|
# c2(未接通)后 1h 有消息:即使任一方向也不算 followup(仅已接通判定)
|
|
s.add(Message(msg_id="f2", channel="wechat", owner_account_id="wx1",
|
|
talker_id="p2", timestamp=_ts(2026, 9, 1, 16, 0),
|
|
is_send=False, type="text", text="客户回拨?"))
|
|
# c3 后 2h WA 账号有消息 → 正例
|
|
s.add(Message(msg_id="f3", channel="whatsapp", owner_account_id="wa1",
|
|
talker_id="p3", timestamp=_ts(2026, 9, 1, 12, 0),
|
|
is_send=True, type="text", text="wa 跟进"))
|
|
# c4(voice_call→wechat)窗口内只有 WA 消息 → 不匹配渠道,反例
|
|
s.add(Message(msg_id="f4", channel="whatsapp", owner_account_id="wa1",
|
|
talker_id="p4", timestamp=_ts(2026, 9, 1, 12, 0),
|
|
is_send=True, type="text", text="错渠道"))
|
|
await s.commit()
|
|
n = await recompute_call_days(s, [20260901])
|
|
await s.commit()
|
|
assert n == 1
|
|
row = (await s.execute(sa.select(EmployeeCallDaily))).scalar_one()
|
|
assert row.call_count == 4
|
|
assert row.call_seconds == 270 # 120+0+60+90;duration 单位=秒(Task0 已核实)
|
|
assert row.answered_call_count == 3 # c1/c3/c4
|
|
# c1(wx2 任一账号)+ c3(wa 渠道正例);c2 未接通守卫、c4 渠道收窄反例
|
|
assert row.call_followup_count == 2
|
|
|
|
|
|
async def test_call_daily_ignores_unknown_staff(make_session):
|
|
s = make_session
|
|
await _seed_org(s)
|
|
s.add(PhoneCall(call_id="c9", channel="phone_call", user_id="ghost",
|
|
call_number="p9", start_call_time=_ts(2026, 9, 1, 10, 0),
|
|
call_duration=60))
|
|
await s.commit()
|
|
await recompute_call_days(s, [20260901])
|
|
await s.commit()
|
|
assert (await s.execute(
|
|
sa.select(sa.func.count()).select_from(EmployeeCallDaily))).scalar_one() == 0
|