smart-bot-factory 0.3.7__py3-none-any.whl → 0.3.9__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.
- smart_bot_factory/admin/__init__.py +7 -7
- smart_bot_factory/admin/admin_events.py +483 -383
- smart_bot_factory/admin/admin_logic.py +234 -158
- smart_bot_factory/admin/admin_manager.py +68 -53
- smart_bot_factory/admin/admin_tester.py +46 -40
- smart_bot_factory/admin/timeout_checker.py +201 -153
- smart_bot_factory/aiogram_calendar/__init__.py +11 -3
- smart_bot_factory/aiogram_calendar/common.py +12 -18
- smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
- smart_bot_factory/aiogram_calendar/schemas.py +49 -28
- smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
- smart_bot_factory/analytics/analytics_manager.py +414 -392
- smart_bot_factory/cli.py +204 -148
- smart_bot_factory/config.py +123 -102
- smart_bot_factory/core/bot_utils.py +474 -332
- smart_bot_factory/core/conversation_manager.py +287 -200
- smart_bot_factory/core/decorators.py +1200 -755
- smart_bot_factory/core/message_sender.py +287 -266
- smart_bot_factory/core/router.py +170 -100
- smart_bot_factory/core/router_manager.py +121 -83
- smart_bot_factory/core/states.py +4 -3
- smart_bot_factory/creation/__init__.py +1 -1
- smart_bot_factory/creation/bot_builder.py +320 -242
- smart_bot_factory/creation/bot_testing.py +440 -365
- smart_bot_factory/dashboard/__init__.py +1 -3
- smart_bot_factory/event/__init__.py +2 -7
- smart_bot_factory/handlers/handlers.py +676 -472
- smart_bot_factory/integrations/openai_client.py +218 -168
- smart_bot_factory/integrations/supabase_client.py +948 -637
- smart_bot_factory/message/__init__.py +18 -22
- smart_bot_factory/router/__init__.py +2 -2
- smart_bot_factory/setup_checker.py +162 -126
- smart_bot_factory/supabase/__init__.py +1 -1
- smart_bot_factory/supabase/client.py +631 -515
- smart_bot_factory/utils/__init__.py +2 -3
- smart_bot_factory/utils/debug_routing.py +38 -27
- smart_bot_factory/utils/prompt_loader.py +153 -120
- smart_bot_factory/utils/user_prompt_loader.py +55 -56
- smart_bot_factory/utm_link_generator.py +123 -116
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/METADATA +3 -1
- smart_bot_factory-0.3.9.dist-info/RECORD +59 -0
- smart_bot_factory-0.3.7.dist-info/RECORD +0 -59
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.9.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 ..
|
|
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 /
|
|
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(
|
|
33
|
-
bots_dir = root_dir /
|
|
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 /
|
|
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[
|
|
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 /
|
|
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(
|
|
70
|
+
local_prompts = Path("prompts")
|
|
69
71
|
if local_prompts.exists():
|
|
70
|
-
logger.info(
|
|
72
|
+
logger.info(
|
|
73
|
+
f"✅ Промпты доступны из рабочей директории: {local_prompts.absolute()}"
|
|
74
|
+
)
|
|
71
75
|
else:
|
|
72
|
-
logger.error(
|
|
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(
|
|
106
|
+
logger.info("📋 Конфигурация:")
|
|
100
107
|
logger.info(f" BOT_ID: {config.BOT_ID}")
|
|
101
|
-
logger.info(
|
|
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(
|
|
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(
|
|
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 =
|
|
121
|
-
|
|
122
|
-
|
|
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(
|
|
133
|
-
|
|
146
|
+
logger.info(
|
|
147
|
+
f" Админ: {conv['admin_id']}, Пользователь: {conv['user_id']}"
|
|
148
|
+
)
|
|
149
|
+
|
|
134
150
|
# Парсим времена
|
|
135
|
-
started_at = conv[
|
|
136
|
-
auto_end_at = conv[
|
|
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(
|
|
144
|
-
start_time = datetime.fromisoformat(
|
|
145
|
-
|
|
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(
|
|
152
|
-
end_time = datetime.fromisoformat(
|
|
153
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
255
|
-
parsed_back = datetime.fromisoformat(auto_end_iso.replace(
|
|
256
|
-
elif
|
|
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(
|
|
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(
|
|
297
|
-
for item in Path(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
356
|
+
|
|
357
|
+
logger.info("⚙️ Конфигурация:")
|
|
326
358
|
logger.info(f" BOT_ID: {config.BOT_ID}")
|
|
327
|
-
logger.info(
|
|
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(
|
|
382
|
+
logger.info("\n🧪 Пример создания тестового диалога:")
|
|
349
383
|
logger.info(f"1. Запустите бота: python {bot_name}.py")
|
|
350
|
-
logger.info(
|
|
351
|
-
logger.info(
|
|
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[
|
|
366
|
-
auto_end_str = conv[
|
|
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(
|
|
374
|
-
start_time = datetime.fromisoformat(
|
|
375
|
-
|
|
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(
|
|
383
|
-
auto_end = datetime.fromisoformat(auto_end_str.replace(
|
|
384
|
-
elif
|
|
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
|
|
425
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
479
|
+
logger.warning(
|
|
480
|
+
" ⚠️ ПРОБЛЕМА: сумма не сходится! Возможна проблема с timezone"
|
|
481
|
+
)
|
|
436
482
|
problems_found += 1
|
|
437
483
|
else:
|
|
438
|
-
logger.info(
|
|
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(
|
|
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=
|
|
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 [
|
|
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()
|