fix(scheduler): daily slot off-plan + cross-midnight resume (#8)
- Cross-midnight: unfinished yesterday seals ASAP with yesterday's stat_date; force seal ignores today's snapshot_at gate when stat_date < today. - Off-plan already_completed: compare completion started_at (not finished_at) to today's planned slot; earlier → still run at slot for all daily tasks. - Cold start: bootstrap task:state from task:last / seed markers. - ensure_pending: do not glue new due onto a different day's unfinished state. - run_if_needed(force): keep_scraped True for seal, False for CLI --force. - Scheduler due_reader: pick up disk due_at each loop (seed→evening seal).
This commit is contained in:
1 parent
dcf37c4a1d
commit
f6bef59e07
7 files changed
+630
-24
No files matched your search
@@ -705,6 +705,73 @@ def test_seal_keep_scraped_skips_view_progress_punches_seed():
|
||||
db.close()
|
||||
|
||||
|
||||
|
||||
def test_force_keep_scraped_false_rescrapes_view_progress():
|
||||
"""C: CLI --force 路径 keep_scraped=False 须重采已有 view 进度行。"""
|
||||
clock = 1_800_000_000
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
transport = SnapshotTransport({
|
||||
1: view_for(1, view=999),
|
||||
2: view_for(2, view=222),
|
||||
})
|
||||
db, repo, service = make_service(tmp, transport)
|
||||
try:
|
||||
add_video(repo, 1)
|
||||
add_video(repo, 2)
|
||||
today = DATE
|
||||
for aid, view, src in ((1, 111, "view"), (2, 20, "seed")):
|
||||
repo.upsert_snapshot(
|
||||
today, aid,
|
||||
metrics={"view": view, "like": 1, "coin": 0, "favorite": 0,
|
||||
"share": 0, "reply": 0, "danmaku": 0},
|
||||
ok=True, err=None, captured_at=clock, src=src,
|
||||
)
|
||||
repo.kv_set(service.seeded_key(today), "1")
|
||||
forced = service.run_if_needed(
|
||||
date=today, force=True, keep_scraped=False,
|
||||
)
|
||||
assert forced.completed is True
|
||||
# 两条都重采
|
||||
assert transport.count("/web-interface/view") == 2
|
||||
assert int(repo.get_snapshot(today, 1)["view"]) == 999
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_force_default_keep_scraped_skips_view_progress():
|
||||
"""C: force 默认 keep_scraped=True(seal)跳过已 view 行。"""
|
||||
clock = 1_800_000_000
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
transport = SnapshotTransport({
|
||||
1: view_for(1, view=999),
|
||||
2: view_for(2, view=222),
|
||||
})
|
||||
db, repo, service = make_service(tmp, transport)
|
||||
try:
|
||||
add_video(repo, 1)
|
||||
add_video(repo, 2)
|
||||
today = DATE
|
||||
repo.upsert_snapshot(
|
||||
today, 1,
|
||||
metrics={"view": 111, "like": 1, "coin": 0, "favorite": 0,
|
||||
"share": 0, "reply": 0, "danmaku": 0},
|
||||
ok=True, err=None, captured_at=clock, src="view",
|
||||
)
|
||||
repo.upsert_snapshot(
|
||||
today, 2,
|
||||
metrics={"view": 20, "like": 1, "coin": 0, "favorite": 0,
|
||||
"share": 0, "reply": 0, "danmaku": 0},
|
||||
ok=True, err=None, captured_at=clock, src="seed",
|
||||
)
|
||||
repo.kv_set(service.seeded_key(today), "1")
|
||||
sealed = service.run_if_needed(date=today, force=True)
|
||||
assert sealed.completed is True
|
||||
assert transport.count("/web-interface/view") == 1
|
||||
assert int(repo.get_snapshot(today, 1)["view"]) == 111
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main():
|
||||
test_snapshot_success_resume_and_force()
|
||||
test_resume_skips_ok_and_retries_failed()
|
||||
@@ -724,6 +791,8 @@ def main():
|
||||
test_seed_from_last_completed_does_not_mark_completed()
|
||||
test_seeded_day_skips_without_force_and_seals_with_force()
|
||||
test_seal_keep_scraped_skips_view_progress_punches_seed()
|
||||
test_force_keep_scraped_false_rescrapes_view_progress()
|
||||
test_force_default_keep_scraped_skips_view_progress()
|
||||
print("OK: 每日快照、断点续跑、失败占位与粉丝采集测试全部通过")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user