Files
aiform_prod/backend/app/workers/ocr_worker.py
AI Assistant 647abf6578 feat: Интеграция n8n + Redis Pub/Sub + SSE для real-time обработки заявок
🎯 Основные изменения:

Backend:
-  Добавлен SSE endpoint для real-time событий (/api/v1/events/{task_id})
-  Redis Pub/Sub для публикации/подписки на события OCR/Vision
-  Удален aioboto3 из requirements.txt (конфликт зависимостей)
-  Добавлен OCR worker (deprecated, логика перенесена в n8n)

Frontend (React):
-  Автогенерация claim_id и session_id
-  Клиентская конвертация файлов в PDF (JPG/PNG/HEIC/WEBP)
-  Сжатие изображений до 2MB перед конвертацией
-  SSE подписка на события OCR/Vision в Step1Policy
-  Валидация документов (полис vs неподходящий контент)
-  Real-time прогресс загрузки и обработки файлов
-  Интеграция с n8n webhooks для проверки полиса и загрузки файлов

n8n Workflows:
-  Проверка полиса в MySQL + запись в PostgreSQL
-  Загрузка файлов в S3 + OCR + Vision AI
-  Публикация событий в Redis через backend API
-  Валидация документов (распознавание полисов ERV)

Документация:
- 📝 N8N_INTEGRATION.md - интеграция с n8n
- 📝 N8N_SQL_QUERIES.md - SQL запросы для workflows
- 📝 N8N_PDF_COMPRESS.md - сжатие PDF
- 📝 N8N_STIRLING_COMPRESS.md - интеграция Stirling-PDF

Утилиты:
- 🔧 monitor_redis.py/sh - мониторинг Redis Pub/Sub
- 🔧 test_redis_events.sh - тестирование событий
- 🔧 pdfConverter.ts - клиентская конвертация в PDF

Архитектура:
React → n8n webhooks (sync) → MySQL/PostgreSQL/S3
      → n8n workflows (async) → OCR/Vision → Redis Pub/Sub → SSE → React
2025-10-27 08:33:16 +03:00

159 lines
5.6 KiB
Python

"""
OCR Worker - обработка файлов в фоне через RabbitMQ + Redis Pub/Sub
"""
import asyncio
import json
import logging
from typing import Dict, Any
from aio_pika import connect_robust, IncomingMessage
from app.config import settings
from app.services.ocr_service import ocr_service
from app.services.redis_service import redis_service
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class OCRWorker:
"""Worker для обработки OCR задач в фоне"""
def __init__(self):
self.connection = None
self.channel = None
self.queue_name = "erv_ocr_processing"
async def connect(self):
"""Подключение к RabbitMQ"""
self.connection = await connect_robust(settings.rabbitmq_url)
self.channel = await self.connection.channel()
await self.channel.set_qos(prefetch_count=1) # По одной задаче
self.queue = await self.channel.declare_queue(
self.queue_name,
durable=True
)
logger.info(f"✅ Worker connected to RabbitMQ: {self.queue_name}")
async def publish_event(self, task_id: str, event: Dict[str, Any]):
"""
Публикация события в Redis для real-time обновлений
Args:
task_id: ID задачи
event: Данные события
"""
channel = f"ocr_events:{task_id}"
event_json = json.dumps(event, ensure_ascii=False)
try:
await redis_service.publish(channel, event_json)
logger.info(f"📢 Event published to {channel}: {event['status']}")
except Exception as e:
logger.error(f"❌ Failed to publish event: {e}")
async def process_task(self, message: IncomingMessage):
"""
Обработка задачи OCR
Args:
message: Сообщение из RabbitMQ
"""
async with message.process():
try:
# Парсим задачу
task = json.loads(message.body.decode())
task_id = task["task_id"]
file_content = bytes.fromhex(task["file_content_hex"])
filename = task["filename"]
logger.info(f"🔄 Processing task {task_id}: {filename}")
# Событие: начало обработки
await self.publish_event(task_id, {
"status": "processing",
"message": "Начата обработка файла",
"filename": filename
})
# Шаг 1: OCR обработка
await self.publish_event(task_id, {
"status": "ocr_started",
"message": "Запущено распознавание текста"
})
result = await ocr_service.process_document(file_content, filename)
# Событие: OCR завершён
await self.publish_event(task_id, {
"status": "ocr_completed",
"message": f"Распознано {len(result['ocr_text'])} символов",
"chars": len(result['ocr_text'])
})
# Шаг 2: AI анализ (если есть текст)
if result['ocr_text']:
await self.publish_event(task_id, {
"status": "ai_started",
"message": "Запущен AI анализ документа"
})
# Событие: всё готово
await self.publish_event(task_id, {
"status": "completed",
"message": "Обработка завершена",
"result": {
"document_type": result["document_type"],
"is_valid": result["is_valid"],
"confidence": result["confidence"],
"extracted_data": result["extracted_data"],
"ocr_text_length": len(result["ocr_text"])
}
})
# Сохраняем результат в Redis (TTL 1 час)
cache_key = f"ocr_result:{task_id}"
await redis_service.set_json(cache_key, result, ttl=3600)
logger.info(f"✅ Task {task_id} completed successfully")
except Exception as e:
logger.error(f"❌ Task processing error: {e}")
# Событие: ошибка
await self.publish_event(task_id, {
"status": "error",
"message": f"Ошибка обработки: {str(e)}"
})
async def start(self):
"""Запуск worker"""
await self.connect()
logger.info(f"🚀 OCR Worker started, waiting for tasks...")
# Слушаем очередь
await self.queue.consume(self.process_task)
# Держим worker живым
try:
await asyncio.Future()
except KeyboardInterrupt:
logger.info("👋 Worker stopped")
async def main():
"""Точка входа"""
worker = OCRWorker()
await worker.start()
if __name__ == "__main__":
asyncio.run(main())