|
|
|
@ -5,6 +5,8 @@ import requests |
|
|
|
from sqlalchemy import inspect |
|
|
|
from sqlalchemy import inspect |
|
|
|
from typing import List, Optional |
|
|
|
from typing import List, Optional |
|
|
|
import logging |
|
|
|
import logging |
|
|
|
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed |
|
|
|
|
|
|
|
import threading |
|
|
|
|
|
|
|
|
|
|
|
from autoLoader.database import get_db_session, Audio, AiConclusion |
|
|
|
from autoLoader.database import get_db_session, Audio, AiConclusion |
|
|
|
from autoLoader.config import GIGAAM_API_URL |
|
|
|
from autoLoader.config import GIGAAM_API_URL |
|
|
|
@ -20,12 +22,13 @@ logger = logging.getLogger(__name__) |
|
|
|
class RecognitionChecker: |
|
|
|
class RecognitionChecker: |
|
|
|
"""Класс для проверки и отправки файлов на распознавание""" |
|
|
|
"""Класс для проверки и отправки файлов на распознавание""" |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, api_url: Optional[str] = None): |
|
|
|
def __init__(self, api_url: Optional[str] = None, max_workers: int = 5): |
|
|
|
""" |
|
|
|
""" |
|
|
|
Инициализация checker |
|
|
|
Инициализация checker |
|
|
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
Args: |
|
|
|
api_url: URL API GigaAM для распознавания (если None, берётся из config) |
|
|
|
api_url: URL API GigaAM для распознавания (если None, берётся из config) |
|
|
|
|
|
|
|
max_workers: Максимальное количество параллельных запросов |
|
|
|
""" |
|
|
|
""" |
|
|
|
# Если api_url не передан, берём из config.py |
|
|
|
# Если api_url не передан, берём из config.py |
|
|
|
if api_url is None: |
|
|
|
if api_url is None: |
|
|
|
@ -33,8 +36,15 @@ class RecognitionChecker: |
|
|
|
|
|
|
|
|
|
|
|
self.api_url = f"{api_url}/api/call/process" |
|
|
|
self.api_url = f"{api_url}/api/call/process" |
|
|
|
self.timeout = 10 # таймаут запроса в секундах |
|
|
|
self.timeout = 10 # таймаут запроса в секундах |
|
|
|
|
|
|
|
self.max_workers = max_workers # количество параллельных потоков |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Thread-safe счётчики для статистики |
|
|
|
|
|
|
|
self._lock = threading.Lock() |
|
|
|
|
|
|
|
self._sent_count = 0 |
|
|
|
|
|
|
|
self._failed_count = 0 |
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"✅ RecognitionChecker инициализирован с URL: {self.api_url}") |
|
|
|
logger.info(f"✅ RecognitionChecker инициализирован с URL: {self.api_url}") |
|
|
|
|
|
|
|
logger.info(f"📊 Параллельная отправка: до {max_workers} запросов одновременно") |
|
|
|
|
|
|
|
|
|
|
|
def check_database(self) -> bool: |
|
|
|
def check_database(self) -> bool: |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -120,7 +130,7 @@ class RecognitionChecker: |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
logger.info(f"📤 Отправка файла {audio.filename} на распознавание...") |
|
|
|
logger.info(f"📤 [Thread-{threading.current_thread().name}] Отправка файла {audio.filename} на распознавание...") |
|
|
|
|
|
|
|
|
|
|
|
response = requests.post( |
|
|
|
response = requests.post( |
|
|
|
self.api_url, |
|
|
|
self.api_url, |
|
|
|
@ -129,38 +139,56 @@ class RecognitionChecker: |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if response.status_code == 200 or response.status_code == 202: |
|
|
|
if response.status_code == 200 or response.status_code == 202: |
|
|
|
logger.info(f"✅ Файл {audio.filename} успешно отправлен на распознавание") |
|
|
|
logger.info(f"✅ [Thread-{threading.current_thread().name}] Файл {audio.filename} успешно отправлен") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Thread-safe обновление счётчиков |
|
|
|
|
|
|
|
with self._lock: |
|
|
|
|
|
|
|
self._sent_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
return True |
|
|
|
return True |
|
|
|
else: |
|
|
|
else: |
|
|
|
logger.error(f"❌ Ошибка API {response.status_code}: {response.text}") |
|
|
|
logger.error(f"❌ [Thread-{threading.current_thread().name}] Ошибка API {response.status_code}: {response.text}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Thread-safe обновление счётчиков |
|
|
|
|
|
|
|
with self._lock: |
|
|
|
|
|
|
|
self._failed_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
return False |
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
except requests.exceptions.Timeout: |
|
|
|
except requests.exceptions.Timeout: |
|
|
|
logger.error(f"❌ Таймаут при отправке файла {audio.filename}") |
|
|
|
logger.error(f"❌ [Thread-{threading.current_thread().name}] Таймаут при отправке файла {audio.filename}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with self._lock: |
|
|
|
|
|
|
|
self._failed_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
return False |
|
|
|
return False |
|
|
|
except requests.exceptions.ConnectionError: |
|
|
|
except requests.exceptions.ConnectionError: |
|
|
|
logger.error(f"❌ Не удалось подключиться к API {self.api_url}") |
|
|
|
logger.error(f"❌ [Thread-{threading.current_thread().name}] Не удалось подключиться к API {self.api_url}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with self._lock: |
|
|
|
|
|
|
|
self._failed_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
return False |
|
|
|
return False |
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
logger.error(f"❌ Ошибка при отправке {audio.filename}: {e}") |
|
|
|
logger.error(f"❌ [Thread-{threading.current_thread().name}] Ошибка при отправке {audio.filename}: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with self._lock: |
|
|
|
|
|
|
|
self._failed_count += 1 |
|
|
|
|
|
|
|
|
|
|
|
return False |
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
def process_all_pending(self, limit: Optional[int] = None) -> dict: |
|
|
|
def send_to_recognition_parallel(self, audio_list: List[Audio]) -> dict: |
|
|
|
""" |
|
|
|
""" |
|
|
|
Находит и отправляет все файлы без заключения на распознавание |
|
|
|
Отправляет несколько файлов на распознавание параллельно |
|
|
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
Args: |
|
|
|
limit: Максимальное количество файлов для обработки |
|
|
|
audio_list: Список объектов Audio для распознавания |
|
|
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
Returns: |
|
|
|
Словарь с результатами обработки |
|
|
|
Словарь с результатами отправки |
|
|
|
""" |
|
|
|
""" |
|
|
|
logger.info("🔍 Поиск файлов без AI заключения...") |
|
|
|
if not audio_list: |
|
|
|
|
|
|
|
logger.info("⏭️ Список файлов пуст, нечего отправлять") |
|
|
|
files_without_conclusion = self.get_files_without_conclusion(limit) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not files_without_conclusion: |
|
|
|
|
|
|
|
logger.info("✅ Все файлы обработаны") |
|
|
|
|
|
|
|
return { |
|
|
|
return { |
|
|
|
"total": 0, |
|
|
|
"total": 0, |
|
|
|
"sent": 0, |
|
|
|
"sent": 0, |
|
|
|
@ -168,37 +196,128 @@ class RecognitionChecker: |
|
|
|
"files": [] |
|
|
|
"files": [] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Сбрасываем счётчики |
|
|
|
|
|
|
|
self._sent_count = 0 |
|
|
|
|
|
|
|
self._failed_count = 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"🚀 Начинаем параллельную отправку {len(audio_list)} файлов") |
|
|
|
|
|
|
|
logger.info(f"📊 Количество потоков: {self.max_workers}") |
|
|
|
|
|
|
|
|
|
|
|
results = { |
|
|
|
results = { |
|
|
|
"total": len(files_without_conclusion), |
|
|
|
"total": len(audio_list), |
|
|
|
"sent": 0, |
|
|
|
"sent": 0, |
|
|
|
"failed": 0, |
|
|
|
"failed": 0, |
|
|
|
"files": [] |
|
|
|
"files": [] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for audio in files_without_conclusion: |
|
|
|
# Используем ThreadPoolExecutor для параллельной отправки |
|
|
|
success = self.send_to_recognition(audio) |
|
|
|
with ThreadPoolExecutor(max_workers=self.max_workers, thread_name_prefix="SendReq") as executor: |
|
|
|
|
|
|
|
# Запускаем все задачи |
|
|
|
result = { |
|
|
|
future_to_audio = { |
|
|
|
"filename": audio.filename, |
|
|
|
executor.submit(self.send_to_recognition, audio): audio |
|
|
|
"audio_id": str(audio.id), |
|
|
|
for audio in audio_list |
|
|
|
"success": success |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
results["files"].append(result) |
|
|
|
# Обрабатываем результаты по мере завершения |
|
|
|
|
|
|
|
for future in as_completed(future_to_audio): |
|
|
|
|
|
|
|
audio = future_to_audio[future] |
|
|
|
|
|
|
|
|
|
|
|
if success: |
|
|
|
try: |
|
|
|
results["sent"] += 1 |
|
|
|
success = future.result() |
|
|
|
else: |
|
|
|
|
|
|
|
results["failed"] += 1 |
|
|
|
result = { |
|
|
|
|
|
|
|
"filename": audio.filename, |
|
|
|
|
|
|
|
"audio_id": str(audio.id), |
|
|
|
|
|
|
|
"success": success |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
results["files"].append(result) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as exc: |
|
|
|
|
|
|
|
logger.error(f"❌ Файл {audio.filename} сгенерировал исключение: {exc}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = { |
|
|
|
|
|
|
|
"filename": audio.filename, |
|
|
|
|
|
|
|
"audio_id": str(audio.id), |
|
|
|
|
|
|
|
"success": False, |
|
|
|
|
|
|
|
"error": str(exc) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
results["files"].append(result) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Получаем итоговую статистику из счётчиков |
|
|
|
|
|
|
|
results["sent"] = self._sent_count |
|
|
|
|
|
|
|
results["failed"] = self._failed_count |
|
|
|
|
|
|
|
|
|
|
|
# Логирование итогов |
|
|
|
# Логирование итогов |
|
|
|
logger.info(f"📊 Итого:") |
|
|
|
logger.info(f"📊 Итого параллельной отправки:") |
|
|
|
logger.info(f" - Всего: {results['total']}") |
|
|
|
logger.info(f" - Всего: {results['total']}") |
|
|
|
logger.info(f" - Отправлено: {results['sent']}") |
|
|
|
logger.info(f" - Отправлено: {results['sent']}") |
|
|
|
logger.info(f" - Ошибок: {results['failed']}") |
|
|
|
logger.info(f" - Ошибок: {results['failed']}") |
|
|
|
|
|
|
|
|
|
|
|
return results |
|
|
|
return results |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_all_pending(self, limit: Optional[int] = None, parallel: bool = True) -> dict: |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Находит и отправляет все файлы без заключения на распознавание |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
|
|
|
limit: Максимальное количество файлов для обработки |
|
|
|
|
|
|
|
parallel: Использовать параллельную отправку (по умолчанию True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
|
|
|
Словарь с результатами обработки |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
logger.info("🔍 Поиск файлов без AI заключения...") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
files_without_conclusion = self.get_files_without_conclusion(limit) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not files_without_conclusion: |
|
|
|
|
|
|
|
logger.info("✅ Все файлы обработаны") |
|
|
|
|
|
|
|
return { |
|
|
|
|
|
|
|
"total": 0, |
|
|
|
|
|
|
|
"sent": 0, |
|
|
|
|
|
|
|
"failed": 0, |
|
|
|
|
|
|
|
"files": [] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Выбираем метод отправки |
|
|
|
|
|
|
|
if parallel: |
|
|
|
|
|
|
|
logger.info("🚀 Используем параллельную отправку") |
|
|
|
|
|
|
|
return self.send_to_recognition_parallel(files_without_conclusion) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
logger.info("📤 Используем последовательную отправку") |
|
|
|
|
|
|
|
results = { |
|
|
|
|
|
|
|
"total": len(files_without_conclusion), |
|
|
|
|
|
|
|
"sent": 0, |
|
|
|
|
|
|
|
"failed": 0, |
|
|
|
|
|
|
|
"files": [] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for audio in files_without_conclusion: |
|
|
|
|
|
|
|
success = self.send_to_recognition(audio) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result = { |
|
|
|
|
|
|
|
"filename": audio.filename, |
|
|
|
|
|
|
|
"audio_id": str(audio.id), |
|
|
|
|
|
|
|
"success": success |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
results["files"].append(result) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if success: |
|
|
|
|
|
|
|
results["sent"] += 1 |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
results["failed"] += 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Логирование итогов |
|
|
|
|
|
|
|
logger.info(f"📊 Итого:") |
|
|
|
|
|
|
|
logger.info(f" - Всего: {results['total']}") |
|
|
|
|
|
|
|
logger.info(f" - Отправлено: {results['sent']}") |
|
|
|
|
|
|
|
logger.info(f" - Ошибок: {results['failed']}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return results |
|
|
|
|
|
|
|
|
|
|
|
def check_api_availability(self) -> bool: |
|
|
|
def check_api_availability(self) -> bool: |
|
|
|
""" |
|
|
|
""" |
|
|
|
Проверяет доступность GigaAM API |
|
|
|
Проверяет доступность GigaAM API |
|
|
|
|