"""每天凌晨的积分清算任务。""" from __future__ import annotations import logging from datetime import date, datetime, timedelta, timezone from app.clock import utcnow from app.database import session_scope from app.services.heartbeat import mark_stale_nodes from app.services.yield_engine import SettleSummary, settle_yield_date logger = logging.getLogger(__name__) def run_daily_yield_cron(now: datetime | None = None, yield_date: date | None = None) -> SettleSummary: """计算所有仍在授权期内的节点,并把当日积分写入 node_yields。 调度器在北京时间 00:05 调用本函数。账本按 UTC 日切分,因此入账日期是 调用时刻之前最近一个已经结束的 UTC 日,而不是刚开始的这一天。 同一节点同一天已有流水时跳过,重复触发不会重复入账。 """ moment = now or utcnow() if moment.tzinfo is None: raise ValueError("清算时间必须带时区") target = yield_date or (moment.astimezone(timezone.utc).date() - timedelta(days=1)) with session_scope() as session: stale = mark_stale_nodes(session, moment) summary = settle_yield_date(session, target, moment) logger.info( "daily yield settled date=%s created=%s skipped=%s points=%s partial=%s stale_nodes=%s", summary.yield_date, summary.created, summary.skipped, format(summary.total_points, "f"), summary.is_partial, len(stale), ) return summary