feat: OCR 面单 enrichment flow (api/ocr/ + flows/sds/label_ocr.py + db OCR 方法)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
"""OCR 面单识别服务客户端(无鉴权内部服务)。
|
||||
|
||||
Inference API: http://192.168.124.92:9801
|
||||
"""
|
||||
|
||||
from api.ocr.client import OcrClient
|
||||
from api.ocr.models import OcrResult, SUPPORTED_COUNTRIES, country_to_ocr_code
|
||||
|
||||
__all__ = ["OcrClient", "OcrResult", "SUPPORTED_COUNTRIES", "country_to_ocr_code"]
|
||||
@@ -0,0 +1,43 @@
|
||||
"""OCR 面单识别 Inference API 客户端(httpx,无鉴权)。
|
||||
|
||||
端点: POST /recognize_url JSON {url, country}
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
|
||||
from api.ocr.models import OcrResult
|
||||
|
||||
DEFAULT_BASE_URL = "http://192.168.124.92:9801"
|
||||
DEFAULT_TIMEOUT = 60.0
|
||||
|
||||
|
||||
class OcrClient:
|
||||
"""OCR 推理服务客户端。"""
|
||||
|
||||
def __init__(self, base_url: str = DEFAULT_BASE_URL, timeout: float = DEFAULT_TIMEOUT) -> None:
|
||||
self._http = httpx.Client(base_url=base_url, timeout=timeout)
|
||||
|
||||
def recognize_url(self, url: str, country: str = "US") -> OcrResult:
|
||||
"""通过 PDF URL 识别面单,返回 OcrResult。"""
|
||||
resp = self._http.post("/recognize_url", json={"url": url, "country": country})
|
||||
resp.raise_for_status()
|
||||
return OcrResult.model_validate(resp.json())
|
||||
|
||||
def health(self) -> bool:
|
||||
"""健康检查。"""
|
||||
try:
|
||||
resp = self._http.get("/health")
|
||||
return resp.status_code == 200
|
||||
except Exception: # noqa: BLE001
|
||||
return False
|
||||
|
||||
def close(self) -> None:
|
||||
self._http.close()
|
||||
|
||||
def __enter__(self) -> "OcrClient":
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc: object) -> None:
|
||||
self.close()
|
||||
@@ -0,0 +1,56 @@
|
||||
"""OCR 面单识别响应模型 + 国家代码映射。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class OcrResult(BaseModel):
|
||||
"""单张面单 OCR 识别结果。"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
file: str | None = None
|
||||
layout: str | None = None
|
||||
courier: str | None = None
|
||||
courier_name: str | None = None
|
||||
country: str | None = None
|
||||
error: str | None = None
|
||||
tracking: str | None = None
|
||||
s_name: str | None = None
|
||||
s_street: str | None = None
|
||||
s_city: str | None = None
|
||||
s_state: str | None = None
|
||||
s_zip: str | None = None
|
||||
r_name: str | None = None
|
||||
r_street: str | None = None
|
||||
r_city: str | None = None
|
||||
r_state: str | None = None
|
||||
r_zip: str | None = None
|
||||
marker: str | None = None
|
||||
|
||||
@property
|
||||
def ok(self) -> bool:
|
||||
return not self.error
|
||||
|
||||
|
||||
# OCR 服务已有分类器模型的国家(版面见 api.md §4)
|
||||
SUPPORTED_COUNTRIES = {"US", "UK", "DE", "MX", "PL"}
|
||||
|
||||
# ISO 国家代码 / 中文国名 → OCR 国家代码
|
||||
_COUNTRY_MAP: dict[str, str] = {
|
||||
# 代码
|
||||
"GB": "UK", "UK": "UK", "US": "US", "DE": "DE", "MX": "MX", "PL": "PL",
|
||||
# 中文名(SDS countryName 可能用)
|
||||
"英国": "UK", "美国": "US", "德国": "DE", "墨西哥": "MX", "波兰": "PL",
|
||||
}
|
||||
|
||||
|
||||
def country_to_ocr_code(country: Any) -> str | None:
|
||||
"""将 ISO 代码或中文国名转为 OCR 服务支持的国家代码,不支持则返回 None。"""
|
||||
if not country or not isinstance(country, str):
|
||||
return None
|
||||
code = _COUNTRY_MAP.get(country.strip().upper()) or _COUNTRY_MAP.get(country.strip())
|
||||
return code if code in SUPPORTED_COUNTRIES else None
|
||||
Reference in New Issue
Block a user