feat: 百度通用文字识别提取供应商/日期(config api_key 持久化)
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"""批量处理服务:扫描 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
|
||||
Reference in New Issue
Block a user