fix(cps): 群内微信用户按"本群点击"算,不用 first_group_id

first_group_id 只记首次授权群;同一用户(cookie 已存)再去别的群不再走授权回调,
会被漏算(实测:用户首次在群47授权,再去群52点击,群52详情显示0)。
改为:在本群有带 openid 点击的 distinct openid 即本群用户,关联 cps_wx_user 画像。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 16:36:57 +08:00
parent f868414966
commit e13f0f7658
+34 -25
View File
@@ -474,44 +474,53 @@ def group_order_daily(
def group_wx_users(db: Session, *, group_id: int, limit: int = 200) -> list[dict]:
"""群内微信用户(首次来源 first_group_id=group_id)+ 每人在该群的 visit/copy 次数(领券画像)
"""该群来过的微信用户:在本群有带 openid 点击的所有用户 + 本群 visit/copy 次数 + 画像。
按首次进入倒序。copy 次数 = 该用户在该群点了几次「复制口令」(领券意向),visit = 进落地页次数。
用「本群点击」判定归属,而非 cps_wx_user.first_group_id —— 同一用户会在多个群活动,
first_group_id 只记首次授权群,会漏掉"先在别处授权、cookie 已存、又来本群"的用户。
copy=本群点「复制口令」次数(领券意向),visit=进落地页次数。按本群首次点击倒序。
昵称/头像来自授权画像;美团/京东群只 302 跳转、无 userinfo 触发点,故多为空。
"""
users = (
db.execute(
select(CpsWxUser)
.where(CpsWxUser.first_group_id == group_id)
.order_by(desc(CpsWxUser.first_seen))
.limit(limit)
)
.scalars()
.all()
)
if not users:
return []
openids = [u.openid for u in users]
stat: dict[str, dict] = {}
rows = db.execute(
select(CpsClick.openid, CpsClick.event_type, func.count())
.where(CpsClick.group_id == group_id)
.where(CpsClick.openid.in_(openids))
.where(CpsClick.openid.is_not(None))
.group_by(CpsClick.openid, CpsClick.event_type)
).all()
if not rows:
return []
stat: dict[str, dict] = {}
for openid, event_type, cnt in rows:
s = stat.setdefault(openid, {"visit": 0, "copy": 0})
if event_type == "copy":
s["copy"] = cnt
else:
s["visit"] = cnt
return [
openids = list(stat.keys())
# 本群每个 openid 的首次点击时间(排序 + 展示"首次进入")
first_seen = dict(
db.execute(
select(CpsClick.openid, func.min(CpsClick.clicked_at))
.where(CpsClick.group_id == group_id)
.where(CpsClick.openid.is_not(None))
.group_by(CpsClick.openid)
).all()
)
# 关联画像(未授权 userinfo 的昵称/头像为空)
users = {
u.openid: u
for u in db.execute(select(CpsWxUser).where(CpsWxUser.openid.in_(openids))).scalars().all()
}
result = [
{
"openid": u.openid,
"nickname": u.nickname,
"headimgurl": u.headimgurl,
"first_seen": u.first_seen,
"visit_count": stat.get(u.openid, {}).get("visit", 0),
"copy_count": stat.get(u.openid, {}).get("copy", 0),
"openid": openid,
"nickname": users[openid].nickname if openid in users else None,
"headimgurl": users[openid].headimgurl if openid in users else None,
"first_seen": first_seen.get(openid),
"visit_count": stat[openid]["visit"],
"copy_count": stat[openid]["copy"],
}
for u in users
for openid in openids
]
result.sort(key=lambda x: x["first_seen"], reverse=True)
return result[:limit]