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:
bot-ci committed 2026-11-03 02:35:13 +08:00
1 parent dcf37c4a1d
commit f6bef59e07
7 files changed
+630 -24

No files matched your search

+69
View File
@@ -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: 每日快照、断点续跑、失败占位与粉丝采集测试全部通过")