feat: EOD 23:15 seal + midnight seed shell for video/fans
Move daily snapshot/fans to 23:15 and add 00:01 seed that clones last_completed ok=1 rows into today without advancing completed. Evening runs force through seed markers; growth previous is per-aid adjacent. Home shows short-update clock + 全站数据 date, and 收录视频 counts.
This commit is contained in:
1 parent
374b138041
commit
8a9cb21408
17 files changed
+520
-39
No files matched your search
@@ -154,7 +154,8 @@ def test_analytics_defaults_and_validation():
|
||||
}
|
||||
data["schedule"] = {
|
||||
"discover_minutes": 30,
|
||||
"snapshot_at": "00:01",
|
||||
"snapshot_at": "23:15",
|
||||
"seed_at": "00:01",
|
||||
"candidate_ttl_days": 7,
|
||||
"recent_snapshot_minutes": 15,
|
||||
"recent_video_days": 3,
|
||||
@@ -276,7 +277,8 @@ def test_schedule_recent_validation():
|
||||
data = base_config()
|
||||
data["schedule"] = {
|
||||
"discover_minutes": 60,
|
||||
"snapshot_at": "00:01",
|
||||
"snapshot_at": "23:15",
|
||||
"seed_at": "00:01",
|
||||
"candidate_ttl_days": 7,
|
||||
**bad,
|
||||
}
|
||||
|
||||
+96
-1
@@ -501,7 +501,7 @@ def test_run_recent_videos_refreshes_without_completion_markers():
|
||||
|
||||
|
||||
def test_full_snapshot_skips_ok_rows_including_hourly_refresh():
|
||||
"""00:01 全量:同日 ok=1(含小时任务刚刷)直接跳过。"""
|
||||
"""晚间全量(未 force):同日 ok=1(含小时任务刚刷)直接跳过。"""
|
||||
clock = 1_800_000_000
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
transport = SnapshotTransport({1: view_for(1), 2: view_for(2)})
|
||||
@@ -530,6 +530,99 @@ def test_full_snapshot_skips_ok_rows_including_hourly_refresh():
|
||||
|
||||
|
||||
|
||||
def test_seed_from_last_completed_does_not_mark_completed():
|
||||
clock = 1_800_000_000
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
transport = SnapshotTransport({1: view_for(1)})
|
||||
db, repo, service = make_service(tmp, transport, track_fans=(37754047,))
|
||||
try:
|
||||
add_video(repo, 1)
|
||||
yday = "2026-10-30"
|
||||
today = "2026-10-31"
|
||||
repo.upsert_snapshot(
|
||||
yday, 1,
|
||||
metrics={"view": 10, "like": 1, "coin": 0, "favorite": 0,
|
||||
"share": 0, "reply": 0, "danmaku": 0},
|
||||
ok=True, err=None, captured_at=clock - 86400,
|
||||
)
|
||||
repo.upsert_up_stat(
|
||||
yday, 37754047, follower=100, following=1, ok=True,
|
||||
captured_at=clock - 86400,
|
||||
)
|
||||
repo.kv_set("snapshot:last_completed", yday)
|
||||
repo.kv_set(service.completed_key(yday), "1")
|
||||
|
||||
summary = service.run_seed_from_last_completed(date=today)
|
||||
assert summary.success == 1
|
||||
assert summary.fans_ok == 1
|
||||
assert repo.kv_get(service.seeded_key(today)) == "1"
|
||||
assert repo.kv_get(service.fans_seeded_key(today)) == "1"
|
||||
assert repo.kv_get(service.completed_key(today)) is None
|
||||
assert repo.kv_get("snapshot:last_completed") == yday
|
||||
row = repo.get_snapshot(today, 1)
|
||||
assert row is not None
|
||||
assert int(row["view"]) == 10
|
||||
assert row["src"] == "seed"
|
||||
fan = repo.get_up_stat(today, 37754047)
|
||||
assert fan is not None and int(fan["follower"]) == 100
|
||||
|
||||
# 幂等:再 seed 不重复
|
||||
again = service.run_seed_from_last_completed(date=today)
|
||||
assert again.already_completed is True
|
||||
assert transport.count("/web-interface/view") == 0
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seeded_day_skips_without_force_and_seals_with_force():
|
||||
clock = 1_800_000_000
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
fan_payload = dict(RELATION_PAYLOAD)
|
||||
fan_payload["data"] = dict(RELATION_PAYLOAD["data"])
|
||||
fan_payload["data"]["follower"] = 999
|
||||
transport = SnapshotTransport({1: view_for(1, view=555)}, fan=fan_payload)
|
||||
db, repo, service = make_service(tmp, transport, track_fans=(37754047,))
|
||||
try:
|
||||
add_video(repo, 1)
|
||||
yday = "2026-10-30"
|
||||
today = DATE
|
||||
repo.upsert_snapshot(
|
||||
yday, 1,
|
||||
metrics={"view": 10, "like": 1, "coin": 0, "favorite": 0,
|
||||
"share": 0, "reply": 0, "danmaku": 0},
|
||||
ok=True, err=None, captured_at=clock - 86400,
|
||||
)
|
||||
repo.upsert_up_stat(
|
||||
yday, 37754047, follower=100, following=1, ok=True,
|
||||
captured_at=clock - 86400,
|
||||
)
|
||||
repo.kv_set("snapshot:last_completed", yday)
|
||||
service.run_seed_from_last_completed(date=today)
|
||||
|
||||
pending = service.run_if_needed(date=today)
|
||||
assert pending.completed is False
|
||||
assert pending.already_completed is False
|
||||
assert repo.kv_get(service.seeded_key(today)) == "1"
|
||||
assert transport.count("/web-interface/view") == 0
|
||||
|
||||
sealed = service.run_if_needed(date=today, force=True)
|
||||
assert sealed.completed is True
|
||||
assert sealed.success >= 1
|
||||
assert repo.kv_get(service.completed_key(today)) == "1"
|
||||
assert repo.kv_get("snapshot:last_completed") == today
|
||||
assert repo.kv_get(service.seeded_key(today)) is None
|
||||
row = repo.get_snapshot(today, 1)
|
||||
assert int(row["view"]) == 555 # 被真实 view 覆盖
|
||||
assert row["src"] == "view"
|
||||
|
||||
fans = service.run_fans_if_needed(date=today, force=True)
|
||||
assert fans.fans_ok >= 1
|
||||
assert repo.kv_get(service.fans_seeded_key(today)) is None
|
||||
assert int(repo.get_up_stat(today, 37754047)["follower"]) == 999
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main():
|
||||
test_snapshot_success_resume_and_force()
|
||||
test_resume_skips_ok_and_retries_failed()
|
||||
@@ -545,6 +638,8 @@ def main():
|
||||
test_backup_failure_retries_without_api()
|
||||
test_run_recent_videos_refreshes_without_completion_markers()
|
||||
test_full_snapshot_skips_ok_rows_including_hourly_refresh()
|
||||
test_seed_from_last_completed_does_not_mark_completed()
|
||||
test_seeded_day_skips_without_force_and_seals_with_force()
|
||||
print("OK: 每日快照、断点续跑、失败占位与粉丝采集测试全部通过")
|
||||
|
||||
|
||||
|
||||
@@ -626,6 +626,61 @@ def test_snapshot_recent_next_run_follows_completion():
|
||||
db.close()
|
||||
|
||||
|
||||
|
||||
def test_overview_adjacent_previous_with_seed_and_dual_fields():
|
||||
"""seed 当日行存在时,播放增量按片相邻成功日;overview 暴露更新时刻与总数。"""
|
||||
import json
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db, q = setup(tmp)
|
||||
try:
|
||||
repo = Repository(db)
|
||||
today = "2026-11-01"
|
||||
# 克隆上一完成日到今天(不推进 last_completed)
|
||||
for aid in (101, 102):
|
||||
prev = repo.get_snapshot(DATES[-1], aid)
|
||||
metrics = {
|
||||
k: int(prev[k]) for k in (
|
||||
"view", "like", "coin", "favorite", "share", "reply", "danmaku"
|
||||
)
|
||||
}
|
||||
repo.upsert_snapshot(
|
||||
today, aid, metrics=metrics, ok=True, err=None,
|
||||
captured_at=FIXED_NOW, src="seed",
|
||||
)
|
||||
repo.kv_set("snapshot:seeded:" + today, "1")
|
||||
# 小时短更新任务记录
|
||||
repo.kv_set(
|
||||
"task:last:snapshot_recent",
|
||||
json.dumps({
|
||||
"name": "snapshot_recent", "ok": True,
|
||||
"started_at": FIXED_NOW - 60, "finished_at": FIXED_NOW - 30,
|
||||
"error": None, "summary": {},
|
||||
}),
|
||||
)
|
||||
DASHBOARD_CACHE.clear()
|
||||
overview = q.overview()
|
||||
assert overview["data_date"] == DATES[-1]
|
||||
assert overview["updated_at"] == FIXED_NOW - 30
|
||||
assert overview["counts"]["total"] == (
|
||||
overview["counts"]["active"]
|
||||
+ overview["counts"]["unfaved"]
|
||||
+ overview["counts"]["removed"]
|
||||
)
|
||||
# 当前=seed(=完成日数值),相邻 previous=完成日本身 → 增量 0
|
||||
assert overview["view_delta_available"] is True
|
||||
assert overview["view_delta"] == 0
|
||||
|
||||
growth = q.list_videos(sort="growth", growth_days=1)
|
||||
assert growth["available"] is True
|
||||
for item in growth["items"]:
|
||||
if item["aid"] in (101, 102):
|
||||
assert item["growth"]["available"] is True
|
||||
assert item["growth"]["delta"] == 0
|
||||
assert item["growth"]["gap_days"] == 1
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def main_test():
|
||||
test_overview()
|
||||
test_video_listing_filters_and_sorting()
|
||||
@@ -643,6 +698,7 @@ def main_test():
|
||||
test_system_next_runs_follow_last_completion_not_wall_clock()
|
||||
test_growth_auto_falls_back_to_one_day()
|
||||
test_snapshot_recent_next_run_follows_completion()
|
||||
test_overview_adjacent_previous_with_seed_and_dual_fields()
|
||||
print("OK: Web 查询层总览/榜单/详情/UP/时长效率/粉丝/候选/系统/访问统计测试全部通过")
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ def write_config(
|
||||
"up_blacklist_seed": [],
|
||||
"schedule": {
|
||||
"discover_minutes": 60,
|
||||
"snapshot_at": "00:01",
|
||||
"snapshot_at": "23:15",
|
||||
"seed_at": "00:01",
|
||||
"candidate_ttl_days": 7,
|
||||
"recent_snapshot_minutes": 60,
|
||||
"recent_video_days": 2,
|
||||
|
||||
Reference in new issue
Block a user