194 lines
6.4 KiB
Python
194 lines
6.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import os
|
||
|
|
import sqlite3
|
||
|
|
from datetime import datetime, timedelta, timezone
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
try:
|
||
|
|
from zoneinfo import ZoneInfo
|
||
|
|
except Exception:
|
||
|
|
ZoneInfo = None # type: ignore[assignment]
|
||
|
|
|
||
|
|
|
||
|
|
def compute_target_week_key(
|
||
|
|
now_local: datetime,
|
||
|
|
settle_weekday: int,
|
||
|
|
settle_hour: int,
|
||
|
|
settle_minute: int,
|
||
|
|
) -> str:
|
||
|
|
week_start = (now_local - timedelta(days=now_local.isoweekday() - 1)).replace(
|
||
|
|
hour=0,
|
||
|
|
minute=0,
|
||
|
|
second=0,
|
||
|
|
microsecond=0,
|
||
|
|
)
|
||
|
|
settle_dt = week_start + timedelta(days=settle_weekday - 1)
|
||
|
|
settle_dt = settle_dt.replace(
|
||
|
|
hour=settle_hour,
|
||
|
|
minute=settle_minute,
|
||
|
|
second=0,
|
||
|
|
microsecond=0,
|
||
|
|
)
|
||
|
|
ref = now_local - timedelta(days=7 if now_local >= settle_dt else 14)
|
||
|
|
iso_year, iso_week, _ = ref.isocalendar()
|
||
|
|
return f"{iso_year}-W{iso_week:02d}"
|
||
|
|
|
||
|
|
|
||
|
|
def resolve_tz(tz_name: str):
|
||
|
|
if ZoneInfo is None:
|
||
|
|
return timezone(timedelta(hours=8)), "UTC+08:00"
|
||
|
|
try:
|
||
|
|
return ZoneInfo(tz_name), tz_name
|
||
|
|
except Exception:
|
||
|
|
return ZoneInfo("Asia/Shanghai"), "Asia/Shanghai"
|
||
|
|
|
||
|
|
|
||
|
|
def get_candidates(conn: sqlite3.Connection, week_key: str, limit: int):
|
||
|
|
conn.row_factory = sqlite3.Row
|
||
|
|
rows = conn.execute(
|
||
|
|
"""
|
||
|
|
SELECT *
|
||
|
|
FROM (
|
||
|
|
SELECT
|
||
|
|
u.telegram_id,
|
||
|
|
u.username,
|
||
|
|
lower(trim(COALESCE(u.supabase_user_id, ''))) AS supabase_user_id,
|
||
|
|
COALESCE(u.points, 0) AS points,
|
||
|
|
COALESCE(u.message_count, 0) AS message_count,
|
||
|
|
COALESCE(a.points,
|
||
|
|
CASE
|
||
|
|
WHEN u.weekly_points_week = ? THEN COALESCE(u.weekly_points, 0)
|
||
|
|
ELSE 0
|
||
|
|
END
|
||
|
|
) AS weekly_points
|
||
|
|
FROM users u
|
||
|
|
LEFT JOIN weekly_points_archive a
|
||
|
|
ON a.telegram_id = u.telegram_id
|
||
|
|
AND a.week_key = ?
|
||
|
|
) ranked
|
||
|
|
WHERE weekly_points > 0
|
||
|
|
ORDER BY weekly_points DESC, points DESC, message_count DESC, telegram_id ASC
|
||
|
|
LIMIT ?
|
||
|
|
""",
|
||
|
|
(week_key, week_key, max(1, int(limit))),
|
||
|
|
).fetchall()
|
||
|
|
return [dict(r) for r in rows]
|
||
|
|
|
||
|
|
|
||
|
|
def get_settle_run(conn: sqlite3.Connection, week_key: str) -> Optional[sqlite3.Row]:
|
||
|
|
conn.row_factory = sqlite3.Row
|
||
|
|
return conn.execute(
|
||
|
|
"""
|
||
|
|
SELECT week_key, settled_at, winners_count
|
||
|
|
FROM weekly_reward_runs
|
||
|
|
WHERE week_key = ?
|
||
|
|
LIMIT 1
|
||
|
|
""",
|
||
|
|
(week_key,),
|
||
|
|
).fetchone()
|
||
|
|
|
||
|
|
|
||
|
|
def get_recent_runs(conn: sqlite3.Connection, limit: int = 5):
|
||
|
|
conn.row_factory = sqlite3.Row
|
||
|
|
rows = conn.execute(
|
||
|
|
"""
|
||
|
|
SELECT week_key, settled_at, winners_count
|
||
|
|
FROM weekly_reward_runs
|
||
|
|
ORDER BY settled_at DESC
|
||
|
|
LIMIT ?
|
||
|
|
""",
|
||
|
|
(max(1, int(limit)),),
|
||
|
|
).fetchall()
|
||
|
|
return [dict(r) for r in rows]
|
||
|
|
|
||
|
|
|
||
|
|
def get_recent_payouts(conn: sqlite3.Connection, limit: int = 20):
|
||
|
|
conn.row_factory = sqlite3.Row
|
||
|
|
rows = conn.execute(
|
||
|
|
"""
|
||
|
|
SELECT week_key, telegram_id, username, rank, points_bonus, pro_days, pro_granted, pro_error, created_at
|
||
|
|
FROM weekly_reward_payouts
|
||
|
|
ORDER BY created_at DESC
|
||
|
|
LIMIT ?
|
||
|
|
""",
|
||
|
|
(max(1, int(limit)),),
|
||
|
|
).fetchall()
|
||
|
|
return [dict(r) for r in rows]
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
parser = argparse.ArgumentParser(description="Check PolyWeather weekly reward settlement status.")
|
||
|
|
parser.add_argument("--db", default=os.getenv("POLYWEATHER_DB_PATH", "data/polyweather.db"))
|
||
|
|
parser.add_argument("--timezone", default=os.getenv("POLYWEATHER_WEEKLY_REWARD_TIMEZONE", "Asia/Shanghai"))
|
||
|
|
parser.add_argument("--settle-weekday", type=int, default=int(os.getenv("POLYWEATHER_WEEKLY_REWARD_SETTLE_WEEKDAY", "1")))
|
||
|
|
parser.add_argument("--settle-hour", type=int, default=int(os.getenv("POLYWEATHER_WEEKLY_REWARD_SETTLE_HOUR", "0")))
|
||
|
|
parser.add_argument("--settle-minute", type=int, default=int(os.getenv("POLYWEATHER_WEEKLY_REWARD_SETTLE_MINUTE", "5")))
|
||
|
|
parser.add_argument("--top", type=int, default=10)
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
settle_weekday = min(7, max(1, int(args.settle_weekday)))
|
||
|
|
settle_hour = min(23, max(0, int(args.settle_hour)))
|
||
|
|
settle_minute = min(59, max(0, int(args.settle_minute)))
|
||
|
|
tz, tz_label = resolve_tz(str(args.timezone).strip() or "Asia/Shanghai")
|
||
|
|
now_local = datetime.now(tz)
|
||
|
|
week_key = compute_target_week_key(
|
||
|
|
now_local,
|
||
|
|
settle_weekday=settle_weekday,
|
||
|
|
settle_hour=settle_hour,
|
||
|
|
settle_minute=settle_minute,
|
||
|
|
)
|
||
|
|
|
||
|
|
print("=== Weekly Reward Status ===")
|
||
|
|
print(f"db={args.db}")
|
||
|
|
print(f"now_local={now_local.isoformat()}")
|
||
|
|
print(f"timezone={tz_label}")
|
||
|
|
print(f"settle_rule=weekday:{settle_weekday} time:{settle_hour:02d}:{settle_minute:02d}")
|
||
|
|
print(f"target_week={week_key}")
|
||
|
|
|
||
|
|
with sqlite3.connect(args.db) as conn:
|
||
|
|
run = get_settle_run(conn, week_key)
|
||
|
|
print(f"settled={bool(run)}")
|
||
|
|
if run:
|
||
|
|
print(f"settled_at={run['settled_at']} winners_count={run['winners_count']}")
|
||
|
|
|
||
|
|
candidates = get_candidates(conn, week_key, args.top)
|
||
|
|
print(f"candidates={len(candidates)}")
|
||
|
|
for idx, row in enumerate(candidates, start=1):
|
||
|
|
print(
|
||
|
|
f" {idx}. {row.get('username')} "
|
||
|
|
f"(tg={row.get('telegram_id')}, weekly={row.get('weekly_points')}, total={row.get('points')})"
|
||
|
|
)
|
||
|
|
|
||
|
|
print("\nrecent_runs:")
|
||
|
|
runs = get_recent_runs(conn, limit=5)
|
||
|
|
if not runs:
|
||
|
|
print(" (none)")
|
||
|
|
else:
|
||
|
|
for row in runs:
|
||
|
|
print(
|
||
|
|
f" - {row.get('week_key')} settled_at={row.get('settled_at')} "
|
||
|
|
f"winners={row.get('winners_count')}"
|
||
|
|
)
|
||
|
|
|
||
|
|
print("\nrecent_payouts:")
|
||
|
|
payouts = get_recent_payouts(conn, limit=20)
|
||
|
|
if not payouts:
|
||
|
|
print(" (none)")
|
||
|
|
else:
|
||
|
|
for row in payouts:
|
||
|
|
print(
|
||
|
|
f" - week={row.get('week_key')} rank={row.get('rank')} "
|
||
|
|
f"user={row.get('username')} tg={row.get('telegram_id')} "
|
||
|
|
f"+{row.get('points_bonus')}pts +{row.get('pro_days')}dPro "
|
||
|
|
f"pro_granted={row.get('pro_granted')}"
|
||
|
|
)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|
||
|
|
|