feat: home growth auto(7→1) and hourly recent-video snapshot

- Add analytics.home_growth_days (auto|1|7); API growth_days=auto tries 7 then 1
- Homepage shows active window and growth-board link; clearer empty reason
- New snapshot_recent scheduler task (schedule.recent_snapshot_minutes/days)
- run_recent_videos refreshes new aids only; never touches completed/backup
- Full 00:01 job keeps ok=1 skip (covers hourly refresh within 1h); mutex with recent
This commit is contained in:
bot-ci committed 2026-11-02 10:00:02 +08:00
1 parent 04716aec1b
commit 9163f47b76
15 files changed
+496 -22

No files matched your search

+36 -5
View File
@@ -699,7 +699,7 @@ class DashboardQueries:
sort: str = "heat",
within_days: int | None = None,
year: int | None = None,
growth_days: int = 7,
growth_days: int | str = 7,
folder_id: int | None = None,
q: str | None = None,
page: int = 1,
@@ -734,7 +734,7 @@ class DashboardQueries:
sort: str = "heat",
within_days: int | None = None,
year: int | None = None,
growth_days: int = 7,
growth_days: int | str = 7,
folder_id: int | None = None,
q: str | None = None,
page: int = 1,
@@ -742,6 +742,25 @@ class DashboardQueries:
) -> dict[str, Any]:
if sort not in VIDEO_SORTS:
raise ValueError(f"不支持的排序: {sort}")
if growth_days == "auto":
preferred = self.config_store.get().analytics.home_growth_days
if preferred in (1, 7):
growth_days = int(preferred)
else:
trial = self._list_videos_uncached(
sort=sort,
within_days=within_days,
year=year,
growth_days=7,
folder_id=folder_id,
q=q,
page=page,
page_size=page_size,
)
if trial.get("available") and int(trial.get("total") or 0) > 0:
return trial
growth_days = 1
growth_days = int(growth_days)
if growth_days < 1 or growth_days > 365:
raise ValueError("growth_days 必须在 1~365 之间")
page = max(1, int(page))
@@ -900,7 +919,9 @@ class DashboardQueries:
reason = None
if sort in {"growth", "growth_rate"} and not growth_available:
available = False
reason = "需要至少两天的快照才能计算增长"
reason = (
f"近 {growth_days} 日增长需要至少 {growth_days + 1} 天快照"
)
duration_filter = None
if sort in duration_sorts:
duration_filter = {
@@ -1571,6 +1592,7 @@ class DashboardQueries:
"min_view_for_ranking": cfg.analytics.min_view_for_ranking,
"video_shrink_minutes": cfg.analytics.video_shrink_minutes,
"shrink_minutes": cfg.analytics.shrink_minutes,
"home_growth_days": cfg.analytics.home_growth_days,
},
"heat_weights": {
metric: float(value)
@@ -1747,23 +1769,30 @@ class DashboardQueries:
running = self._task_running(conn)
now = self._now()
discover_interval = int(cfg.schedule.discover_minutes) * 60
recent_interval = int(cfg.schedule.recent_snapshot_minutes) * 60
next_runs = {
"fans": int(next_daily_run(now, cfg.schedule.snapshot_at)),
"snapshot": int(next_daily_run(now, cfg.schedule.snapshot_at)),
"discover": int(now + discover_interval),
"snapshot_recent": int(now + recent_interval),
}
for name, key in (
("fans", "fans"),
("snapshot", "snapshot"),
("discover", "discovery"),
("snapshot_recent", "snapshot_recent"),
):
record = tasks.get(key)
if not (isinstance(record, dict) and record.get("finished_at")):
continue
finished_at = int(record["finished_at"])
if name == "discover":
if name in {"discover", "snapshot_recent"}:
# 只按上次完成推下一档;进行中由 running 展示,不按墙钟抢跑。
next_runs[name] = finished_at + discover_interval
interval = (
recent_interval if name == "snapshot_recent"
else discover_interval
)
next_runs[name] = finished_at + interval
else:
next_runs[name] = int(
next_daily_run(finished_at, cfg.schedule.snapshot_at)
@@ -1805,6 +1834,8 @@ class DashboardQueries:
"discover_minutes": cfg.schedule.discover_minutes,
"snapshot_at": cfg.schedule.snapshot_at,
"candidate_ttl_days": cfg.schedule.candidate_ttl_days,
"recent_snapshot_minutes": cfg.schedule.recent_snapshot_minutes,
"recent_video_days": cfg.schedule.recent_video_days,
},
"telegram_enabled": bool(cfg.telegram.enabled),
}