feat(monitor): 作品导出里,没有作品的博主也占一行
导出原先是从作品摊出来的,于是「这个号这段时间一条作品都没发」直接从表里消失 —— 而在表里,**消失和「一切正常」长得一模一样**。这类博主恰恰是名单上最该被看见的: 还在涨粉、只是没动静,或者号出了问题。 来源换成 博主 ∪ 作品(和作品栏同一份 list_creators),没有作品的那一行只填博主, 作品那几列留空 —— **空,不是 0**。 顺带把账号级指标加进作品导出(粉丝数 / 总获赞 / 主页作品数):没有作品的博主那一行 就只剩它们,不加的话那一行等于空行。「主页作品数」是平台上的总数,和下面几列(我们 监控到的作品)不是一回事,所以名字分得开。 排序改成按「这是谁」,同一个博主的行挨在一起 —— 表格是拿去筛和做透视的。
This commit is contained in:
@@ -370,3 +370,82 @@ class TestNotesExportColumns:
|
||||
assert re.fullmatch(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}", by_id["note-a"]["首次发现"])
|
||||
# 而且不能是原始毫秒。
|
||||
assert by_id["note-a"]["发布时间"] != str(PUBLISHED_A)
|
||||
|
||||
|
||||
class TestNotesExportWithoutWorks:
|
||||
"""**一条作品都没有的博主,也要在导出里占一行**,作品那几列留空。
|
||||
|
||||
导出是拿去比对名单的,「这个号这段时间一条作品都没发」恰恰是名单上最该被看见的
|
||||
一行 —— 还在涨粉、只是没动静,或者号出了问题。按作品导的话他直接消失,而在表里,
|
||||
**消失和「一切正常」长得一模一样**。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
async def _add_creator_without_works(**overrides) -> None:
|
||||
from sqlalchemy import select
|
||||
|
||||
from api.monitor.models import MonitorCreatorStat
|
||||
|
||||
fields = dict(
|
||||
run_id=1, creator_hash="hash-c", nickname="博主丙", fans=8800,
|
||||
total_favorited=4321, works_count=7, following=2,
|
||||
captured_at=1_700_000_000_000,
|
||||
)
|
||||
fields.update(overrides)
|
||||
|
||||
async with monitor_db.get_session() as session:
|
||||
task_id = await session.scalar(select(MonitorTask.id))
|
||||
session.add(MonitorCreatorStat(task_id=task_id, **fields))
|
||||
|
||||
@staticmethod
|
||||
async def _rows(client):
|
||||
response = await client.get(
|
||||
"/api/monitor/export", params={"kind": "notes", "format": "csv"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
return list(csv.DictReader(io.StringIO(response.content.decode("utf-8-sig"))))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_creator_without_works_gets_a_row(self, client):
|
||||
await self._add_creator_without_works()
|
||||
|
||||
rows = await self._rows(client)
|
||||
only = [row for row in rows if row["博主昵称"] == "博主丙"]
|
||||
|
||||
assert len(only) == 1, "没有作品的博主从导出里消失了"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_that_row_keeps_the_creator_info_and_leaves_works_empty(self, client):
|
||||
await self._add_creator_without_works()
|
||||
|
||||
row = next(r for r in await self._rows(client) if r["博主昵称"] == "博主丙")
|
||||
|
||||
# 博主那几列要有 —— 否则这一行就是个空行。
|
||||
assert row["粉丝数"] == "8800"
|
||||
assert row["总获赞"] == "4321"
|
||||
assert row["主页作品数"] == "7"
|
||||
# 作品那几列留空。**空,不是 0** —— 0 会读成「这条作品零互动」。
|
||||
for column in ("作品ID", "标题", "链接", "发布时间", "点赞", "评论", "收藏", "分享"):
|
||||
assert row[column] == "", f"{column} 应该是空的"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_works_are_still_all_there(self, client):
|
||||
"""加了这一类行之后,原来那些作品一行都不能少。"""
|
||||
await self._add_creator_without_works()
|
||||
|
||||
rows = await self._rows(client)
|
||||
|
||||
assert {row["作品ID"] for row in rows if row["作品ID"]} == {"note-a", "note-b"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_creator_whose_metrics_are_unknown_leaves_them_empty(self, client):
|
||||
"""采不到账号指标时留空,不写 0 —— 和界面一个口径。"""
|
||||
await self._add_creator_without_works(
|
||||
fans=None, total_favorited=None, works_count=None
|
||||
)
|
||||
|
||||
row = next(r for r in await self._rows(client) if r["博主昵称"] == "博主丙")
|
||||
|
||||
assert row["粉丝数"] == ""
|
||||
assert row["总获赞"] == ""
|
||||
assert row["主页作品数"] == ""
|
||||
|
||||
Reference in New Issue
Block a user