add autorestart rec
This commit is contained in:
@@ -1,3 +1,84 @@
|
|||||||
|
"""
|
||||||
|
API endpoints для управления аудиофайлами (регистрация и пакетная обработка)
|
||||||
|
Используется Calls_WEB_Client_main для оркестрации процесса распознавания
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional, List
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from apiApp.database import get_db
|
||||||
|
from apiApp.database.Audio import Audio
|
||||||
|
from apiApp.database.AiConclusion import AiConclusion
|
||||||
|
from apiApp.config import AUDIOFILES_PATH
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
audio_management_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def query_audio_without_conclusion(db, limit=None):
|
||||||
|
"""
|
||||||
|
Возвращает запрос для поиска Audio без AiConclusion
|
||||||
|
|
||||||
|
Использует exists() подзапрос, так как AiConclusion - это relationship
|
||||||
|
"""
|
||||||
|
from sqlalchemy import exists
|
||||||
|
|
||||||
|
subquery = db.query(AiConclusion.audio_id).filter(
|
||||||
|
AiConclusion.audio_id == Audio.id
|
||||||
|
)
|
||||||
|
|
||||||
|
query = db.query(Audio).filter(
|
||||||
|
~exists().where(subquery.exists())
|
||||||
|
).order_by(Audio.index_date.asc())
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
query = query.limit(limit)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
class AudioRegisterRequest(BaseModel):
|
||||||
|
"""Запрос на регистрацию аудиофайла"""
|
||||||
|
filename: str
|
||||||
|
file_path: str # Полный путь к файлу в общей папке audiofiles
|
||||||
|
|
||||||
|
|
||||||
|
class AudioProcessAllRequest(BaseModel):
|
||||||
|
"""Запрос на пакетное распознавание"""
|
||||||
|
limit: int = 100
|
||||||
|
|
||||||
|
|
||||||
|
class AudioRegisterResponse(BaseModel):
|
||||||
|
"""Ответ на регистрацию аудиофайла"""
|
||||||
|
id: str
|
||||||
|
filename: str
|
||||||
|
file_size: int
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
@audio_management_router.post("/audio/register", response_model=AudioRegisterResponse, status_code=201)
|
||||||
|
async def register_audio_file(
|
||||||
|
request: AudioRegisterRequest,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Регистрация аудиофайла в БД (без копирования файла)
|
||||||
|
|
||||||
|
Создаёт запись в таблице Audio для файла, который уже находится
|
||||||
|
в общей папке audiofiles. НЕ копирует файл, только создаёт запись в БД.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: {filename: "in-xxx.wav", file_path: "/app/audiofiles/in-xxx.wav"}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
201 Created + информация о созданной записи
|
||||||
|
400 Bad Request если файл уже зарегистрирован
|
||||||
|
404 Not Found если файл не существует на диске
|
||||||
|
"""
|
||||||
|
try:
|
||||||
filename = request.filename
|
filename = request.filename
|
||||||
file_path = request.file_path
|
file_path = request.file_path
|
||||||
|
|
||||||
@@ -111,24 +192,13 @@ async def process_all_pending_audio(
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import exists
|
|
||||||
|
|
||||||
limit = request.limit
|
limit = request.limit
|
||||||
|
|
||||||
logger.info(f"🚀 Поиск Audio без AiConclusion (limit={limit})")
|
logger.info(f"🚀 Поиск Audio без AiConclusion (limit={limit})")
|
||||||
|
|
||||||
# Находим все Audio без AiConclusion через подзапрос
|
# Находим все Audio без AiConclusion используя вспомогательную функцию
|
||||||
subquery = db.query(AiConclusion.audio_id).filter(
|
pending_audio = query_audio_without_conclusion(db, limit).all()
|
||||||
AiConclusion.audio_id == Audio.id
|
total_pending = query_audio_without_conclusion(db).count()
|
||||||
)
|
|
||||||
|
|
||||||
pending_audio = db.query(Audio).filter(
|
|
||||||
~exists().where(subquery.exists())
|
|
||||||
).order_by(Audio.index_date.asc()).limit(limit).all()
|
|
||||||
|
|
||||||
total_pending = db.query(Audio).filter(
|
|
||||||
~exists().where(subquery.exists())
|
|
||||||
).count()
|
|
||||||
|
|
||||||
if not pending_audio:
|
if not pending_audio:
|
||||||
logger.info("ℹ️ Нет файлов для распознавания")
|
logger.info("ℹ️ Нет файлов для распознавания")
|
||||||
@@ -211,15 +281,7 @@ async def get_pending_audio(
|
|||||||
Список файлов, ожидающих распознавания
|
Список файлов, ожидающих распознавания
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import exists
|
pending_audio = query_audio_without_conclusion(db, limit).all()
|
||||||
|
|
||||||
subquery = db.query(AiConclusion.audio_id).filter(
|
|
||||||
AiConclusion.audio_id == Audio.id
|
|
||||||
)
|
|
||||||
|
|
||||||
pending_audio = db.query(Audio).filter(
|
|
||||||
~exists().where(subquery.exists())
|
|
||||||
).order_by(Audio.index_date.asc()).limit(limit).all()
|
|
||||||
|
|
||||||
files_info = []
|
files_info = []
|
||||||
for audio in pending_audio:
|
for audio in pending_audio:
|
||||||
@@ -234,9 +296,7 @@ async def get_pending_audio(
|
|||||||
"exists_on_disk": exists
|
"exists_on_disk": exists
|
||||||
})
|
})
|
||||||
|
|
||||||
total_pending = db.query(Audio).filter(
|
total_pending = query_audio_without_conclusion(db).count()
|
||||||
~exists().where(subquery.exists())
|
|
||||||
).count()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total_pending": total_pending,
|
"total_pending": total_pending,
|
||||||
@@ -261,19 +321,9 @@ async def get_audio_stats(db: Session = Depends(get_db)):
|
|||||||
Статистика по Audio записям
|
Статистика по Audio записям
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import exists
|
|
||||||
|
|
||||||
total_audio = db.query(Audio).count()
|
total_audio = db.query(Audio).count()
|
||||||
|
with_conclusion = total_audio - query_audio_without_conclusion(db).count()
|
||||||
subquery = db.query(AiConclusion.audio_id).filter(
|
without_conclusion = query_audio_without_conclusion(db).count()
|
||||||
AiConclusion.audio_id == Audio.id
|
|
||||||
)
|
|
||||||
|
|
||||||
with_conclusion = db.query(Audio).filter(
|
|
||||||
exists().where(subquery.exists())
|
|
||||||
).count()
|
|
||||||
|
|
||||||
without_conclusion = total_audio - with_conclusion
|
|
||||||
|
|
||||||
# Проверяем существование файлов на диске
|
# Проверяем существование файлов на диске
|
||||||
all_audio = db.query(Audio).all()
|
all_audio = db.query(Audio).all()
|
||||||
@@ -299,62 +349,3 @@ async def get_audio_stats(db: Session = Depends(get_db)):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def auto_restore_on_startup(db: Session, limit: int = 100):
|
|
||||||
"""
|
|
||||||
Автоматическое восстановление распознавания при старте FileAudioAPI
|
|
||||||
|
|
||||||
Проверяет, есть ли файлы без AiConclusion, и запускает их распознавание
|
|
||||||
|
|
||||||
Args:
|
|
||||||
db: Сессия БД
|
|
||||||
limit: Максимум файлов для восстановления
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
from sqlalchemy import or_
|
|
||||||
|
|
||||||
# Проверяем, есть ли файлы без AiConclusion
|
|
||||||
pending_audio = db.query(Audio).filter(
|
|
||||||
or_(
|
|
||||||
Audio.AiConclusion == None,
|
|
||||||
Audio.AiConclusion == ''
|
|
||||||
)
|
|
||||||
).limit(limit).all()
|
|
||||||
|
|
||||||
if not pending_audio:
|
|
||||||
logger.info("ℹ️ Auto-restore: нет файлов для распознавания")
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info(f"🔄 Auto-restore: найдено {len(pending_audio)} файлов без AiConclusion")
|
|
||||||
|
|
||||||
# Запускаем распознавание
|
|
||||||
started_count = 0
|
|
||||||
for audio in pending_audio:
|
|
||||||
file_path = os.path.join(AUDIOFILES_PATH, audio.filename)
|
|
||||||
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
logger.warning(f"⚠️ Файл не найден: {audio.filename}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Отправляем в GigaAM API
|
|
||||||
from apiApp.config import GIGAAM_API_URL
|
|
||||||
api_url = f"{GIGAAM_API_URL}/api/call/process"
|
|
||||||
|
|
||||||
payload = {"filename": audio.filename}
|
|
||||||
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
response = requests.post(api_url, json=payload, timeout=5)
|
|
||||||
|
|
||||||
if response.status_code in [200, 202]:
|
|
||||||
logger.info(f"✅ Запущено распознавание: {audio.filename}")
|
|
||||||
started_count += 1
|
|
||||||
else:
|
|
||||||
logger.warning(f"⚠️ Ошибка запуска {audio.filename}: {response.status_code}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Ошибка при запуске {audio.filename}: {e}")
|
|
||||||
|
|
||||||
logger.info(f"🎉 Auto-restore завершено: запущено {started_count} файлов")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Ошибка при auto-restore: {e}")
|
|
||||||
|
|||||||
Reference in New Issue
Block a user