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:
159
ocr-service/app/main.py
Normal file
159
ocr-service/app/main.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI, File, UploadFile, HTTPException
|
||||
from pydantic import BaseModel
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# Импортируем модули
|
||||
from app.schemas.models import ParsedItem, RecognitionResult
|
||||
from app.services.qr import detect_and_decode_qr, fetch_data_from_api, extract_fiscal_data
|
||||
# Импортируем новый модуль
|
||||
from app.services.ocr import yandex_engine
|
||||
from app.services.llm import llm_parser
|
||||
from app.services.excel import extract_text_from_excel
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI(title="RMSER OCR Service (Cloud-only: QR + Yandex + GigaChat)")
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.post("/recognize", response_model=RecognitionResult)
|
||||
async def recognize_receipt(image: UploadFile = File(...)):
|
||||
"""
|
||||
Стратегия:
|
||||
1. Excel файл (.xlsx) -> Извлечение текста -> LLM парсинг
|
||||
2. QR Code + FNS API (Приоритет 1 - Идеальная точность)
|
||||
3. Yandex Vision OCR + LLM (Приоритет 2 - Высокая точность, если настроен)
|
||||
Если ничего не найдено, возвращает пустой результат.
|
||||
"""
|
||||
logger.info(f"Received file: {image.filename}, content_type: {image.content_type}")
|
||||
|
||||
# Проверка на Excel файл
|
||||
if image.filename and image.filename.lower().endswith('.xlsx'):
|
||||
logger.info("Processing Excel file...")
|
||||
try:
|
||||
content = await image.read()
|
||||
excel_text = extract_text_from_excel(content)
|
||||
|
||||
if excel_text:
|
||||
logger.info(f"Excel text extracted, length: {len(excel_text)}")
|
||||
logger.info("Calling LLM Manager to parse Excel text...")
|
||||
excel_result = llm_parser.parse_receipt(excel_text)
|
||||
|
||||
return RecognitionResult(
|
||||
source="excel_llm",
|
||||
items=excel_result["items"],
|
||||
raw_text=excel_text,
|
||||
doc_number=excel_result["doc_number"],
|
||||
doc_date=excel_result["doc_date"]
|
||||
)
|
||||
else:
|
||||
logger.warning("Excel file is empty or contains no text")
|
||||
return RecognitionResult(
|
||||
source="none",
|
||||
items=[],
|
||||
raw_text=""
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing Excel file: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Error processing Excel file: {str(e)}")
|
||||
|
||||
# Проверка на изображение
|
||||
if not image.content_type.startswith("image/"):
|
||||
raise HTTPException(status_code=400, detail="File must be an image or .xlsx file")
|
||||
|
||||
try:
|
||||
# Читаем сырые байты
|
||||
content = await image.read()
|
||||
|
||||
# Конвертируем в numpy для QR
|
||||
nparr = np.frombuffer(content, np.uint8)
|
||||
original_cv_image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
||||
|
||||
if original_cv_image is None:
|
||||
raise HTTPException(status_code=400, detail="Invalid image data")
|
||||
|
||||
# --- ЭТАП 1: QR Code Strategy ---
|
||||
logger.info("--- Stage 1: QR Code Detection ---")
|
||||
qr_raw = detect_and_decode_qr(original_cv_image)
|
||||
|
||||
if qr_raw:
|
||||
logger.info("QR found! Fetching data from API...")
|
||||
api_items = fetch_data_from_api(qr_raw)
|
||||
|
||||
if api_items:
|
||||
logger.info(f"Success: Retrieved {len(api_items)} items via QR API.")
|
||||
return RecognitionResult(
|
||||
source="qr_api",
|
||||
items=api_items,
|
||||
raw_text=f"QR Content: {qr_raw}"
|
||||
)
|
||||
else:
|
||||
logger.warning("QR found but API failed. Falling back to OCR.")
|
||||
else:
|
||||
logger.info("QR code not found. Proceeding to OCR.")
|
||||
|
||||
# --- ЭТАП 2: OCR + Virtual QR Strategy ---
|
||||
if yandex_engine.is_configured():
|
||||
logger.info("--- Stage 2: Yandex Vision OCR + Virtual QR ---")
|
||||
yandex_text = yandex_engine.recognize(content)
|
||||
|
||||
if yandex_text and len(yandex_text) > 10:
|
||||
logger.info(f"OCR success. Raw text length: {len(yandex_text)}")
|
||||
|
||||
# Попытка собрать виртуальный QR из текста
|
||||
virtual_qr = extract_fiscal_data(yandex_text)
|
||||
if virtual_qr:
|
||||
logger.info(f"Virtual QR constructed: {virtual_qr}")
|
||||
api_items = fetch_data_from_api(virtual_qr)
|
||||
if api_items:
|
||||
logger.info(f"Success: Retrieved {len(api_items)} items via Virtual QR API.")
|
||||
return RecognitionResult(
|
||||
source="virtual_qr_api",
|
||||
items=api_items,
|
||||
raw_text=yandex_text
|
||||
)
|
||||
|
||||
# Вызываем LLM для парсинга текста
|
||||
logger.info("Calling LLM Manager to parse text...")
|
||||
yandex_result = llm_parser.parse_receipt(yandex_text)
|
||||
|
||||
return RecognitionResult(
|
||||
source="yandex_vision_llm",
|
||||
items=yandex_result["items"],
|
||||
raw_text=yandex_text,
|
||||
doc_number=yandex_result["doc_number"],
|
||||
doc_date=yandex_result["doc_date"]
|
||||
)
|
||||
else:
|
||||
logger.warning("Yandex Vision returned empty text or failed. No fallback available.")
|
||||
return RecognitionResult(
|
||||
source="none",
|
||||
items=[],
|
||||
raw_text=""
|
||||
)
|
||||
else:
|
||||
logger.info("Yandex Vision credentials not set. No OCR available.")
|
||||
return RecognitionResult(
|
||||
source="none",
|
||||
items=[],
|
||||
raw_text=""
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing request: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
Reference in New Issue
Block a user