From cf925e876386be97f25a9af364783513c85f6e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 15 Sep 2026 14:50:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20stats=20=E6=8F=90=E5=8F=96=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=20daily=5Fcounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refresh_daily 四指标口径提取为模块级 daily_counts(site, date), site=None 时聚合全部站点,为 dashboard 今日实时位复用做准备 (Task8 审查结论:防两处口径漂移)。语义不变,既有 stats 测试全绿。 Co-Authored-By: Claude Code --- server/tracker/services/stats.py | 36 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/server/tracker/services/stats.py b/server/tracker/services/stats.py index 557dbe4..3c791ba 100644 --- a/server/tracker/services/stats.py +++ b/server/tracker/services/stats.py @@ -3,24 +3,34 @@ from django.utils import timezone from tracker.models import DailyStat, Event, VisitSession, Visitor -def refresh_daily(site, date) -> DailyStat: - """重算某站点某自然日的聚合(幂等,update_or_create)。 +def daily_counts(site, date) -> dict: + """四指标共享口径(site=None 聚合全部站点)。所有指标排除爬虫。 日界取 TIME_ZONE(Asia/Shanghai)自然日:make_aware(datetime.combine(date, time.min))。 口径:pv/uv 均从事件表统计(排除 is_bot),uv=distinct visitor,与 pv 同源; - sessions 按会话 started_at 落入当日且访客非爬虫;new_visitors 按 Visitor.first_seen_at 落入当日(同样排除爬虫访客,四指标口径统一)。 + sessions 按会话 started_at 落入当日且访客非爬虫;new_visitors 按 Visitor.first_seen_at 落入当日。 + refresh_daily 落库与 dashboard 今日实时位共用此函数,防两处口径漂移(Task8 审查结论)。 """ day_start = timezone.make_aware(datetime.combine(date, time.min)) day_end = day_start + timedelta(days=1) - pv = Event.objects.filter(site=site, is_bot=False, - occurred_at__gte=day_start, occurred_at__lt=day_end).count() - uv = Event.objects.filter(site=site, is_bot=False, occurred_at__gte=day_start, - occurred_at__lt=day_end).values("visitor").distinct().count() - sessions = VisitSession.objects.filter(site=site, visitor__is_bot=False, - started_at__gte=day_start, - started_at__lt=day_end).count() - new_v = Visitor.objects.filter(site=site, is_bot=False, first_seen_at__gte=day_start, - first_seen_at__lt=day_end).count() + ev = Event.objects.filter(is_bot=False, + occurred_at__gte=day_start, occurred_at__lt=day_end) + vs = VisitSession.objects.filter(visitor__is_bot=False, + started_at__gte=day_start, started_at__lt=day_end) + vis = Visitor.objects.filter(is_bot=False, + first_seen_at__gte=day_start, first_seen_at__lt=day_end) + if site is not None: + ev = ev.filter(site=site) + vs = vs.filter(site=site) + vis = vis.filter(site=site) + return {"pv": ev.count(), + "uv": ev.values("visitor").distinct().count(), + "sessions": vs.count(), + "new_visitors": vis.count()} + + +def refresh_daily(site, date) -> DailyStat: + """重算某站点某自然日的聚合(幂等,update_or_create)。指标口径见 daily_counts。""" obj, _ = DailyStat.objects.update_or_create(site=site, date=date, - defaults={"pv": pv, "uv": uv, "sessions": sessions, "new_visitors": new_v}) + defaults=daily_counts(site, date)) return obj