Files
PolyGateway/tests/unit/_document_fixtures.py
T
iomgaa 80bb8aba71 feat: add OcrClient.parse_document with full governance
Reuses the existing retry, breaker, limiter and telemetry loop; the transport capability is checked once before the statistics boundary. Legacy entrances keep their error text. Tests cover the document path through the real transport so parser exceptions, deadlines and probe bookkeeping are proven end to end.
2026-10-22 00:01:45 -04:00

82 lines
2.7 KiB
Python

"""OCR 文档结果包的合成 fixtures(#34)。
全部**合成**: 保留 2026-10-21 实测的结构骨架(para_blocks 块类型、table/image 的嵌套
`blocks`、span 字段),一切识别文本替换为 `SYNTH-*` 占位——第三方论文内容许可不明、
医疗数据不入库(零业务假设 + P5),真实 ZIP 不提交。
"""
import io
import json
import zipfile
_BBOX = (10.0, 20.0, 110.0, 40.0)
def _span(content="SYNTH-TEXT", type_="text"):
return {"type": type_, "content": content, "bbox": list(_BBOX), "score": 1.0}
def _text_block(content="SYNTH-TEXT", *, type_="text", bbox=_BBOX):
return {
"type": type_,
"bbox": list(bbox),
"index": 0,
"lines": [{"bbox": list(bbox), "spans": [_span(content)]}],
}
def _body(type_, spans, bbox=_BBOX):
return {"type": type_, "bbox": list(bbox), "lines": [{"bbox": list(bbox), "spans": spans}]}
def _table_block(html="<table><tr><td>SYNTH-CELL</td></tr></table>", extra_subs=()):
body = _body("table_body", [{"type": "table", "html": html, "image_path": "synth.jpg"}])
return {"type": "table", "bbox": list(_BBOX), "index": 1, "blocks": [*extra_subs, body]}
def _image_block():
body = _body("image_body", [{"type": "image", "image_path": "synth.jpg"}])
return {"type": "image", "bbox": list(_BBOX), "index": 2, "blocks": [body]}
def _page(blocks=None, *, page_idx=0, page_size=(612.0, 792.0), **extra):
page = {"page_idx": page_idx, "page_size": list(page_size), "discarded_blocks": []}
if blocks is not None:
page["para_blocks"] = blocks
page.update(extra)
return page
def _package(
pages=None,
*,
md="# synth\n",
middle=None,
middle_name="doc_middle.json",
md_name=None,
extra=(),
):
"""组装结果包。`middle` 给原始 bytes 时原样写入;`md_name` 默认由 middle 名配对。"""
if middle is None:
middle = json.dumps({"pdf_info": pages if pages is not None else [_page([])]}).encode()
if md_name is None:
md_name = middle_name[: -len("_middle.json")] + ".md"
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr(middle_name, middle)
if md is not None:
zf.writestr(md_name, md if isinstance(md, bytes) else md.encode())
for name, data in extra:
zf.writestr(name, data)
return buf.getvalue()
def _encrypted_package() -> bytes:
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
info = zipfile.ZipInfo("doc_middle.json")
info.flag_bits |= 0x1 # 加密位:读取时 zipfile 要求密码
zf.writestr(info, b'{"pdf_info": []}')
zf.writestr("doc.md", "x")
return buf.getvalue()