feat(analytics): stat_daily_staff 员工日汇总维护层——rollup派生/幂等/判别法
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""stat_daily_staff 维护层:派生/幂等/判别法/未映射兜底。
|
||||
|
||||
判别法(9-03 先例):rollup 只读 msg_daily_stats——回填后删光 message 明细,
|
||||
员工汇总重算仍出完整数据。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
|
||||
from app.db.models import Message, MsgDailyStats, StatDailyStaff, WechatAccount
|
||||
from app.storage import staff_summary
|
||||
|
||||
|
||||
def _mk_msg(msg_id: str, ts: int, is_send: bool | None) -> Message:
|
||||
return Message(
|
||||
msg_id=msg_id,
|
||||
channel="wechat",
|
||||
owner_account_id="wx1",
|
||||
talker_id="t1",
|
||||
timestamp=ts,
|
||||
is_send=is_send,
|
||||
text="x",
|
||||
fetched_at=dt.datetime(2026, 9, 4, 12, 0),
|
||||
)
|
||||
|
||||
|
||||
async def _seed_pair(session) -> None:
|
||||
"""两账号同一员工(wx1/wx2→u1) + 未映射账号 wx3,20260904 各有消息。"""
|
||||
session.add(WechatAccount(wx_id="wx1", user_id="u1", user_name="张三"))
|
||||
session.add(WechatAccount(wx_id="wx2", user_id="u1", user_name="张三"))
|
||||
rows = [
|
||||
MsgDailyStats(channel="wechat", owner_account_id="wx1", peer_kind="friend",
|
||||
peer_id="t1", day=20260904, send_cnt=10, recv_cnt=5,
|
||||
last_ts=1756900000000, last_is_send=True),
|
||||
MsgDailyStats(channel="wechat", owner_account_id="wx1", peer_kind="room",
|
||||
peer_id="r1", day=20260904, send_cnt=3, recv_cnt=0,
|
||||
last_ts=1756900000000, last_is_send=True),
|
||||
MsgDailyStats(channel="wechat", owner_account_id="wx2", peer_kind="friend",
|
||||
peer_id="t1", day=20260904, send_cnt=2, recv_cnt=1,
|
||||
last_ts=1756900001000, last_is_send=False),
|
||||
MsgDailyStats(channel="wechat", owner_account_id="wx3", peer_kind="friend",
|
||||
peer_id="t9", day=20260904, send_cnt=1, recv_cnt=0,
|
||||
last_ts=1756900002000, last_is_send=True),
|
||||
]
|
||||
session.add_all(rows)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def test_recompute_aggregates_by_user(make_client):
|
||||
"""两账号同人合并(sent=10+3+2=15,recv=5+0+1=6;t1 在 wx1/wx2 都聊 → 合并后 distinct friend={t1} + room={r1});wx3 未映射按原值。"""
|
||||
async def seed(session):
|
||||
await _seed_pair(session)
|
||||
await staff_summary.recompute_days(session, [20260904])
|
||||
await session.commit()
|
||||
from sqlalchemy import select
|
||||
|
||||
rows = (await session.execute(select(StatDailyStaff))).scalars().all()
|
||||
by_uid = {r.user_id: r for r in rows}
|
||||
assert set(by_uid) == {"u1", "wx3"} # wx3 映射不上按 owner 原值
|
||||
u1 = by_uid["u1"]
|
||||
assert u1.msg_sent == 15 and u1.msg_recv == 6
|
||||
assert u1.active_friends == 1 # t1 跨账号去重
|
||||
assert u1.active_rooms == 1
|
||||
assert by_uid["wx3"].active_friends == 1
|
||||
|
||||
await make_client(seed)
|
||||
|
||||
|
||||
async def test_recompute_idempotent(make_client):
|
||||
async def seed(session):
|
||||
await _seed_pair(session)
|
||||
await staff_summary.recompute_days(session, [20260904])
|
||||
await staff_summary.recompute_days(session, [20260904]) # 再跑不翻倍
|
||||
await session.commit()
|
||||
from sqlalchemy import func, select
|
||||
|
||||
n = (
|
||||
await session.execute(
|
||||
select(func.count()).select_from(StatDailyStaff)
|
||||
)
|
||||
).scalar_one()
|
||||
assert n == 2 # u1 + wx3 各一行
|
||||
|
||||
await make_client(seed)
|
||||
|
||||
|
||||
async def test_discriminant_survives_message_delete(make_client):
|
||||
"""判别法:rollup 回填后删光 message,重算结果不变(只依赖 msg_daily_stats)。"""
|
||||
async def seed(session):
|
||||
await _seed_pair(session)
|
||||
session.add(_mk_msg("m1", 1756900000000, True))
|
||||
await session.commit()
|
||||
await staff_summary.recompute_days(session, [20260904])
|
||||
await session.commit()
|
||||
from sqlalchemy import delete, select
|
||||
|
||||
await session.execute(delete(Message))
|
||||
await staff_summary.recompute_days(session, [20260904]) # 自愈重算
|
||||
await session.commit()
|
||||
rows = (await session.execute(select(StatDailyStaff))).scalars().all()
|
||||
by_uid = {r.user_id: r for r in rows}
|
||||
assert by_uid["u1"].msg_sent == 15 # 数据不丢
|
||||
|
||||
await make_client(seed)
|
||||
|
||||
|
||||
async def test_recompute_all_and_rolling(make_client):
|
||||
async def seed(session):
|
||||
await _seed_pair(session)
|
||||
n = await staff_summary.recompute_all(session)
|
||||
await session.commit()
|
||||
assert n == 1 # 全库只有一个「有数据的 day×channel」
|
||||
|
||||
await make_client(seed)
|
||||
days = staff_summary.rolling_days(20260904, window=3)
|
||||
assert days == [20260902, 20260903, 20260904]
|
||||
Reference in New Issue
Block a user