feat(analytics): 4h 调度分析链+CLI 回填+full_sync 夜间自愈步
- scheduler: yunke_analytics_tick job(与消息增量同节奏 IntervalTrigger), _analytics_tick 纯 DB——stat_daily_staff 滚动3天刷新 + 敏感词增量扫描, 不抢 _page_lock 无子进程,异常只记日志不断链;today 用 time.time() UTC epoch 喂 day_key,规避服务器非上海时区的 naive now 误判 - cli: staff-daily-stats(全量重建 rollup,幂等)+ sensitive-scan (默认增量水位扫描,--full 首跑建水位/新词回填历史;description 明示 加词不回填历史)+ 幂等补种起步词库 - full_sync_all.sh: daily-stats 后追加 staff-daily-stats(rollup 随 msg_daily_stats 重建同步自愈) - 测试: tick 行为测试(今日 seed→stat_daily_staff 出行)+ 异常吞噬 (monkeypatch 抛错不外抛);同步更新注册断言(3→4 jobs) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -416,10 +416,13 @@ async def test_start_registers_two_jobs_with_expected_triggers(engine, session_m
|
||||
with patch.object(scheduler_module, "AsyncIOScheduler", return_value=spy):
|
||||
await sched.start()
|
||||
|
||||
# Three jobs registered (2026-08-21 加每周全量对齐).
|
||||
assert spy.add_job.call_count == 3
|
||||
# Four jobs registered (2026-08-21 加每周全量对齐;2026-09-04 加 4h 分析链).
|
||||
assert spy.add_job.call_count == 4
|
||||
job_ids = {c.kwargs["id"] for c in spy.add_job.call_args_list}
|
||||
assert job_ids == {"yunke_full_sync", "yunke_message_sync", "yunke_weekly_full_history"}
|
||||
assert job_ids == {
|
||||
"yunke_full_sync", "yunke_message_sync", "yunke_weekly_full_history",
|
||||
"yunke_analytics_tick",
|
||||
}
|
||||
|
||||
# Full sync -> CronTrigger(hour=3); messages -> IntervalTrigger(hours=6).
|
||||
# add_job was called as (func, trigger, kwargs...) so trigger is args[1].
|
||||
@@ -454,6 +457,16 @@ async def test_start_registers_two_jobs_with_expected_triggers(engine, session_m
|
||||
assert weekly_call.args[0].__name__ == "_safe_page_run"
|
||||
assert weekly_call.kwargs["args"] == ["full-history"]
|
||||
|
||||
# 2026-09-04 加 4h 分析链:纯 DB(非 _safe_page_run,无子进程),
|
||||
# IntervalTrigger 与消息增量同节奏,不带 args。
|
||||
tick_call = next(
|
||||
c for c in spy.add_job.call_args_list if c.kwargs["id"] == "yunke_analytics_tick"
|
||||
)
|
||||
assert tick_call.args[0].__name__ == "_analytics_tick"
|
||||
tick_interval = tick_call.args[1]
|
||||
assert tick_interval.interval.total_seconds() == 6 * 3600
|
||||
assert "args" not in tick_call.kwargs
|
||||
|
||||
# coalesce=True + max_instances=1 protects against overlap.
|
||||
for c in spy.add_job.call_args_list:
|
||||
assert c.kwargs["coalesce"] is True
|
||||
|
||||
Reference in New Issue
Block a user