From 31f83e380fd3c49a76438071cb561f6bb5f146b3 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Tue, 21 Jul 2026 22:28:03 -0400 Subject: [PATCH] feat: add OCR port family protocols --- src/polygateway/ports.py | 37 +++++++++++++++++++++++++++++++++++++ tests/unit/test_ports.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/polygateway/ports.py b/src/polygateway/ports.py index 2c894bd..f0b0638 100644 --- a/src/polygateway/ports.py +++ b/src/polygateway/ports.py @@ -14,6 +14,10 @@ from .types import ( ChatRequest, EmbeddingTransportResult, LLMResponse, + OcrLayoutResult, + OcrLayoutTransportResult, + OcrTextResult, + OcrTextTransportResult, SourceConfig, SourceStats, TransportResult, @@ -53,6 +57,39 @@ class EmbeddingTransport(Protocol): ) -> EmbeddingTransportResult: ... +@runtime_checkable +class OcrTextPort(Protocol): + """OCR 文本转录端口(D9;对应 MonkeyOCR POST /ocr/text)。""" + + async def recognize_text(self, image: bytes) -> OcrTextResult: ... + + +@runtime_checkable +class OcrLayoutPort(Protocol): + """OCR 版面解析端口(D9;对应 MonkeyOCR POST /parse → GET ZIP)。""" + + async def parse_layout(self, image: bytes) -> OcrLayoutResult: ... + + +@runtime_checkable +class OcrTransport(Protocol): + """一次原始 OCR 调用的协议细节(M3 设计 §3.2);不含任何治理。 + + check_health 是探测不是调用: 2xx → True,异常 → False, + CancelledError 穿透;由消费方(启动门)决定成败语义。 + """ + + async def recognize_text( + self, *, image: bytes, source: SourceConfig, call_id: str + ) -> OcrTextTransportResult: ... + + async def parse_layout( + self, *, image: bytes, source: SourceConfig, call_id: str + ) -> OcrLayoutTransportResult: ... + + async def check_health(self, *, source: SourceConfig) -> bool: ... + + @runtime_checkable class Permit(Protocol): """限流入场许可;settle/release 均幂等,finally 中必然执行。""" diff --git a/tests/unit/test_ports.py b/tests/unit/test_ports.py index bb275e2..341d6d7 100644 --- a/tests/unit/test_ports.py +++ b/tests/unit/test_ports.py @@ -199,3 +199,32 @@ class TestGateUpdate: GateUpdate( applied=True, state=GateState.CLOSED, epoch=0, failure_count=-1, retry_after_s=0.0 ) + + +class TestOcrPorts: + """M3 三个 OCR Protocol(设计 §3.2): runtime_checkable 结构判定。""" + + def test_ocr_ports_runtime_checkable(self): + from polygateway.ports import OcrLayoutPort, OcrTextPort, OcrTransport + + class GoodClient: + async def recognize_text(self, image): ... + async def parse_layout(self, image): ... + + class GoodTransport: + async def recognize_text(self, *, image, source, call_id): ... + async def parse_layout(self, *, image, source, call_id): ... + async def check_health(self, *, source): ... + + assert isinstance(GoodClient(), OcrTextPort) + assert isinstance(GoodClient(), OcrLayoutPort) + assert isinstance(GoodTransport(), OcrTransport) + + def test_missing_method_rejected(self): + from polygateway.ports import OcrTransport + + class NoHealth: + async def recognize_text(self, *, image, source, call_id): ... + async def parse_layout(self, *, image, source, call_id): ... + + assert not isinstance(NoHealth(), OcrTransport)