mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-05 03:12:34 -06:00
2612-есть ок OCR, нужно допиливать бота под новый flow для операторов
This commit is contained in:
114
ocr-service/app/services/ocr.py
Normal file
114
ocr-service/app/services/ocr.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from app.services.auth import yandex_auth
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VISION_URL = "https://ocr.api.cloud.yandex.net/ocr/v1/recognizeText"
|
||||
|
||||
class OCREngine(ABC):
|
||||
@abstractmethod
|
||||
def recognize(self, image_bytes: bytes) -> str:
|
||||
"""Распознает текст из изображения и возвращает строку."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_configured(self) -> bool:
|
||||
"""Проверяет, настроен ли движок (наличие ключей/настроек)."""
|
||||
pass
|
||||
|
||||
class YandexOCREngine(OCREngine):
|
||||
def __init__(self):
|
||||
self.folder_id = os.getenv("YANDEX_FOLDER_ID")
|
||||
|
||||
if not yandex_auth.is_configured() or not self.folder_id:
|
||||
logger.warning("Yandex OCR credentials (YANDEX_OAUTH_TOKEN, YANDEX_FOLDER_ID) not set. Yandex OCR will be unavailable.")
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
return yandex_auth.is_configured() and bool(self.folder_id)
|
||||
|
||||
def recognize(self, image_bytes: bytes) -> str:
|
||||
"""
|
||||
Отправляет изображение в Yandex Vision и возвращает полный текст.
|
||||
"""
|
||||
if not self.is_configured():
|
||||
logger.error("Yandex credentials missing.")
|
||||
return ""
|
||||
|
||||
iam_token = yandex_auth.get_iam_token()
|
||||
if not iam_token:
|
||||
return ""
|
||||
|
||||
# 1. Кодируем в Base64
|
||||
b64_image = base64.b64encode(image_bytes).decode("utf-8")
|
||||
|
||||
# 2. Формируем тело запроса
|
||||
# Используем модель 'page' (для документов) и '*' для автоопределения языка
|
||||
payload = {
|
||||
"mimeType": "JPEG", # Yandex переваривает и PNG под видом JPEG часто, но лучше быть аккуратным.
|
||||
# В идеале определять mime-type из файла, но JPEG - безопасный дефолт для фото.
|
||||
"languageCodes": ["*"],
|
||||
"model": "page",
|
||||
"content": b64_image
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {iam_token}",
|
||||
"x-folder-id": self.folder_id,
|
||||
"x-data-logging-enabled": "true"
|
||||
}
|
||||
|
||||
# 3. Отправляем запрос
|
||||
try:
|
||||
logger.info("Sending request to Yandex Vision OCR...")
|
||||
response = requests.post(VISION_URL, headers=headers, json=payload, timeout=20)
|
||||
|
||||
# Если 401 Unauthorized, возможно токен протух раньше времени (редко, но бывает)
|
||||
if response.status_code == 401:
|
||||
logger.warning("Got 401 from Yandex. Retrying with fresh token...")
|
||||
yandex_auth.reset_token() # сброс кэша
|
||||
iam_token = yandex_auth.get_iam_token()
|
||||
if iam_token:
|
||||
headers["Authorization"] = f"Bearer {iam_token}"
|
||||
response = requests.post(VISION_URL, headers=headers, json=payload, timeout=20)
|
||||
|
||||
response.raise_for_status()
|
||||
result_json = response.json()
|
||||
|
||||
# 4. Парсим ответ
|
||||
# Структура: result -> textAnnotation -> fullText
|
||||
# Или (если fullText нет) blocks -> lines -> text
|
||||
|
||||
text_annotation = result_json.get("result", {}).get("textAnnotation", {})
|
||||
|
||||
if not text_annotation:
|
||||
logger.warning("Yandex returned success but no textAnnotation found.")
|
||||
return ""
|
||||
|
||||
# Самый простой способ - взять fullText, он обычно склеен с \n
|
||||
full_text = text_annotation.get("fullText", "")
|
||||
|
||||
if not full_text:
|
||||
# Фолбэк: если fullText пуст, собираем вручную по блокам
|
||||
logger.info("fullText empty, assembling from blocks...")
|
||||
lines_text = []
|
||||
for block in text_annotation.get("blocks", []):
|
||||
for line in block.get("lines", []):
|
||||
lines_text.append(line.get("text", ""))
|
||||
full_text = "\n".join(lines_text)
|
||||
|
||||
return full_text
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during Yandex Vision request: {e}")
|
||||
return ""
|
||||
|
||||
# Глобальный инстанс
|
||||
yandex_engine = YandexOCREngine()
|
||||
Reference in New Issue
Block a user