smart-bot-factory 0.3.7__py3-none-any.whl → 0.3.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of smart-bot-factory might be problematic. Click here for more details.

Files changed (45) hide show
  1. smart_bot_factory/admin/__init__.py +7 -7
  2. smart_bot_factory/admin/admin_events.py +483 -383
  3. smart_bot_factory/admin/admin_logic.py +234 -158
  4. smart_bot_factory/admin/admin_manager.py +68 -53
  5. smart_bot_factory/admin/admin_tester.py +46 -40
  6. smart_bot_factory/admin/timeout_checker.py +201 -153
  7. smart_bot_factory/aiogram_calendar/__init__.py +11 -3
  8. smart_bot_factory/aiogram_calendar/common.py +12 -18
  9. smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
  10. smart_bot_factory/aiogram_calendar/schemas.py +49 -28
  11. smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
  12. smart_bot_factory/analytics/analytics_manager.py +414 -392
  13. smart_bot_factory/cli.py +204 -148
  14. smart_bot_factory/config.py +123 -102
  15. smart_bot_factory/core/bot_utils.py +474 -332
  16. smart_bot_factory/core/conversation_manager.py +287 -200
  17. smart_bot_factory/core/decorators.py +1129 -749
  18. smart_bot_factory/core/message_sender.py +287 -266
  19. smart_bot_factory/core/router.py +170 -100
  20. smart_bot_factory/core/router_manager.py +121 -83
  21. smart_bot_factory/core/states.py +4 -3
  22. smart_bot_factory/creation/__init__.py +1 -1
  23. smart_bot_factory/creation/bot_builder.py +320 -242
  24. smart_bot_factory/creation/bot_testing.py +440 -365
  25. smart_bot_factory/dashboard/__init__.py +1 -3
  26. smart_bot_factory/event/__init__.py +2 -7
  27. smart_bot_factory/handlers/handlers.py +676 -472
  28. smart_bot_factory/integrations/openai_client.py +218 -168
  29. smart_bot_factory/integrations/supabase_client.py +928 -637
  30. smart_bot_factory/message/__init__.py +18 -22
  31. smart_bot_factory/router/__init__.py +2 -2
  32. smart_bot_factory/setup_checker.py +162 -126
  33. smart_bot_factory/supabase/__init__.py +1 -1
  34. smart_bot_factory/supabase/client.py +631 -515
  35. smart_bot_factory/utils/__init__.py +2 -3
  36. smart_bot_factory/utils/debug_routing.py +38 -27
  37. smart_bot_factory/utils/prompt_loader.py +153 -120
  38. smart_bot_factory/utils/user_prompt_loader.py +55 -56
  39. smart_bot_factory/utm_link_generator.py +123 -116
  40. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/METADATA +3 -1
  41. smart_bot_factory-0.3.8.dist-info/RECORD +59 -0
  42. smart_bot_factory-0.3.7.dist-info/RECORD +0 -59
  43. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/WHEEL +0 -0
  44. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
  45. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/licenses/LICENSE +0 -0
@@ -4,262 +4,293 @@
4
4
 
5
5
  import asyncio
6
6
  import logging
7
- from datetime import datetime, timezone
8
- from pathlib import Path
9
- import sys
10
7
  import os
8
+ import sys
9
+ from datetime import datetime, timezone, timedelta
10
+ from pathlib import Path
11
11
  from typing import Optional
12
12
 
13
+ from ..admin.admin_manager import AdminManager
13
14
  from ..config import Config
14
- from ..integrations.supabase_client import SupabaseClient
15
15
  from ..core.conversation_manager import ConversationManager
16
- from ..admin.admin_manager import AdminManager
16
+ from ..integrations.supabase_client import SupabaseClient
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
20
+
20
21
  def setup_bot_environment(bot_name: str = "growthmed-october-24") -> Optional[Path]:
21
22
  """Настраивает окружение для указанного бота с автоопределением BOT_ID"""
22
23
  root_dir = Path(os.getcwd()) # Используем текущую директорию как корневую
23
- config_dir = root_dir / 'bots' / bot_name
24
-
24
+ config_dir = root_dir / "bots" / bot_name
25
+
25
26
  logger.info(f"🔍 Ищем конфигурацию бота в: {config_dir}")
26
-
27
+
27
28
  # Сохраняем оригинальный путь для возврата
28
29
  original_cwd = os.getcwd()
29
-
30
+
30
31
  if not config_dir.exists():
31
32
  logger.error(f"❌ Папка конфигурации не найдена: {config_dir}")
32
- logger.info(f" Доступные боты:")
33
- bots_dir = root_dir / 'bots'
33
+ logger.info(" Доступные боты:")
34
+ bots_dir = root_dir / "bots"
34
35
  if bots_dir.exists():
35
36
  for bot_dir in bots_dir.iterdir():
36
37
  if bot_dir.is_dir():
37
38
  logger.info(f" - {bot_dir.name}")
38
39
  return None
39
-
40
+
40
41
  # Проверяем наличие промптов
41
- prompts_dir = config_dir / 'prompts'
42
+ prompts_dir = config_dir / "prompts"
42
43
  if not prompts_dir.exists():
43
44
  logger.error(f"❌ Папка с промптами не найдена: {prompts_dir}")
44
45
  return None
45
-
46
+
46
47
  logger.info(f"✅ Найдена папка промптов: {prompts_dir}")
47
-
48
+
48
49
  # Устанавливаем BOT_ID из имени бота
49
- os.environ['BOT_ID'] = bot_name
50
+ os.environ["BOT_ID"] = bot_name
50
51
  logger.info(f"🤖 Автоматически установлен BOT_ID: {bot_name}")
51
-
52
+
52
53
  # Загружаем .env из конфигурации бота
53
- env_file = config_dir / '.env'
54
+ env_file = config_dir / ".env"
54
55
  if env_file.exists():
55
56
  logger.info(f"🔧 Загружаем .env из: {env_file}")
56
57
  from dotenv import load_dotenv
58
+
57
59
  load_dotenv(env_file)
58
60
  else:
59
61
  logger.error(f"❌ Файл .env не найден: {env_file}")
60
62
  return None
61
-
63
+
62
64
  # Сохраняем текущую директорию и меняем её
63
65
  original_cwd = os.getcwd()
64
66
  os.chdir(str(config_dir))
65
67
  logger.info(f"📁 Изменена рабочая директория: {os.getcwd()}")
66
-
68
+
67
69
  # Проверяем что промпты доступны относительно новой директории
68
- local_prompts = Path('prompts')
70
+ local_prompts = Path("prompts")
69
71
  if local_prompts.exists():
70
- logger.info(f"✅ Промпты доступны из рабочей директории: {local_prompts.absolute()}")
72
+ logger.info(
73
+ f"✅ Промпты доступны из рабочей директории: {local_prompts.absolute()}"
74
+ )
71
75
  else:
72
- logger.error(f"❌ Промпты не найдены в рабочей директории: {local_prompts.absolute()}")
76
+ logger.error(
77
+ f"❌ Промпты не найдены в рабочей директории: {local_prompts.absolute()}"
78
+ )
73
79
  os.chdir(original_cwd) # Восстанавливаем директорию
74
80
  return None
75
-
81
+
76
82
  return config_dir
77
83
 
84
+
78
85
  async def debug_timeout_issue(bot_name: str = "growthmed-october-24") -> bool:
79
86
  """
80
87
  Диагностирует проблему с таймаутом диалогов
81
-
88
+
82
89
  Args:
83
90
  bot_name: Имя бота для диагностики
84
-
91
+
85
92
  Returns:
86
93
  bool: True если диагностика прошла успешно, False если найдены проблемы
87
94
  """
88
95
  logger.info("🔍 Диагностика проблемы с таймаутом диалогов\n")
89
96
  logger.info(f"🚀 Диагностика для бота: {bot_name}")
90
97
  logger.info(f"🤖 Bot ID будет автоопределен как: {bot_name}\n")
91
-
98
+
92
99
  # Настраиваем окружение для бота (автоматически устанавливает BOT_ID)
93
100
  config_dir = setup_bot_environment(bot_name)
94
101
  if not config_dir:
95
102
  return False
96
-
103
+
97
104
  # Инициализируем конфигурацию
98
105
  config = Config()
99
- logger.info(f"📋 Конфигурация:")
106
+ logger.info("📋 Конфигурация:")
100
107
  logger.info(f" BOT_ID: {config.BOT_ID}")
101
- logger.info(f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}")
108
+ logger.info(
109
+ f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}"
110
+ )
102
111
  logger.info(f" PROMT_FILES_DIR: {config.PROMT_FILES_DIR}")
103
112
  logger.info(f" Найдено промпт-файлов: {len(config.PROMPT_FILES)}")
104
113
  logger.info("")
105
-
114
+
106
115
  # Проверяем часовые пояса
107
- logger.info(f"🕐 Временные зоны:")
116
+ logger.info("🕐 Временные зоны:")
108
117
  now_naive = datetime.now()
109
118
  now_utc = datetime.now(timezone.utc)
110
119
  logger.info(f" datetime.now() (локальное): {now_naive}")
111
120
  logger.info(f" datetime.now(timezone.utc): {now_utc}")
112
- logger.info(f" Разница: {(now_naive.replace(tzinfo=timezone.utc) - now_utc).total_seconds() / 3600:.1f} часов")
121
+ logger.info(
122
+ f" Разница: {(now_naive.replace(tzinfo=timezone.utc) - now_utc).total_seconds() / 3600:.1f} часов"
123
+ )
113
124
  logger.info("")
114
-
125
+
115
126
  # Проверяем активные диалоги в БД
116
127
  try:
117
128
  supabase_client = SupabaseClient(config.SUPABASE_URL, config.SUPABASE_KEY)
118
129
  await supabase_client.initialize()
119
-
120
- response = supabase_client.client.table('admin_user_conversations').select(
121
- 'id', 'admin_id', 'user_id', 'started_at', 'auto_end_at'
122
- ).eq('status', 'active').execute()
123
-
130
+
131
+ response = (
132
+ supabase_client.client.table("admin_user_conversations")
133
+ .select("id", "admin_id", "user_id", "started_at", "auto_end_at")
134
+ .eq("status", "active")
135
+ .execute()
136
+ )
137
+
124
138
  conversations = response.data
125
-
139
+
126
140
  logger.info(f"📊 Активные диалоги в БД: {len(conversations)}")
127
-
141
+
128
142
  problems_found = 0
129
-
143
+
130
144
  for i, conv in enumerate(conversations, 1):
131
145
  logger.info(f"\n{i}. Диалог ID: {conv['id']}")
132
- logger.info(f" Админ: {conv['admin_id']}, Пользователь: {conv['user_id']}")
133
-
146
+ logger.info(
147
+ f" Админ: {conv['admin_id']}, Пользователь: {conv['user_id']}"
148
+ )
149
+
134
150
  # Парсим времена
135
- started_at = conv['started_at']
136
- auto_end_at = conv['auto_end_at']
137
-
151
+ started_at = conv["started_at"]
152
+ auto_end_at = conv["auto_end_at"]
153
+
138
154
  logger.info(f" started_at (сырое): {started_at}")
139
155
  logger.info(f" auto_end_at (сырое): {auto_end_at}")
140
-
156
+
141
157
  try:
142
158
  # Парсим как делает код
143
- if started_at.endswith('Z'):
144
- start_time = datetime.fromisoformat(started_at.replace('Z', '+00:00'))
145
- elif '+' in started_at or started_at.count(':') >= 3:
159
+ if started_at.endswith("Z"):
160
+ start_time = datetime.fromisoformat(
161
+ started_at.replace("Z", "+00:00")
162
+ )
163
+ elif "+" in started_at or started_at.count(":") >= 3:
146
164
  start_time = datetime.fromisoformat(started_at)
147
165
  else:
148
166
  naive_time = datetime.fromisoformat(started_at)
149
167
  start_time = naive_time.replace(tzinfo=timezone.utc)
150
-
151
- if auto_end_at.endswith('Z'):
152
- end_time = datetime.fromisoformat(auto_end_at.replace('Z', '+00:00'))
153
- elif '+' in auto_end_at or auto_end_at.count(':') >= 3:
168
+
169
+ if auto_end_at.endswith("Z"):
170
+ end_time = datetime.fromisoformat(
171
+ auto_end_at.replace("Z", "+00:00")
172
+ )
173
+ elif "+" in auto_end_at or auto_end_at.count(":") >= 3:
154
174
  end_time = datetime.fromisoformat(auto_end_at)
155
175
  else:
156
176
  naive_time = datetime.fromisoformat(auto_end_at)
157
177
  end_time = naive_time.replace(tzinfo=timezone.utc)
158
-
178
+
159
179
  logger.info(f" start_time (парсед): {start_time}")
160
180
  logger.info(f" end_time (парсед): {end_time}")
161
-
181
+
162
182
  # Вычисляем длительность диалога
163
183
  planned_duration = end_time - start_time
164
184
  planned_minutes = int(planned_duration.total_seconds() / 60)
165
185
  logger.info(f" Запланированная длительность: {planned_minutes} минут")
166
-
186
+
167
187
  # Проверяем соответствие конфигу
168
188
  expected = config.ADMIN_SESSION_TIMEOUT_MINUTES
169
189
  if planned_minutes == expected:
170
190
  logger.info(f" ✅ Соответствует конфигу ({expected} мин)")
171
191
  else:
172
- logger.error(f" ❌ НЕ соответствует конфигу! Ожидалось {expected} мин, получили {planned_minutes} мин")
192
+ logger.error(
193
+ f" ❌ НЕ соответствует конфигу! Ожидалось {expected} мин, получили {planned_minutes} мин"
194
+ )
173
195
  problems_found += 1
174
-
196
+
175
197
  # Вычисляем текущее время до автозавершения
176
198
  now_utc = datetime.now(timezone.utc)
177
-
199
+
178
200
  # Приводим к UTC
179
201
  if end_time.tzinfo != timezone.utc:
180
202
  end_time_utc = end_time.astimezone(timezone.utc)
181
203
  else:
182
204
  end_time_utc = end_time
183
-
205
+
184
206
  remaining = end_time_utc - now_utc
185
207
  remaining_minutes = max(0, int(remaining.total_seconds() / 60))
186
-
208
+
187
209
  logger.info(f" now_utc: {now_utc}")
188
210
  logger.info(f" end_time_utc: {end_time_utc}")
189
211
  logger.info(f" Оставшееся время: {remaining_minutes} минут")
190
-
212
+
191
213
  # Вычисляем сколько уже прошло
192
214
  if start_time.tzinfo != timezone.utc:
193
215
  start_time_utc = start_time.astimezone(timezone.utc)
194
216
  else:
195
217
  start_time_utc = start_time
196
-
218
+
197
219
  elapsed = now_utc - start_time_utc
198
220
  elapsed_minutes = max(0, int(elapsed.total_seconds() / 60))
199
221
  logger.info(f" Прошло времени: {elapsed_minutes} минут")
200
-
222
+
201
223
  # Проверяем математику
202
224
  total_check = elapsed_minutes + remaining_minutes
203
- logger.info(f" Проверка: {elapsed_minutes} + {remaining_minutes} = {total_check} мин (должно быть ~{planned_minutes})")
204
-
225
+ logger.info(
226
+ f" Проверка: {elapsed_minutes} + {remaining_minutes} = {total_check} мин (должно быть ~{planned_minutes})"
227
+ )
228
+
205
229
  if abs(total_check - planned_minutes) > 2:
206
- logger.warning(f" ⚠️ ПРОБЛЕМА: сумма не сходится! Возможная проблема с timezone")
230
+ logger.warning(
231
+ " ⚠️ ПРОБЛЕМА: сумма не сходится! Возможная проблема с timezone"
232
+ )
207
233
  problems_found += 1
208
-
234
+
209
235
  except Exception as e:
210
236
  logger.error(f" ❌ Ошибка парсинга времени: {e}")
211
237
  problems_found += 1
212
-
238
+
213
239
  if not conversations:
214
240
  logger.info(" Нет активных диалогов для анализа")
215
241
  logger.info(" 💡 Создайте диалог командой /чат USER_ID для тестирования")
216
-
242
+
217
243
  return problems_found == 0
218
-
244
+
219
245
  except Exception as e:
220
246
  logger.error(f"❌ Ошибка подключения к БД: {e}")
221
247
  return False
222
248
 
249
+
223
250
  async def test_conversation_creation(config: Config) -> bool:
224
251
  """
225
252
  Тестирует создание нового диалога с правильным таймаутом
226
-
253
+
227
254
  Args:
228
255
  config: Конфигурация бота
229
-
256
+
230
257
  Returns:
231
258
  bool: True если тест прошел успешно, False если найдены проблемы
232
259
  """
233
260
  logger.info(f"\n{'='*50}")
234
261
  logger.info("🧪 ТЕСТ СОЗДАНИЯ ДИАЛОГА")
235
262
  logger.info(f"{'='*50}")
236
-
263
+
237
264
  timeout_minutes = config.ADMIN_SESSION_TIMEOUT_MINUTES
238
265
  logger.info(f"📋 Конфигурация таймаута: {timeout_minutes} минут")
239
-
266
+
240
267
  # Эмулируем создание диалога
241
268
  now_utc = datetime.now(timezone.utc)
242
269
  auto_end_utc = now_utc + timedelta(minutes=timeout_minutes)
243
-
270
+
244
271
  logger.info(f"🕐 now_utc: {now_utc}")
245
272
  logger.info(f"⏰ auto_end_utc: {auto_end_utc}")
246
- logger.info(f"📏 Разница: {int((auto_end_utc - now_utc).total_seconds() / 60)} минут")
247
-
273
+ logger.info(
274
+ f"📏 Разница: {int((auto_end_utc - now_utc).total_seconds() / 60)} минут"
275
+ )
276
+
248
277
  # Проверяем ISO формат
249
278
  auto_end_iso = auto_end_utc.isoformat()
250
279
  logger.info(f"📝 ISO формат: {auto_end_iso}")
251
-
280
+
252
281
  # Проверяем парсинг обратно
253
282
  try:
254
- if auto_end_iso.endswith('Z'):
255
- parsed_back = datetime.fromisoformat(auto_end_iso.replace('Z', '+00:00'))
256
- elif '+' in auto_end_iso:
283
+ if auto_end_iso.endswith("Z"):
284
+ parsed_back = datetime.fromisoformat(auto_end_iso.replace("Z", "+00:00"))
285
+ elif "+" in auto_end_iso:
257
286
  parsed_back = datetime.fromisoformat(auto_end_iso)
258
287
  else:
259
- parsed_back = datetime.fromisoformat(auto_end_iso).replace(tzinfo=timezone.utc)
260
-
288
+ parsed_back = datetime.fromisoformat(auto_end_iso).replace(
289
+ tzinfo=timezone.utc
290
+ )
291
+
261
292
  logger.info(f"🔄 Парсед обратно: {parsed_back}")
262
-
293
+
263
294
  # Проверяем что время совпадает
264
295
  if abs((parsed_back - auto_end_utc).total_seconds()) < 1:
265
296
  logger.info("✅ Парсинг работает корректно")
@@ -267,70 +298,73 @@ async def test_conversation_creation(config: Config) -> bool:
267
298
  else:
268
299
  logger.error("❌ Проблема с парсингом времени")
269
300
  return False
270
-
301
+
271
302
  except Exception as e:
272
303
  logger.error(f"❌ Ошибка парсинга: {e}")
273
304
  return False
274
305
 
306
+
275
307
  async def check_timeouts(bot_name: str = "growthmed-october-24") -> bool:
276
308
  """
277
309
  Проверяет корректность таймаутов диалогов админов
278
-
310
+
279
311
  Args:
280
312
  bot_name: Имя бота для проверки
281
-
313
+
282
314
  Returns:
283
315
  bool: True если все таймауты корректны, False если найдены проблемы
284
316
  """
285
317
  logger.info("🔍 Проверка таймаутов диалогов админов\n")
286
318
  logger.info(f"🚀 Проверка для бота: {bot_name}")
287
319
  logger.info(f"🤖 Bot ID будет автоопределен как: {bot_name}\n")
288
-
320
+
289
321
  # Настраиваем окружение для бота (автоматически устанавливает BOT_ID)
290
322
  config_dir = setup_bot_environment(bot_name)
291
323
  if not config_dir:
292
324
  logger.error("❌ Не удалось настроить окружение бота")
293
325
  return False
294
-
326
+
295
327
  logger.info(f"📁 Текущая рабочая директория: {os.getcwd()}")
296
- logger.info(f"📂 Содержимое рабочей директории:")
297
- for item in Path('.').iterdir():
328
+ logger.info("📂 Содержимое рабочей директории:")
329
+ for item in Path(".").iterdir():
298
330
  if item.is_dir():
299
331
  logger.info(f" 📁 {item.name}/")
300
332
  else:
301
333
  logger.info(f" 📄 {item.name}")
302
334
  logger.info("")
303
-
335
+
304
336
  # Инициализация
305
337
  try:
306
338
  logger.info("⚙️ Инициализация конфигурации...")
307
339
  config = Config()
308
- logger.info(f"✅ Конфигурация загружена")
309
-
340
+ logger.info("✅ Конфигурация загружена")
341
+
310
342
  logger.info("🔗 Подключение к Supabase...")
311
343
  supabase_client = SupabaseClient(config.SUPABASE_URL, config.SUPABASE_KEY)
312
344
  await supabase_client.initialize()
313
- logger.info(f"✅ Supabase подключен")
314
-
345
+ logger.info("✅ Supabase подключен")
346
+
315
347
  logger.info("👑 Инициализация менеджеров...")
316
348
  admin_manager = AdminManager(config, supabase_client)
317
349
  conversation_manager = ConversationManager(supabase_client, admin_manager)
318
- logger.info(f"✅ Менеджеры инициализированы\n")
319
-
350
+ logger.info("✅ Менеджеры инициализированы\n")
351
+
320
352
  except Exception as e:
321
353
  logger.error(f"❌ Ошибка инициализации: {e}")
322
354
  logger.exception("Стек ошибки:")
323
355
  return False
324
-
325
- logger.info(f"⚙️ Конфигурация:")
356
+
357
+ logger.info("⚙️ Конфигурация:")
326
358
  logger.info(f" BOT_ID: {config.BOT_ID}")
327
- logger.info(f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}")
359
+ logger.info(
360
+ f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}"
361
+ )
328
362
  logger.info(f" PROMT_FILES_DIR: {config.PROMT_FILES_DIR}")
329
363
  logger.info(f" Найдено промпт-файлов: {len(config.PROMPT_FILES)}")
330
364
  logger.info(f" Админов: {len(config.ADMIN_TELEGRAM_IDS)}")
331
365
  logger.info(f" Сейчас UTC: {datetime.now(timezone.utc)}")
332
366
  logger.info("")
333
-
367
+
334
368
  # Получаем активные диалоги
335
369
  try:
336
370
  logger.info("📊 Получение активных диалогов...")
@@ -339,111 +373,123 @@ async def check_timeouts(bot_name: str = "growthmed-october-24") -> bool:
339
373
  except Exception as e:
340
374
  logger.error(f"❌ Ошибка получения диалогов: {e}")
341
375
  return False
342
-
376
+
343
377
  if not conversations:
344
378
  logger.info("💬 Нет активных диалогов")
345
379
  logger.info("💡 Создайте диалог командой /чат USER_ID для тестирования")
346
-
380
+
347
381
  # Показываем пример создания тестового диалога
348
- logger.info(f"\n🧪 Пример создания тестового диалога:")
382
+ logger.info("\n🧪 Пример создания тестового диалога:")
349
383
  logger.info(f"1. Запустите бота: python {bot_name}.py")
350
- logger.info(f"2. Как админ выполните: /чат 123456789")
351
- logger.info(f"3. Затем проверьте: /чаты")
384
+ logger.info("2. Как админ выполните: /чат 123456789")
385
+ logger.info("3. Затем проверьте: /чаты")
352
386
  return True # Нет диалогов = нет проблем
353
-
387
+
354
388
  logger.info(f"📊 Найдено {len(conversations)} активных диалогов:")
355
389
  logger.info("")
356
-
390
+
357
391
  problems_found = 0
358
-
392
+
359
393
  for i, conv in enumerate(conversations, 1):
360
394
  logger.info(f"{i}. Диалог ID: {conv['id']}")
361
395
  logger.info(f" 👤 Пользователь: {conv['user_id']}")
362
396
  logger.info(f" 👑 Админ: {conv['admin_id']}")
363
-
397
+
364
398
  # Анализируем времена
365
- started_at_str = conv['started_at']
366
- auto_end_str = conv['auto_end_at']
367
-
399
+ started_at_str = conv["started_at"]
400
+ auto_end_str = conv["auto_end_at"]
401
+
368
402
  logger.info(f" 🕐 started_at (сырое): {started_at_str}")
369
403
  logger.info(f" ⏰ auto_end_at (сырое): {auto_end_str}")
370
-
404
+
371
405
  try:
372
406
  # Парсим время начала с правильной обработкой timezone
373
- if started_at_str.endswith('Z'):
374
- start_time = datetime.fromisoformat(started_at_str.replace('Z', '+00:00'))
375
- elif '+' in started_at_str or started_at_str.count(':') >= 3:
407
+ if started_at_str.endswith("Z"):
408
+ start_time = datetime.fromisoformat(
409
+ started_at_str.replace("Z", "+00:00")
410
+ )
411
+ elif "+" in started_at_str or started_at_str.count(":") >= 3:
376
412
  start_time = datetime.fromisoformat(started_at_str)
377
413
  else:
378
414
  naive_time = datetime.fromisoformat(started_at_str)
379
415
  start_time = naive_time.replace(tzinfo=timezone.utc)
380
-
416
+
381
417
  # Парсим время автозавершения с правильной обработкой timezone
382
- if auto_end_str.endswith('Z'):
383
- auto_end = datetime.fromisoformat(auto_end_str.replace('Z', '+00:00'))
384
- elif '+' in auto_end_str or auto_end_str.count(':') >= 3:
418
+ if auto_end_str.endswith("Z"):
419
+ auto_end = datetime.fromisoformat(auto_end_str.replace("Z", "+00:00"))
420
+ elif "+" in auto_end_str or auto_end_str.count(":") >= 3:
385
421
  auto_end = datetime.fromisoformat(auto_end_str)
386
422
  else:
387
423
  naive_time = datetime.fromisoformat(auto_end_str)
388
424
  auto_end = naive_time.replace(tzinfo=timezone.utc)
389
-
425
+
390
426
  logger.info(f" 📅 start_time (parsed): {start_time}")
391
427
  logger.info(f" ⏰ auto_end (parsed): {auto_end}")
392
-
428
+
393
429
  # Планируемая длительность
394
430
  planned_duration = auto_end - start_time
395
431
  planned_minutes = int(planned_duration.total_seconds() / 60)
396
432
  logger.info(f" 📏 Планируемая длительность: {planned_minutes} минут")
397
-
433
+
398
434
  # Текущее время в UTC
399
435
  now_utc = datetime.now(timezone.utc)
400
-
436
+
401
437
  # Приводим все к UTC для корректных расчетов
402
438
  if start_time.tzinfo != timezone.utc:
403
439
  start_time_utc = start_time.astimezone(timezone.utc)
404
440
  else:
405
441
  start_time_utc = start_time
406
-
442
+
407
443
  if auto_end.tzinfo != timezone.utc:
408
444
  auto_end_utc = auto_end.astimezone(timezone.utc)
409
445
  else:
410
446
  auto_end_utc = auto_end
411
-
447
+
412
448
  # Прошло времени
413
449
  elapsed = now_utc - start_time_utc
414
450
  elapsed_minutes = max(0, int(elapsed.total_seconds() / 60))
415
451
  logger.info(f" ⏱️ Прошло времени: {elapsed_minutes} минут")
416
-
452
+
417
453
  # Оставшееся время
418
454
  remaining = auto_end_utc - now_utc
419
455
  remaining_minutes = max(0, int(remaining.total_seconds() / 60))
420
456
  logger.info(f" ⏰ Осталось времени: {remaining_minutes} минут")
421
-
457
+
422
458
  # Проверяем корректность конфигурации
423
459
  expected_timeout = config.ADMIN_SESSION_TIMEOUT_MINUTES
424
- if abs(planned_minutes - expected_timeout) <= 2: # допускаем погрешность 2 минуты
425
- logger.info(f" ✅ Таймаут корректный (ожидался {expected_timeout} мин)")
460
+ if (
461
+ abs(planned_minutes - expected_timeout) <= 2
462
+ ): # допускаем погрешность 2 минуты
463
+ logger.info(
464
+ f" ✅ Таймаут корректный (ожидался {expected_timeout} мин)"
465
+ )
426
466
  else:
427
- logger.error(f" ❌ ОШИБКА: ожидался {expected_timeout} мин, получили {planned_minutes} мин")
467
+ logger.error(
468
+ f" ❌ ОШИБКА: ожидался {expected_timeout} мин, получили {planned_minutes} мин"
469
+ )
428
470
  problems_found += 1
429
-
471
+
430
472
  # Проверяем математику
431
473
  total_check = elapsed_minutes + remaining_minutes
432
- logger.info(f" 🔢 Проверка: {elapsed_minutes} + {remaining_minutes} = {total_check} мин")
433
-
474
+ logger.info(
475
+ f" 🔢 Проверка: {elapsed_minutes} + {remaining_minutes} = {total_check} мин"
476
+ )
477
+
434
478
  if abs(total_check - planned_minutes) > 2:
435
- logger.warning(f" ⚠️ ПРОБЛЕМА: сумма не сходится! Возможна проблема с timezone")
479
+ logger.warning(
480
+ " ⚠️ ПРОБЛЕМА: сумма не сходится! Возможна проблема с timezone"
481
+ )
436
482
  problems_found += 1
437
483
  else:
438
- logger.info(f" ✅ Математика сходится")
439
-
484
+ logger.info(" ✅ Математика сходится")
485
+
440
486
  except Exception as e:
441
487
  logger.error(f" ❌ Ошибка парсинга: {e}")
442
488
  problems_found += 1
443
489
  logger.exception(" Стек ошибки:")
444
-
490
+
445
491
  logger.info("")
446
-
492
+
447
493
  # Тестируем функцию форматирования
448
494
  logger.info("🧪 Тестирование format_active_conversations:")
449
495
  try:
@@ -452,38 +498,39 @@ async def check_timeouts(bot_name: str = "growthmed-october-24") -> bool:
452
498
  except Exception as e:
453
499
  logger.error(f"❌ Ошибка форматирования: {e}")
454
500
  problems_found += 1
455
-
501
+
456
502
  # Итоговый результат
457
503
  logger.info(f"\n{'='*50}")
458
- logger.info(f"📊 ИТОГОВЫЙ РЕЗУЛЬТАТ:")
504
+ logger.info("📊 ИТОГОВЫЙ РЕЗУЛЬТАТ:")
459
505
  if problems_found == 0:
460
506
  logger.info("✅ Все таймауты корректны!")
461
507
  else:
462
508
  logger.error(f"❌ Найдено {problems_found} проблем")
463
509
  logger.info("💡 Запустите fix_existing_timeouts.py для исправления")
464
510
  logger.info(f"{'='*50}")
465
-
511
+
466
512
  return problems_found == 0
467
513
 
514
+
468
515
  def main():
469
516
  """Точка входа для запуска из командной строки"""
470
517
  # Убираем лишние логи для чистого вывода
471
- logging.basicConfig(level=logging.INFO, format='%(message)s')
472
-
518
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
519
+
473
520
  logger.info("🔍 Утилита проверки таймаутов диалогов")
474
521
  logger.info("Использование:")
475
522
  logger.info(" python -m smart_bot_factory.timeout_checker [bot_name]")
476
523
  logger.info(" python -m smart_bot_factory.timeout_checker growthmed-october-24")
477
524
  logger.info("")
478
-
479
- if len(sys.argv) > 1 and sys.argv[1] in ['-h', '--help', 'help']:
525
+
526
+ if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help", "help"]:
480
527
  return
481
-
528
+
482
529
  # Определяем какого бота проверять
483
530
  bot_name = "growthmed-october-24" # по умолчанию
484
531
  if len(sys.argv) > 1:
485
532
  bot_name = sys.argv[1]
486
-
533
+
487
534
  try:
488
535
  success = asyncio.run(check_timeouts(bot_name))
489
536
  if not success:
@@ -495,5 +542,6 @@ def main():
495
542
  logger.exception("Стек ошибки:")
496
543
  sys.exit(1)
497
544
 
545
+
498
546
  if __name__ == "__main__":
499
547
  main()