SilverElixir commited on
Commit
fe3cf23
·
1 Parent(s): 3704a0f

stats: active chats counted over trailing 24h, total kept

Browse files
Files changed (2) hide show
  1. lumen_commands.py +7 -1
  2. tests/test_bot_commands.py +36 -0
lumen_commands.py CHANGED
@@ -419,6 +419,12 @@ async def cmd_stats(message: Message) -> None:
419
  bot._reset_quota_if_new_day()
420
 
421
  total_chats = len(bot.chat_state)
 
 
 
 
 
 
422
  uptime_sec = int(time.monotonic() - bot._PROCESS_START_MONOTONIC)
423
  uptime_str = f"{uptime_sec // 3600}ч {(uptime_sec % 3600) // 60}м"
424
 
@@ -459,7 +465,7 @@ async def cmd_stats(message: Message) -> None:
459
 
460
  text = (
461
  f"<b>Статистика Lumen</b>\n\n"
462
- f"Активных чатов: {total_chats}\n"
463
  f"Аптайм процесса: {uptime_str}\n"
464
  f"Счётчики квоты за сутки: {quota_day} (America/Los_Angeles, сбрасываются автоматически)\n\n"
465
  f"<b>Gemini — запросов по моделям:</b>\n{gemini_text}\n\n"
 
419
  bot._reset_quota_if_new_day()
420
 
421
  total_chats = len(bot.chat_state)
422
+ # "Активные" — с живой активностью за сутки, а не все записи в памяти (те копятся до пруна на 5000).
423
+ _active_cutoff = time.monotonic() - 24 * 3600
424
+ active_chats = sum(
425
+ 1 for s in bot.chat_state.values()
426
+ if isinstance(s, dict) and s.get("last_activity", 0) >= _active_cutoff
427
+ )
428
  uptime_sec = int(time.monotonic() - bot._PROCESS_START_MONOTONIC)
429
  uptime_str = f"{uptime_sec // 3600}ч {(uptime_sec % 3600) // 60}м"
430
 
 
465
 
466
  text = (
467
  f"<b>Статистика Lumen</b>\n\n"
468
+ f"Активных чатов (24ч): {active_chats} (всего: {total_chats})\n"
469
  f"Аптайм процесса: {uptime_str}\n"
470
  f"Счётчики квоты за сутки: {quota_day} (America/Los_Angeles, сбрасываются автоматически)\n\n"
471
  f"<b>Gemini — запросов по моделям:</b>\n{gemini_text}\n\n"
tests/test_bot_commands.py CHANGED
@@ -56,6 +56,42 @@ def test_cmd_logs_flushes_the_listeners_real_handlers_not_root():
56
  bot._LOG_LISTENER = original_listener
57
 
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  def test_cmd_stats_hides_idle_models_and_shows_totals():
60
  # Проф-вид /stats: нули по мёртвым моделям не мусорят, у каждого провайдера итог и остаток лимита.
61
  chat_id = 999811
 
56
  bot._LOG_LISTENER = original_listener
57
 
58
 
59
+ def test_cmd_stats_counts_only_recently_active_chats():
60
+ # "Активных" — с активностью за 24ч: молчащий год чат в счёт не идёт, но виден в "всего".
61
+ chat_id = 999812
62
+ incoming = _FakeIncomingMessage(chat_id)
63
+ incoming.from_user = SimpleNamespace(id=777002)
64
+ sent = {}
65
+
66
+ async def fake_tg_call(method, *args, **kwargs):
67
+ sent["text"] = args[0] if args else kwargs.get("text", "")
68
+ return SimpleNamespace()
69
+
70
+ original_owner = bot.OWNER_ID
71
+ original_tg_call = bot._tg_call
72
+ real_quota = dict(bot.GLOBAL_QUOTA)
73
+ real_states = dict(bot.chat_state)
74
+ now = time.monotonic()
75
+ bot.OWNER_ID = 777002
76
+ bot._tg_call = fake_tg_call
77
+ bot.GLOBAL_QUOTA.clear()
78
+ bot.GLOBAL_QUOTA.update({"quota_day": bot._current_quota_day()})
79
+ bot.chat_state.clear()
80
+ bot.chat_state.update({
81
+ 111: {"history": [], "last_activity": now - 3600},
82
+ 222: {"history": [], "last_activity": now - 25 * 3600},
83
+ })
84
+ try:
85
+ asyncio.run(bot.cmd_stats(incoming))
86
+ assert "Активных чатов (24ч): 1 (всего: 2)" in sent["text"]
87
+ finally:
88
+ bot.OWNER_ID = original_owner
89
+ bot._tg_call = original_tg_call
90
+ bot.GLOBAL_QUOTA.clear()
91
+ bot.GLOBAL_QUOTA.update(real_quota)
92
+ bot.chat_state.clear()
93
+ bot.chat_state.update(real_states)
94
+ # Проф-вид /stats: нули по мёртвым моделям не мусорят, у каждого провайдера итог и остаток лимита.
95
  def test_cmd_stats_hides_idle_models_and_shows_totals():
96
  # Проф-вид /stats: нули по мёртвым моделям не мусорят, у каждого провайдера итог и остаток лимита.
97
  chat_id = 999811