smart-bot-factory 0.3.5__py3-none-any.whl → 0.3.6__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.

@@ -271,6 +271,13 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
271
271
  # Выполняем событие
272
272
  result = await execute_event_handler(event_type, user_id, event_info)
273
273
 
274
+ # Проверяем наличие поля 'info' для дашборда
275
+ import json
276
+ info_dashboard_json = None
277
+ if isinstance(result, dict) and 'info' in result:
278
+ info_dashboard_json = json.dumps(result['info'], ensure_ascii=False)
279
+ logger.info(f" 📊 Дашборд данные добавлены: {result['info'].get('title', 'N/A')}")
280
+
274
281
  # Сохраняем в БД УЖЕ со статусом completed (избегаем дублирования)
275
282
  event_record = {
276
283
  'event_type': event_type,
@@ -281,7 +288,8 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
281
288
  'status': 'completed', # Сразу completed!
282
289
  'session_id': session_id,
283
290
  'executed_at': __import__('datetime').datetime.now(__import__('datetime').timezone.utc).isoformat(),
284
- 'result_data': __import__('json').dumps(result, ensure_ascii=False) if result else None
291
+ 'result_data': __import__('json').dumps(result, ensure_ascii=False) if result else None,
292
+ 'info_dashboard': info_dashboard_json # Добавится только если есть поле 'info'
285
293
  }
286
294
  response = supabase_client.client.table('scheduled_events').insert(event_record).execute()
287
295
  event_id = response.data[0]['id']
@@ -1271,6 +1271,11 @@ async def update_event_result(
1271
1271
  if result_data:
1272
1272
  import json
1273
1273
  update_data['result_data'] = json.dumps(result_data, ensure_ascii=False)
1274
+
1275
+ # Проверяем наличие поля 'info' для дашборда
1276
+ if isinstance(result_data, dict) and 'info' in result_data:
1277
+ update_data['info_dashboard'] = json.dumps(result_data['info'], ensure_ascii=False)
1278
+ logger.info(f"📊 Дашборд данные добавлены в событие {event_id}")
1274
1279
 
1275
1280
  if error_message:
1276
1281
  update_data['last_error'] = error_message
@@ -1471,8 +1476,16 @@ async def background_event_processor():
1471
1476
  continue
1472
1477
 
1473
1478
  # Выполняем событие
1474
- await process_scheduled_event(event)
1475
- await update_event_result(event['id'], 'completed', {"processed": True})
1479
+ result = await process_scheduled_event(event)
1480
+
1481
+ # Проверяем наличие поля 'info' для дашборда
1482
+ result_data = {"processed": True}
1483
+ if isinstance(result, dict):
1484
+ result_data.update(result)
1485
+ if 'info' in result:
1486
+ logger.info(f" 📊 Дашборд данные для задачи: {result['info'].get('title', 'N/A')}")
1487
+
1488
+ await update_event_result(event['id'], 'completed', result_data)
1476
1489
  logger.info(f"✅ Событие {event['id']} выполнено")
1477
1490
 
1478
1491
  except Exception as e:
@@ -1486,7 +1499,7 @@ async def background_event_processor():
1486
1499
  await asyncio.sleep(60)
1487
1500
 
1488
1501
  async def process_scheduled_event(event: Dict):
1489
- """Обрабатывает одно событие из БД"""
1502
+ """Обрабатывает одно событие из БД и возвращает результат"""
1490
1503
 
1491
1504
  event_type = event['event_type']
1492
1505
  event_category = event['event_category']
@@ -1495,14 +1508,17 @@ async def process_scheduled_event(event: Dict):
1495
1508
 
1496
1509
  logger.info(f"🔄 Обработка события {event['id']}: {event_category}/{event_type}")
1497
1510
 
1511
+ result = None
1498
1512
  if event_category == 'scheduled_task':
1499
- await execute_scheduled_task(event_type, user_id, event_data)
1513
+ result = await execute_scheduled_task(event_type, user_id, event_data)
1500
1514
  elif event_category == 'global_handler':
1501
- await execute_global_handler(event_type, event_data)
1515
+ result = await execute_global_handler(event_type, event_data)
1502
1516
  elif event_category == 'user_event':
1503
- await execute_event_handler(event_type, user_id, event_data)
1517
+ result = await execute_event_handler(event_type, user_id, event_data)
1504
1518
  else:
1505
1519
  logger.warning(f"⚠️ Неизвестная категория события: {event_category}")
1520
+
1521
+ return result
1506
1522
 
1507
1523
  # =============================================================================
1508
1524
  # ОБНОВЛЕННЫЕ ФУНКЦИИ С СОХРАНЕНИЕМ В БД
@@ -1884,3 +1900,69 @@ async def process_admin_event(event: Dict):
1884
1900
  shutil.rmtree(temp_after_msg, ignore_errors=True)
1885
1901
  logger.error(f"❌ Критическая ошибка обработки события: {e}")
1886
1902
  raise
1903
+
1904
+ # =============================================================================
1905
+ # ФУНКЦИЯ ДЛЯ ПОДГОТОВКИ ДАННЫХ ДАШБОРДА
1906
+ # =============================================================================
1907
+
1908
+ async def prepare_dashboard_info(
1909
+ description_template: str,
1910
+ title: str,
1911
+ user_id: int
1912
+ ) -> Dict[str, Any]:
1913
+ """
1914
+ Подготавливает данные для дашборда (БЕЗ записи в БД)
1915
+
1916
+ Возвращаемый dict нужно поместить в поле 'info' результата обработчика.
1917
+ bot_utils.py автоматически запишет его в столбец info_dashboard таблицы.
1918
+
1919
+ Args:
1920
+ description_template: Строка с {username}, например "{username} купил подписку"
1921
+ title: Заголовок для дашборда
1922
+ user_id: Telegram ID
1923
+
1924
+ Returns:
1925
+ Dict с данными для дашборда
1926
+
1927
+ Example:
1928
+ @event_router.event_handler("collect_phone", notify=True)
1929
+ async def handle_phone_collection(user_id: int, phone_number: str):
1930
+ # ... бизнес-логика ...
1931
+
1932
+ return {
1933
+ "status": "success",
1934
+ "phone": phone_number,
1935
+ "info": await prepare_dashboard_info(
1936
+ description_template="{username} оставил телефон",
1937
+ title="Новый контакт",
1938
+ user_id=user_id
1939
+ )
1940
+ }
1941
+ """
1942
+ supabase_client = get_supabase_client()
1943
+
1944
+ # Получаем username из sales_users
1945
+ username = f"user_{user_id}" # fallback
1946
+ if supabase_client:
1947
+ try:
1948
+ query = supabase_client.client.table('sales_users').select('username').eq('telegram_id', user_id)
1949
+ if supabase_client.bot_id:
1950
+ query = query.eq('bot_id', supabase_client.bot_id)
1951
+ response = query.execute()
1952
+ if response.data:
1953
+ username = response.data[0].get('username') or username
1954
+ except Exception as e:
1955
+ logger.warning(f"⚠️ Не удалось получить username для дашборда: {e}")
1956
+
1957
+ # Форматируем строку
1958
+ description = description_template.format(username=username)
1959
+
1960
+ # Московское время (UTC+3)
1961
+ moscow_tz = timezone(timedelta(hours=3))
1962
+ moscow_time = datetime.now(moscow_tz)
1963
+
1964
+ return {
1965
+ 'title': title,
1966
+ 'description': description,
1967
+ 'created_at': moscow_time.isoformat()
1968
+ }
@@ -0,0 +1,5 @@
1
+ from ..core.decorators import prepare_dashboard_info
2
+
3
+ __all__ = [
4
+ 'prepare_dashboard_info'
5
+ ]
@@ -398,8 +398,8 @@ class SupabaseClient:
398
398
  try:
399
399
  # Проверяем существует ли админ
400
400
  response = self.client.table('sales_admins').select('telegram_id').eq(
401
- 'telegram_id', admin_data['telegram_id']
402
- ).execute()
401
+ 'telegram_id', admin_data['telegram_id'],
402
+ ).eq('bot_id', self.bot_id).execute()
403
403
 
404
404
  if response.data:
405
405
  # Обновляем существующего
@@ -408,13 +408,14 @@ class SupabaseClient:
408
408
  'first_name': admin_data.get('first_name'),
409
409
  'last_name': admin_data.get('last_name'),
410
410
  'is_active': True
411
- }).eq('telegram_id', admin_data['telegram_id']).execute()
411
+ }).eq('telegram_id', admin_data['telegram_id']).eq('bot_id', self.bot_id).execute()
412
412
 
413
413
  logger.debug(f"Обновлен админ {admin_data['telegram_id']}")
414
414
  else:
415
415
  # Создаем нового
416
416
  self.client.table('sales_admins').insert({
417
417
  'telegram_id': admin_data['telegram_id'],
418
+ 'bot_id': self.bot_id,
418
419
  'username': admin_data.get('username'),
419
420
  'first_name': admin_data.get('first_name'),
420
421
  'last_name': admin_data.get('last_name'),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smart-bot-factory
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Библиотека для создания умных чат-ботов
5
5
  Author-email: Kopatych <eserov73@gmail.com>
6
6
  License: MIT
@@ -44,6 +44,7 @@ Description-Content-Type: text/markdown
44
44
  - [event_handler](#event_handler---обработчики-событий)
45
45
  - [schedule_task](#schedule_task---запланированные-задачи)
46
46
  - [global_handler](#global_handler---глобальные-обработчики)
47
+ - [Dashboard Info](#-dashboard-info---отправка-данных-в-дашборд)
47
48
  - [Хуки для кастомизации](#-хуки-для-кастомизации)
48
49
  - [Telegram роутеры](#-telegram-роутеры)
49
50
  - [Расширенные возможности](#-расширенные-возможности)
@@ -187,9 +188,10 @@ sbf link
187
188
 
188
189
  ```python
189
190
  @event_router.event_handler(
190
- event_type: str, # Тип события
191
- notify: bool = False, # Уведомлять админов
192
- once_only: bool = True # Выполнять только 1 раз
191
+ event_type: str, # Тип события
192
+ notify: bool = False, # Уведомлять админов
193
+ once_only: bool = True, # Выполнять только 1 раз
194
+ send_ai_response: bool = True # Отправлять ответ от ИИ
193
195
  )
194
196
  async def handler(user_id: int, event_data: str):
195
197
  # Ваш код
@@ -201,6 +203,7 @@ async def handler(user_id: int, event_data: str):
201
203
  - **`event_type`** (обязательный) - Уникальное имя события
202
204
  - **`notify`** (по умолчанию `False`) - Отправлять уведомление админам после выполнения
203
205
  - **`once_only`** (по умолчанию `True`) - Если `True`, событие выполнится только 1 раз для пользователя
206
+ - **`send_ai_response`** (по умолчанию `True`) - Если `False`, ИИ НЕ отправит сообщение после выполнения обработчика
204
207
 
205
208
  **Как работает:**
206
209
 
@@ -235,6 +238,16 @@ async def handle_question(user_id: int, question: str):
235
238
  """Обрабатывает вопросы (может быть много)"""
236
239
  # Логика обработки
237
240
  return {"status": "answered"}
241
+
242
+ # БЕЗ отправки ответа от ИИ
243
+ @event_router.event_handler("silent_event", send_ai_response=False)
244
+ async def handle_silent(user_id: int, event_data: str):
245
+ """
246
+ Выполняет логику БЕЗ отправки сообщения от ИИ
247
+ Используйте когда хотите только собрать данные без ответа пользователю
248
+ """
249
+ await send_message_by_human(user_id, "✅ Данные сохранены")
250
+ return {"status": "saved"}
238
251
  ```
239
252
 
240
253
  ---
@@ -247,12 +260,13 @@ async def handle_question(user_id: int, question: str):
247
260
 
248
261
  ```python
249
262
  @event_router.schedule_task(
250
- task_name: str, # Название задачи
251
- delay: Union[str, int], # Задержка: "1h 30m" или секунды
252
- notify: bool = False, # Уведомлять админов
253
- smart_check: bool = True, # Умная проверка активности
254
- once_only: bool = True, # Выполнять только 1 раз
255
- event_type: Union[str, Callable] = None # Источник времени события
263
+ task_name: str, # Название задачи
264
+ delay: Union[str, int], # Задержка: "1h 30m" или секунды
265
+ notify: bool = False, # Уведомлять админов
266
+ smart_check: bool = True, # Умная проверка активности
267
+ once_only: bool = True, # Выполнять только 1 раз
268
+ event_type: Union[str, Callable] = None, # Источник времени события
269
+ send_ai_response: bool = True # Отправлять ответ от ИИ
256
270
  )
257
271
  async def handler(user_id: int, user_data: str):
258
272
  # Ваш код
@@ -273,6 +287,7 @@ async def handler(user_id: int, user_data: str):
273
287
  - **`event_type`** (опционально) - Источник времени события:
274
288
  - **Строка**: `"appointment_booking"` - ищет событие в БД и вычисляет время
275
289
  - **Функция**: `async def(user_id, user_data) -> datetime` - кастомная логика
290
+ - **`send_ai_response`** (по умолчанию `True`) - Если `False`, ИИ НЕ отправит сообщение после выполнения задачи
276
291
 
277
292
  **Формула времени с `event_type`:**
278
293
 
@@ -362,11 +377,12 @@ async def important_reminder(user_id: int, text: str):
362
377
 
363
378
  ```python
364
379
  @event_router.global_handler(
365
- handler_type: str, # Тип обработчика
366
- delay: Union[str, int], # Задержка
367
- notify: bool = False, # Уведомлять админов
368
- once_only: bool = True, # Выполнять только 1 раз
369
- event_type: Union[str, Callable] = None # Источник времени
380
+ handler_type: str, # Тип обработчика
381
+ delay: Union[str, int], # Задержка
382
+ notify: bool = False, # Уведомлять админов
383
+ once_only: bool = True, # Выполнять только 1 раз
384
+ event_type: Union[str, Callable] = None, # Источник времени
385
+ send_ai_response: bool = True # Отправлять ответ от ИИ
370
386
  )
371
387
  async def handler(handler_data: str):
372
388
  # Ваш код
@@ -420,6 +436,173 @@ async def notify_promo_ending(handler_data: str):
420
436
 
421
437
  ---
422
438
 
439
+ ## 📊 Dashboard Info - Отправка данных в дашборд
440
+
441
+ **Назначение:** Позволяет отправлять информацию о событиях в дашборд (таблица `scheduled_events`, столбец `info_dashboard`) для аналитики и мониторинга.
442
+
443
+ ### Как работает
444
+
445
+ 1. Обработчик события возвращает результат с полем `'info'`
446
+ 2. Система автоматически извлекает это поле и записывает в `info_dashboard` таблицы
447
+ 3. Функция `prepare_dashboard_info` автоматически:
448
+ - Получает `username` из таблицы `sales_users`
449
+ - Форматирует строку с подстановкой данных
450
+ - Добавляет московское время (UTC+3)
451
+
452
+ ### Сигнатура
453
+
454
+ ```python
455
+ from smart_bot_factory.dashboard import prepare_dashboard_info
456
+
457
+ dashboard_data = await prepare_dashboard_info(
458
+ description_template: str, # Строка с {username}, например "{username} купил подписку"
459
+ title: str, # Заголовок для дашборда
460
+ user_id: int # Telegram ID пользователя
461
+ )
462
+ ```
463
+
464
+ **Возвращает:**
465
+
466
+ ```python
467
+ {
468
+ 'title': 'Заголовок',
469
+ 'description': '@username123 купил подписку', # С подстановкой реального username
470
+ 'created_at': '2025-10-18T15:30:45.123456+03:00' # Московское время
471
+ }
472
+ ```
473
+
474
+ ### Примеры использования
475
+
476
+ #### С event_handler
477
+
478
+ ```python
479
+ from smart_bot_factory.dashboard import prepare_dashboard_info
480
+
481
+ @event_router.event_handler("collect_phone", notify=True, once_only=True)
482
+ async def handle_phone_collection(user_id: int, phone_number: str):
483
+ """Сохраняет телефон клиента"""
484
+
485
+ # Ваша бизнес-логика
486
+ session = await supabase_client.get_active_session(user_id)
487
+ if session:
488
+ metadata = session.get('metadata', {})
489
+ metadata['phone'] = phone_number
490
+ await supabase_client.update_session_metadata(session['id'], metadata)
491
+
492
+ await send_message_by_human(
493
+ user_id=user_id,
494
+ message_text=f"✅ Спасибо! Ваш номер {phone_number} сохранен"
495
+ )
496
+
497
+ # 📊 Возвращаем результат С данными для дашборда
498
+ return {
499
+ "status": "success",
500
+ "phone": phone_number,
501
+ "info": await prepare_dashboard_info(
502
+ description_template="{username} оставил номер телефона",
503
+ title="Новый контакт",
504
+ user_id=user_id
505
+ )
506
+ }
507
+ ```
508
+
509
+ #### С schedule_task
510
+
511
+ ```python
512
+ @event_router.schedule_task("follow_up", delay="24h", smart_check=True)
513
+ async def send_follow_up(user_id: int, reminder_text: str):
514
+ """Напоминание через 24 часа"""
515
+
516
+ await send_message_by_human(
517
+ user_id=user_id,
518
+ message_text=f"👋 {reminder_text}"
519
+ )
520
+
521
+ # 📊 Работает и для задач!
522
+ return {
523
+ "status": "sent",
524
+ "type": "follow_up",
525
+ "info": await prepare_dashboard_info(
526
+ description_template="{username} получил напоминание",
527
+ title="Напоминание отправлено",
528
+ user_id=user_id
529
+ )
530
+ }
531
+ ```
532
+
533
+ #### БЕЗ дашборда
534
+
535
+ Если не нужно отправлять данные в дашборд - просто не добавляйте поле `'info'`:
536
+
537
+ ```python
538
+ @event_router.event_handler("collect_name", once_only=False)
539
+ async def handle_name_collection(user_id: int, client_name: str):
540
+ """БЕЗ дашборда - просто сохраняем имя"""
541
+
542
+ await send_message_by_human(
543
+ user_id=user_id,
544
+ message_text=f"✅ Спасибо! Ваше имя {client_name} сохранено"
545
+ )
546
+
547
+ # Возвращаем БЕЗ поля 'info' - дашборд останется пустым
548
+ return {"status": "success"}
549
+ ```
550
+
551
+ ### Что попадает в БД
552
+
553
+ **События С дашбордом:**
554
+
555
+ ```sql
556
+ SELECT * FROM scheduled_events WHERE id = '123';
557
+
558
+ id: 123
559
+ event_type: collect_phone
560
+ event_category: user_event
561
+ user_id: 12345
562
+ status: completed
563
+ result_data: {"status": "success", "phone": "+79001234567", "info": {...}}
564
+ info_dashboard: {
565
+ "title": "Новый контакт",
566
+ "description": "@username123 оставил номер телефона",
567
+ "created_at": "2025-10-18T15:30:45+03:00"
568
+ }
569
+ ```
570
+
571
+ **События БЕЗ дашборда:**
572
+
573
+ ```sql
574
+ SELECT * FROM scheduled_events WHERE id = '124';
575
+
576
+ id: 124
577
+ event_type: collect_name
578
+ event_category: user_event
579
+ user_id: 12345
580
+ status: completed
581
+ result_data: {"status": "success"}
582
+ info_dashboard: NULL ← Остается пустым
583
+ ```
584
+
585
+ ### Форматирование строк
586
+
587
+ Функция `prepare_dashboard_info` поддерживает подстановку `{username}`:
588
+
589
+ ```python
590
+ # Примеры шаблонов:
591
+ "{username} купил подписку на 1 год"
592
+ "{username} оставил контакт"
593
+ "{username} записался на консультацию"
594
+ "{username} задал вопрос о продукте"
595
+ "{username} завершил оплату"
596
+
597
+ # После подстановки:
598
+ "@user123 купил подписку на 1 год"
599
+ "@ivan_petrov оставил контакт"
600
+ ```
601
+
602
+ Если пользователь не найден в `sales_users` - будет использован fallback: `user_12345`
603
+
604
+ ---
605
+
423
606
  ## 🎣 Хуки для кастомизации
424
607
 
425
608
  Хуки позволяют внедрять свою логику в стандартную обработку сообщений без переписывания всей функции.
@@ -813,16 +996,46 @@ DEBUG_MODE=false
813
996
 
814
997
  ## 🎯 Сравнение декораторов
815
998
 
816
- | Декоратор | Когда выполняется | Для кого | Параметры |
817
- |-----------|-------------------|----------|-----------|
818
- | `@event_handler` | Немедленно | 1 пользователь | `event_type`, `notify`, `once_only` |
819
- | `@schedule_task` | Через время | 1 пользователь | `task_name`, `delay`, `event_type`, `smart_check`, `once_only`, `notify` |
820
- | `@global_handler` | Через время | Все пользователи | `handler_type`, `delay`, `event_type`, `once_only`, `notify` |
999
+ | Декоратор | Когда выполняется | Для кого | Ключевые параметры |
1000
+ |-----------|-------------------|----------|--------------------|
1001
+ | `@event_handler` | Немедленно | 1 пользователь | `event_type`, `notify`, `once_only`, `send_ai_response` |
1002
+ | `@schedule_task` | Через время | 1 пользователь | `task_name`, `delay`, `event_type`, `smart_check`, `once_only`, `notify`, `send_ai_response` |
1003
+ | `@global_handler` | Через время | Все пользователи | `handler_type`, `delay`, `event_type`, `once_only`, `notify`, `send_ai_response` |
821
1004
 
822
1005
  ---
823
1006
 
824
1007
  ## 🔑 Ключевые концепции
825
1008
 
1009
+ ### `send_ai_response=True`
1010
+
1011
+ Контролирует отправку сообщения от ИИ после выполнения обработчика:
1012
+
1013
+ - **`True`** (по умолчанию) - ИИ отправит сообщение пользователю после выполнения обработчика
1014
+ - **`False`** - ИИ НЕ отправит сообщение (используйте когда нужна только фоновая обработка или когда отправляете сообщение вручную)
1015
+
1016
+ **Когда использовать `send_ai_response=False`:**
1017
+
1018
+ - Когда нужно только собрать данные без ответа пользователю
1019
+ - Когда вы сами отправляете сообщение через `send_message_by_human()`
1020
+ - Для фоновых задач без взаимодействия с пользователем
1021
+
1022
+ ```python
1023
+ # ИИ отправит сообщение (по умолчанию)
1024
+ @event_router.event_handler("collect_phone")
1025
+ async def save_phone(user_id: int, phone: str):
1026
+ # Сохраняем телефон
1027
+ # ИИ автоматически отправит сообщение после выполнения
1028
+ return {"status": "success"}
1029
+
1030
+ # ИИ НЕ отправит сообщение
1031
+ @event_router.event_handler("collect_name", send_ai_response=False)
1032
+ async def save_name(user_id: int, name: str):
1033
+ # Сохраняем имя
1034
+ await send_message_by_human(user_id, f"✅ Имя {name} сохранено")
1035
+ # ИИ не будет отправлять свое сообщение
1036
+ return {"status": "success"}
1037
+ ```
1038
+
826
1039
  ### `once_only=True`
827
1040
 
828
1041
  Гарантирует выполнение события только 1 раз для пользователя:
@@ -7,7 +7,6 @@ smart_bot_factory/admin/__init__.py,sha256=vdsMTpt_LiXkY-awFu_X9e2Zt7CV50PwmsWkF
7
7
  smart_bot_factory/admin/admin_events.py,sha256=QCosyTbJgrU8daWSK_bQgf8UZoJSIrV6xyO0R3XV2j0,43289
8
8
  smart_bot_factory/admin/admin_logic.py,sha256=vPkNk86bdPsjNUNlZ3qfKtbRr9UuJy2oG54cYUGGNmg,23107
9
9
  smart_bot_factory/admin/admin_manager.py,sha256=xlyG9mIjPmtUhS4E9lp36T7o5Kfp5PZpJ-r1QjnSn5g,6394
10
- smart_bot_factory/admin/admin_migration.sql,sha256=kleMPJBSe2Z7ZZz7rNyOX_yoh4GZivGesqAX90U5PGs,5667
11
10
  smart_bot_factory/admin/admin_tester.py,sha256=PGFpf7fmD5Wxea31xR2ZM_A_QpvrB73gsbxvUrHQBkg,6463
12
11
  smart_bot_factory/admin/timeout_checker.py,sha256=TzA2FGrxwE8fuhKerGnGrt4qYMEZdIR8x3SQAnIW5YQ,24490
13
12
  smart_bot_factory/aiogram_calendar/__init__.py,sha256=_IzB_HJIZuMs__7xBBsYVG20GbRNFw2VowvhOEyiGGc,349
@@ -30,9 +29,9 @@ smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml,sh
30
29
  smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml,sha256=bzDulOU4a2LyWlcHzlQU8GYhOky2WTfyizGfjX4ioMY,2436
31
30
  smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt,sha256=Db21Mm0r8SBWFdX9EeIF2FZtLQ2cvuwVlSRJd2KEYCg,922
32
31
  smart_bot_factory/configs/growthmed-october-24/welcome_file/Чек лист по 152ФЗ и 323ФЗ для медицины.pdf,sha256=BiAiQHNnQXJPMsks9AeL6s0beEjRFkRMJLMlAn4WorA,5284954
33
- smart_bot_factory/core/bot_utils.py,sha256=4c98tVPThXW4ET6XJ0FYMePrx4gYrw_HNhx3YLep9hY,46976
32
+ smart_bot_factory/core/bot_utils.py,sha256=29y9LrwM4yLYqBqiCld4RmNW0q3cHL8psuOl4PiDEjs,47632
34
33
  smart_bot_factory/core/conversation_manager.py,sha256=eoHL7MCEz68DRvTVwRwZgf2PWwGv4T6J9D-I-thETi8,28289
35
- smart_bot_factory/core/decorators.py,sha256=0fDgYNaXU3ZLhFJBxAognLWfy_Fz1F63wdUNaVziDQU,95141
34
+ smart_bot_factory/core/decorators.py,sha256=wiYyFTai7NebH7wQuxEsLbYGOExiCUmpXqz9V2FkXpM,98782
36
35
  smart_bot_factory/core/message_sender.py,sha256=0-SQcK4W1x__VgvyaeVRuFlXcxV56TsR_nNK07Nr4b4,32763
37
36
  smart_bot_factory/core/router.py,sha256=ji7rzpuKaO8yKaxFW58WhlgG5ExXlbCgqCTONxAyqL4,15022
38
37
  smart_bot_factory/core/router_manager.py,sha256=dUwesog-oHk1U2EDdS8p0e4MTSkwtx5_qXn6nrJ9l9I,9700
@@ -40,10 +39,11 @@ smart_bot_factory/core/states.py,sha256=L8qp1UmYFuxTN5U9tY076rDuKgxtFbpSGqBpva2e
40
39
  smart_bot_factory/creation/__init__.py,sha256=IgDk8GDS3pg7Pw_Et41J33ZmeZIU5dRwQdTmYKXfJfE,128
41
40
  smart_bot_factory/creation/bot_builder.py,sha256=yGRmOPD7qCMbhcBiltHWISoKxWx8eqjDSnZXpwhqnUs,43115
42
41
  smart_bot_factory/creation/bot_testing.py,sha256=JDWXyJfZmbgo-DLdAPk8Sd9FiehtHHa4sLD17lBrTOc,55669
42
+ smart_bot_factory/dashboard/__init__.py,sha256=bDBOWQbcAL1Bmz4KVFouAKg8FN-c6EsC_-YthTt_mP4,100
43
43
  smart_bot_factory/event/__init__.py,sha256=hPL449RULIOB-OXv1ZbGNiHctAYaOMUqhSWGPrDHYBM,212
44
44
  smart_bot_factory/handlers/handlers.py,sha256=v3ubszkN7ML-CXChveTdp68EdMjHl02NTJ3hMT2zXKA,61893
45
45
  smart_bot_factory/integrations/openai_client.py,sha256=fwaJpwojFdLBWChcFWpFGOHK9upG-nCIwDochkCRRlY,24291
46
- smart_bot_factory/integrations/supabase_client.py,sha256=Rv0sZHXGSfm3UWodmaR1N-X5-2JbmynPWJKY0a0k_Tk,63557
46
+ smart_bot_factory/integrations/supabase_client.py,sha256=XV1_caDAADnXLwCM7CkUdGfUNBNHBHYz-HBPSYmdGv4,63653
47
47
  smart_bot_factory/message/__init__.py,sha256=-ehDZweUc3uKgmLLxFVsD-KWrDtnHpHms7pCrDelWo0,1950
48
48
  smart_bot_factory/router/__init__.py,sha256=5gEbpG3eylOyow5NmidzGUy0K-AZq7RhYLVu9OaUT6c,270
49
49
  smart_bot_factory/supabase/__init__.py,sha256=XmZP6yM9ffERM5ddAWyJnrNzEhCYtMu3AcjVCi1rOf8,179
@@ -52,8 +52,8 @@ smart_bot_factory/utils/__init__.py,sha256=UhsJXEHfrIK8h1AHsroHSwAriijk-LvnqLyvg
52
52
  smart_bot_factory/utils/debug_routing.py,sha256=BOoDhKBg7UXe5uHQxRk3TSfPfLPOFqt0N7lAo6kjCOo,4719
53
53
  smart_bot_factory/utils/prompt_loader.py,sha256=HS_6Vf-qvRBkhvyzu-HNVS1swFgmqWOKNNv0F6We_AQ,20060
54
54
  smart_bot_factory/utils/user_prompt_loader.py,sha256=dk6P0X_3UcNqxjRtuIvb0LcPrp03zIIsstZwdmeCPaE,2519
55
- smart_bot_factory-0.3.5.dist-info/METADATA,sha256=xOG5tl-e-w8yfvxuZfiyVRnQGdEvUxYLMPbKVGTcbmY,31905
56
- smart_bot_factory-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
- smart_bot_factory-0.3.5.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
58
- smart_bot_factory-0.3.5.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
59
- smart_bot_factory-0.3.5.dist-info/RECORD,,
55
+ smart_bot_factory-0.3.6.dist-info/METADATA,sha256=1szKQIjRny6BWbsR5rNpeDUXZikcp7g4I9E8LIBYeqg,40662
56
+ smart_bot_factory-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
+ smart_bot_factory-0.3.6.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
58
+ smart_bot_factory-0.3.6.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
59
+ smart_bot_factory-0.3.6.dist-info/RECORD,,
@@ -1,136 +0,0 @@
1
- -- ФИНАЛЬНАЯ МИГРАЦИЯ АДМИНСКОЙ СИСТЕМЫ
2
- -- Выполните ПОСЛЕ исправления уникальности telegram_id
3
-
4
- -- 1. Расширяем существующие таблицы
5
- ALTER TABLE sales_chat_sessions
6
- ADD COLUMN IF NOT EXISTS current_stage TEXT,
7
- ADD COLUMN IF NOT EXISTS lead_quality_score INTEGER;
8
-
9
- ALTER TABLE sales_messages
10
- ADD COLUMN IF NOT EXISTS ai_metadata JSONB DEFAULT '{}'::jsonb;
11
-
12
- -- 2. Создаем функцию обновления updated_at
13
- CREATE OR REPLACE FUNCTION update_updated_at_column()
14
- RETURNS TRIGGER AS $$
15
- BEGIN
16
- NEW.updated_at = NOW();
17
- RETURN NEW;
18
- END;
19
- $$ LANGUAGE plpgsql;
20
-
21
- -- 3. Таблица администраторов
22
- CREATE TABLE IF NOT EXISTS sales_admins (
23
- id BIGSERIAL PRIMARY KEY,
24
- telegram_id BIGINT UNIQUE NOT NULL,
25
- username TEXT,
26
- first_name TEXT,
27
- last_name TEXT,
28
- role TEXT DEFAULT 'admin',
29
- is_active BOOLEAN DEFAULT TRUE,
30
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
31
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
32
- );
33
-
34
- -- 4. Диалоги админов с пользователями
35
- CREATE TABLE IF NOT EXISTS admin_user_conversations (
36
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
37
- admin_id BIGINT REFERENCES sales_admins(telegram_id) ON DELETE CASCADE,
38
- user_id BIGINT REFERENCES sales_users(telegram_id) ON DELETE CASCADE,
39
- session_id UUID REFERENCES sales_chat_sessions(id) ON DELETE CASCADE,
40
- status TEXT DEFAULT 'active' CHECK (status IN ('active', 'completed')),
41
- started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
42
- ended_at TIMESTAMP WITH TIME ZONE,
43
- auto_end_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '30 minutes'
44
- );
45
-
46
- -- 5. События из ответов ИИ
47
- CREATE TABLE IF NOT EXISTS session_events (
48
- id BIGSERIAL PRIMARY KEY,
49
- session_id UUID REFERENCES sales_chat_sessions(id) ON DELETE CASCADE,
50
- event_type TEXT NOT NULL,
51
- event_info TEXT,
52
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
53
- notified_admins BIGINT[] DEFAULT '{}'
54
- );
55
-
56
- -- 6. Индексы
57
- CREATE INDEX IF NOT EXISTS idx_sales_admins_telegram_id ON sales_admins(telegram_id);
58
- CREATE INDEX IF NOT EXISTS idx_admin_conversations_status ON admin_user_conversations(status);
59
- CREATE INDEX IF NOT EXISTS idx_admin_conversations_admin ON admin_user_conversations(admin_id);
60
- CREATE INDEX IF NOT EXISTS idx_admin_conversations_user ON admin_user_conversations(user_id);
61
- CREATE INDEX IF NOT EXISTS idx_session_events_type ON session_events(event_type);
62
- CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id);
63
- CREATE INDEX IF NOT EXISTS idx_sales_chat_sessions_stage ON sales_chat_sessions(current_stage);
64
- CREATE INDEX IF NOT EXISTS idx_sales_messages_metadata ON sales_messages USING gin(ai_metadata);
65
-
66
- -- 7. Триггер для sales_admins
67
- DROP TRIGGER IF EXISTS update_sales_admins_updated_at ON sales_admins;
68
- CREATE TRIGGER update_sales_admins_updated_at
69
- BEFORE UPDATE ON sales_admins
70
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
71
-
72
- -- 8. Функция завершения просроченных диалогов
73
- CREATE OR REPLACE FUNCTION end_expired_admin_conversations()
74
- RETURNS INTEGER AS $$
75
- DECLARE
76
- ended_count INTEGER;
77
- BEGIN
78
- UPDATE admin_user_conversations
79
- SET status = 'completed', ended_at = NOW()
80
- WHERE status = 'active' AND auto_end_at < NOW();
81
-
82
- GET DIAGNOSTICS ended_count = ROW_COUNT;
83
- RETURN ended_count;
84
- END;
85
- $$ LANGUAGE plpgsql;
86
-
87
- -- 9. Представления для аналитики
88
- CREATE OR REPLACE VIEW funnel_stats AS
89
- SELECT
90
- current_stage,
91
- COUNT(*) as count,
92
- AVG(lead_quality_score) as avg_quality,
93
- ROUND(COUNT(*) * 100.0 / NULLIF(SUM(COUNT(*)) OVER(), 0), 1) as percentage
94
- FROM sales_chat_sessions
95
- WHERE created_at > NOW() - INTERVAL '7 days'
96
- AND current_stage IS NOT NULL
97
- GROUP BY current_stage;
98
-
99
- CREATE OR REPLACE VIEW daily_events AS
100
- SELECT
101
- DATE(created_at) as event_date,
102
- event_type,
103
- COUNT(*) as count
104
- FROM session_events
105
- WHERE created_at > NOW() - INTERVAL '30 days'
106
- GROUP BY DATE(created_at), event_type
107
- ORDER BY event_date DESC, event_type;
108
-
109
- -- 10. RLS политики
110
- ALTER TABLE sales_admins ENABLE ROW LEVEL SECURITY;
111
- ALTER TABLE admin_user_conversations ENABLE ROW LEVEL SECURITY;
112
- ALTER TABLE session_events ENABLE ROW LEVEL SECURITY;
113
-
114
- DROP POLICY IF EXISTS "Service role can manage all admins" ON sales_admins;
115
- DROP POLICY IF EXISTS "Service role can manage all conversations" ON admin_user_conversations;
116
- DROP POLICY IF EXISTS "Service role can manage all events" ON session_events;
117
-
118
- CREATE POLICY "Service role can manage all admins" ON sales_admins
119
- FOR ALL USING (current_setting('role') = 'service_role');
120
-
121
- CREATE POLICY "Service role can manage all conversations" ON admin_user_conversations
122
- FOR ALL USING (current_setting('role') = 'service_role');
123
-
124
- CREATE POLICY "Service role can manage all events" ON session_events
125
- FOR ALL USING (current_setting('role') = 'service_role');
126
-
127
- -- 11. Комментарии
128
- COMMENT ON TABLE sales_admins IS 'Администраторы бота';
129
- COMMENT ON TABLE admin_user_conversations IS 'Активные диалоги админов с пользователями';
130
- COMMENT ON TABLE session_events IS 'События из ответов ИИ для уведомлений';
131
-
132
- -- Финальная проверка
133
- SELECT
134
- 'АДМИНСКАЯ СИСТЕМА СОЗДАНА!' AS status,
135
- (SELECT COUNT(*) FROM information_schema.tables
136
- WHERE table_name IN ('sales_admins', 'admin_user_conversations', 'session_events')) AS tables_created;