feat(monitor): 博主的粉丝数,以及给作品起备注
两件都是「作品栏里把这东西认出来」的延伸: * **账号级指标**:作品列表只会说「这条涨了多少赞」,说不了「这个人整个 账号的粉丝在涨还是在掉」。抖音的资料接口本来就有粉丝数/总获赞/作品数, 每轮顺手记一条快照(`monitor_creator_stat`,粒度 = 任务×博主×轮次, 和作品指标同形)。组头显示最近一条。 快照在「一条作品都没采到」的早退**之前**落:作品列表被风控挡住的那一轮, 正是「粉丝还在涨、但新作品没在发现」最该被看见的时刻。 * **作品备注**:博主备注回答「这个账号是谁」,这条回答「这条我要盯着」。 一个博主底下常常只有一两件值得盯的作品,所以不能合并成一条。键取 (platform, note_id),跨任务共用一份。 两边都守住同一条口径:**不知道就是 null,不写成 0** —— 0 在趋势图上是一条 砸到底的线,和「还没采到」是两回事。
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""博主备注 —— 作品栏里那一位到底是谁。
|
||||
"""作品栏里的**博主**:备注(他到底是谁)与账号级指标(他现在多大)。
|
||||
|
||||
按 creator_hash 分组、显示 creator_name,两样都认不出人:一个是哈希,一个是平台昵称。
|
||||
备注是人自己起的名字。
|
||||
备注是人自己起的名字。账号级指标则是作品列表给不了的东西 —— 作品说的是"这条涨了多少赞",
|
||||
粉丝数说的是"这个人整个账号在涨还是在掉"。
|
||||
"""
|
||||
|
||||
import httpx
|
||||
@@ -11,7 +12,12 @@ import pytest_asyncio
|
||||
|
||||
from api.main import app
|
||||
from api.monitor import db as monitor_db
|
||||
from api.monitor.models import MODE_CREATOR, MonitorNote, MonitorTask
|
||||
from api.monitor.models import (
|
||||
MODE_CREATOR,
|
||||
MonitorCreatorStat,
|
||||
MonitorNote,
|
||||
MonitorTask,
|
||||
)
|
||||
|
||||
CREATOR_HASH = "hash-a"
|
||||
NICKNAME = "张三"
|
||||
@@ -122,3 +128,93 @@ class TestCreatorAlias:
|
||||
await _set_alias(client, " 竞品A ")
|
||||
|
||||
assert (await _notes(client))[0]["creator_alias"] == "竞品A"
|
||||
|
||||
|
||||
async def _add_stat(
|
||||
task_id: int,
|
||||
run_id: int,
|
||||
fans: int | None,
|
||||
*,
|
||||
total_favorited: int | None = 83000,
|
||||
works: int | None = 42,
|
||||
creator_hash: str = CREATOR_HASH,
|
||||
captured_at: int = 1,
|
||||
) -> None:
|
||||
async with monitor_db.get_session() as session:
|
||||
session.add(
|
||||
MonitorCreatorStat(
|
||||
task_id=task_id,
|
||||
run_id=run_id,
|
||||
creator_hash=creator_hash,
|
||||
nickname=NICKNAME,
|
||||
fans=fans,
|
||||
total_favorited=total_favorited,
|
||||
works_count=works,
|
||||
following=7,
|
||||
captured_at=captured_at,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TestCreatorStats:
|
||||
"""账号级指标跟着作品一起返回 —— 界面上是按博主归组的,为了一个组头再发一轮请求
|
||||
没道理。"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_without_snapshots_the_fields_are_null(self, client):
|
||||
"""null 而不是 0:0 会显示成「粉丝 0」,而事实是"还没采到"。"""
|
||||
await _seed()
|
||||
|
||||
note = (await _notes(client))[0]
|
||||
|
||||
assert note["creator_fans"] is None
|
||||
assert note["creator_total_favorited"] is None
|
||||
assert note["creator_works"] is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_snapshot_shows_up_on_every_work_of_that_creator(self, client):
|
||||
task_id = await _seed()
|
||||
await _add_stat(task_id, run_id=1, fans=12000)
|
||||
|
||||
for note in await _notes(client):
|
||||
assert note["creator_fans"] == 12000
|
||||
assert note["creator_total_favorited"] == 83000
|
||||
assert note["creator_works"] == 42
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_latest_run_wins(self, client):
|
||||
"""一轮一条,所以总会有好几条 —— 给界面的必须是最近那条。"""
|
||||
task_id = await _seed()
|
||||
await _add_stat(task_id, run_id=1, fans=12000)
|
||||
await _add_stat(task_id, run_id=2, fans=12300)
|
||||
|
||||
assert (await _notes(client))[0]["creator_fans"] == 12300
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_another_platforms_snapshot_does_not_leak(self, client):
|
||||
"""两个平台上恰好同名同哈希的博主是两个人 —— 快照挂在任务上,不该串。"""
|
||||
await _seed(platform="xhs")
|
||||
dy_task_id = await _seed(platform="dy")
|
||||
await _add_stat(dy_task_id, run_id=1, fans=999)
|
||||
|
||||
assert (await _notes(client, "xhs"))[0]["creator_fans"] is None
|
||||
assert (await _notes(client, "dy"))[0]["creator_fans"] == 999
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_an_unparsed_count_stays_null(self, client):
|
||||
"""快照在,但某一项没解析出来 —— 那一项必须是 null,不能变成 0。
|
||||
|
||||
和上一条的区别:那条是"根本没有快照",这条是"有快照、其中一项平台没给"。
|
||||
界面上两种都该是「—」。
|
||||
"""
|
||||
task_id = await _seed()
|
||||
await _add_stat(
|
||||
task_id, run_id=1, fans=None, total_favorited=None, works=None
|
||||
)
|
||||
|
||||
note = (await _notes(client))[0]
|
||||
|
||||
assert note["creator_fans"] is None
|
||||
assert note["creator_works"] is None
|
||||
# 快照本身是有的(有采集时间),只是值不知道 —— 前端要能分开这两件事。
|
||||
assert note["creator_stats_at"] is not None
|
||||
|
||||
Reference in New Issue
Block a user