Files
tick-stock-panel/backend/tests/test_watchlist_ocr.py
T
ChenJunheng 0d6b341945 fix(watchlist): 采纳 #94 审查 — 防大图 OOM、OCR 不阻塞事件循环、收紧图片类型
为截图预处理增加像素上限与长边降采样;import-image 将 OCR 放入线程池;
去掉任意 image/* 放行,仅允许白名单 MIME/扩展名。
2026-07-15 23:28:30 +08:00

97 lines
2.9 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.
"""自选截图 OCR:代码抽取与 instruments 匹配(不依赖本机 tesseract)。"""
from __future__ import annotations
from io import BytesIO
from pathlib import Path
import polars as pl
import pytest
from PIL import Image
from app.services.watchlist_ocr.pipeline import (
extract_codes,
import_watchlist_image,
resolve_candidates,
)
from app.services.watchlist_ocr.provider import OcrProvider, preprocess_for_ocr
class _FakeOcr(OcrProvider):
name = "fake"
def __init__(self, text: str) -> None:
self._text = text
def available(self) -> bool:
return True
def extract_text(self, image_bytes: bytes) -> str:
return self._text
def _png_bytes(width: int, height: int) -> bytes:
buf = BytesIO()
Image.new("RGB", (width, height), color=(20, 20, 20)).save(buf, format="PNG")
return buf.getvalue()
def test_extract_codes_order_and_dedupe():
text = "563230 融\n515880\n价格 1.340\n563230 重复\nXAUUSD\n601636"
assert extract_codes(text) == ["563230", "515880", "601636"]
def test_extract_codes_joins_ocr_split():
text = "科创半导体\n5881 70 [融]\n创业板\n159382"
assert extract_codes(text) == ["588170", "159382"]
def test_resolve_candidates_matched_and_unmatched():
code_to_symbol = {"600036": "600036.SH", "515880": "515880.SH"}
symbol_to_name = {"600036.SH": "招商银行", "515880.SH": "通信ETF国泰"}
rows = resolve_candidates(
["600036", "999999", "515880"],
code_to_symbol,
symbol_to_name,
existing_symbols={"600036.SH"},
)
assert rows[0].matched and rows[0].already_in_watchlist
assert rows[0].name == "招商银行"
assert not rows[1].matched and rows[1].symbol is None
assert rows[2].matched and not rows[2].already_in_watchlist
def test_import_watchlist_image_with_fake_ocr(tmp_path: Path):
inst = tmp_path / "instruments"
inst.mkdir()
pl.DataFrame(
{
"code": ["600036", "515880"],
"symbol": ["600036.SH", "515880.SH"],
"name": ["招商银行", "通信ETF国泰"],
}
).write_parquet(inst / "instruments.parquet")
fake_text = "招商银行\n600036\n通信ETF\n515880\n伦敦金 XAUUSD"
result = import_watchlist_image(
b"fake-bytes",
tmp_path,
existing_symbols=set(),
provider=_FakeOcr(fake_text),
)
assert result["provider"] == "fake"
assert result["codes"] == ["600036", "515880"]
assert result["matched_count"] == 2
assert result["unmatched_count"] == 0
def test_preprocess_rejects_excessive_pixels():
# 4000×8000 = 32M 像素 > 12M 上限
with pytest.raises(ValueError, match="分辨率过高"):
preprocess_for_ocr(_png_bytes(4000, 8000))
def test_preprocess_downsamples_large_edge():
# 2500×1000 = 2.5M 像素未超限,但长边 > 2000,应降采样
out = preprocess_for_ocr(_png_bytes(2500, 1000))
assert max(out.size) <= 2000