Files
orc-order-v2/app/services/batch_service.py
T

162 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""批量处理服务:扫描 data/input/ 下图片,串行跑完整 OCR → 采购单流程。
- 不合并(每张图片单独出一个 result)
- 默认串行(避免百度 OCR 限流)
- 返回 dict 汇总:total / success / failed / results[]
"""
from __future__ import annotations
import logging
import os
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional
from ..config.settings import ConfigManager
from ..core.utils.log_utils import get_logger
from .ocr_service import OCRService
from .order_service import OrderService
logger = get_logger(__name__)
IMAGE_EXTS = {'.png', '.jpg', '.jpeg', '.bmp', '.webp', '.tif', '.tiff'}
class BatchService:
"""批量处理服务。"""
def __init__(self, config: Optional[ConfigManager] = None):
self.config = config or ConfigManager()
self.ocr_service = OCRService(self.config)
self.order_service = OrderService(self.config)
self._input_folder = self.config.get_path(
'Paths', 'input_folder', fallback='data/input', create=True
)
@property
def input_folder(self) -> str:
return self._input_folder
def list_input_images(self) -> List[str]:
"""列出 data/input/ 下所有待处理图片(按 mtime 升序,先入先出)。"""
if not os.path.isdir(self._input_folder):
return []
files = []
for name in os.listdir(self._input_folder):
p = os.path.join(self._input_folder, name)
if not os.path.isfile(p):
continue
ext = os.path.splitext(name)[1].lower()
if ext in IMAGE_EXTS:
files.append(p)
files.sort(key=lambda p: os.path.getmtime(p))
return files
def process_all_inputs(
self,
progress_cb: Optional[Callable[[int, int, dict], None]] = None,
) -> Dict[str, Any]:
"""批量处理 data/input/ 下全部图片。
Args:
progress_cb: 可选回调 fn(done_count, total_count, latest_result_dict)
Returns:
{
'total': int,
'success': int,
'failed': int,
'results': [
{
'image': str,
'hash': str,
'status': 'success' | 'failed',
'supplier': str,
'bill_date': str,
'total_amount': float,
'result_file': str | None,
'error': str | None,
},
...
]
}
"""
images = self.list_input_images()
total = len(images)
results: List[Dict[str, Any]] = []
if total == 0:
logger.info("data/input/ 下无待处理图片")
return {'total': 0, 'success': 0, 'failed': 0, 'results': []}
logger.info(f"批量处理开始: 共 {total} 张图片")
success = 0
failed = 0
for idx, image_path in enumerate(images, start=1):
entry: Dict[str, Any] = {
'image': image_path,
'hash': '',
'status': 'failed',
'supplier': '',
'bill_date': '',
'total_amount': 0.0,
'result_file': None,
'error': None,
}
try:
file_hash = Path(image_path).stem
entry['hash'] = file_hash
# 1) OCR(写 data/output/{hash}.xlsx + .meta.json
excel_path = self.ocr_service.process_image(image_path)
if not excel_path:
entry['error'] = 'OCR 失败'
failed += 1
results.append(entry)
if progress_cb:
progress_cb(idx, total, entry)
continue
# 2) process_excel(识别元信息 + 重命名 + 落库)
result_file = self.order_service.process_excel(excel_path)
if not result_file:
entry['error'] = '处理失败'
failed += 1
results.append(entry)
if progress_cb:
progress_cb(idx, total, entry)
continue
# 3) 回查元信息(process_excel 内部已落库)
meta_row = self.order_service.metadata_db.get(file_hash) or {}
entry['result_file'] = result_file
entry['supplier'] = meta_row.get('supplier', '') or ''
entry['bill_date'] = meta_row.get('bill_date', '') or ''
entry['total_amount'] = float(meta_row.get('total_amount') or 0.0)
entry['status'] = 'success'
success += 1
logger.info(
f"[{idx}/{total}] 处理完成: {Path(image_path).name} -> "
f"{Path(result_file).name} | 供应商={entry['supplier']!r} "
f"日期={entry['bill_date']!r} 金额={entry['total_amount']:.2f}"
)
except Exception as e:
logger.error(f"[{idx}/{total}] 处理异常: {image_path}: {e}", exc_info=True)
entry['error'] = str(e)
failed += 1
results.append(entry)
if progress_cb:
progress_cb(idx, total, entry)
summary = {
'total': total,
'success': success,
'failed': failed,
'results': results,
}
logger.info(
f"批量处理完成: 总 {total} 张, 成功 {success}, 失败 {failed}"
)
return summary