feat: 支持按时区查询活跃用户时间窗

This commit is contained in:
Codex
2026-07-15 06:09:58 +02:00
parent 1b0a7d7bb3
commit aab251173e
7 changed files with 140 additions and 21 deletions
@@ -28,9 +28,17 @@ def credits_all_users(token):
return users
page += 1
def credits_active_usage(token, since):
end_at = datetime.now(timezone.utc)
start_at = credits_window_start(since, end_at)
def credits_active_usage(token, since, active_start, active_end, active_start_utc, active_end_utc, timezone_name):
if since:
end_at = datetime.now(timezone.utc)
start_at = credits_window_start(since, end_at)
selection_kind = "active-users"
else:
start_at = credits_parse_time(active_start_utc)
end_at = credits_parse_time(active_end_utc)
selection_kind = "active-window"
if start_at is None or end_at is None or end_at <= start_at:
raise RuntimeError("credits active window end must be after start")
user_requests = {}
page = 1
while True:
@@ -46,11 +54,19 @@ def credits_active_usage(token, since):
reached_start = True
continue
user_id = row.get("user_id") if row.get("user_id") is not None else row.get("userId")
if user_id is not None and (created_at is None or created_at <= end_at):
if user_id is not None and created_at is not None and start_at <= created_at < end_at:
user_requests[str(user_id)] = user_requests.get(str(user_id), 0) + 1
total = int(data.get("total", 0)) if isinstance(data, dict) else 0
if not batch or len(batch) < 100 or reached_start or page * 100 >= total:
return user_requests, start_at, end_at
return user_requests, start_at, end_at, {
"kind": selection_kind,
"since": since,
"timezone": timezone_name if selection_kind == "active-window" else "UTC",
"startAt": active_start if selection_kind == "active-window" else start_at.isoformat(),
"endAt": active_end if selection_kind == "active-window" else end_at.isoformat(),
"startAtUtc": start_at.isoformat().replace("+00:00", "Z"),
"endAtUtc": end_at.isoformat().replace("+00:00", "Z"),
}
page += 1
def credits_selected_users(token, payload):
@@ -58,8 +74,13 @@ def credits_selected_users(token, payload):
users_by_id = {str(item.get("id")): item for item in users if item.get("id") is not None}
users_by_email = {str(item.get("email") or "").strip().lower(): item for item in users if item.get("email")}
active_since = payload.get("activeSince")
if active_since:
requests, start_at, end_at = credits_active_usage(token, active_since)
active_start = payload.get("activeStart")
active_end = payload.get("activeEnd")
if active_since or (active_start and active_end):
requests, _, _, selection = credits_active_usage(
token, active_since, active_start, active_end,
payload.get("activeStartUtc"), payload.get("activeEndUtc"), payload.get("timezone"),
)
selected = []
excluded = []
for user_id, request_count in requests.items():
@@ -72,7 +93,7 @@ def credits_selected_users(token, payload):
excluded.append({"userId": user.get("id"), "email": user.get("email"), "reason": "inactive-or-deleted"})
else:
selected.append((user, request_count))
return selected, excluded, {"kind": "active-users", "since": active_since, "startAt": start_at.isoformat(), "endAt": end_at.isoformat()}
return selected, excluded, selection
selected = []
seen = set()
for selector in payload.get("users") or []: