"""接口协议 - Pydantic v2""" from typing import List, Optional from pydantic import BaseModel, Field # ===== 单行 OCR 结果 ===== class OCRLine(BaseModel): """OCR 识别到的一行文字""" text: str = Field(..., description="识别文本") confidence: float = Field(..., ge=0, le=1, description="置信度 0~1") box: List[List[float]] = Field(default_factory=list, description="四点坐标 [[x1,y1],...]") # ===== 发票字段 ===== class InvoiceFields(BaseModel): """结构化发票字段""" # 基本信息 invoice_type: Optional[str] = Field(None, description="发票类型,如 增值税电子普通发票") invoice_no: Optional[str] = Field(None, description="发票号码") invoice_code: Optional[str] = Field(None, description="发票代码") invoice_date: Optional[str] = Field(None, description="开票日期 YYYY-MM-DD") # 金额 amount: Optional[float] = Field(None, description="价税合计(小写)") amount_cn: Optional[str] = Field(None, description="价税合计(大写中文)") amount_pretax: Optional[float] = Field(None, description="不含税金额") tax_amount: Optional[float] = Field(None, description="税额") # 主体 seller_name: Optional[str] = Field(None, description="销售方名称") seller_tax_no: Optional[str] = Field(None, description="销售方纳税人识别号") buyer_name: Optional[str] = Field(None, description="购买方名称") buyer_tax_no: Optional[str] = Field(None, description="购买方纳税人识别号") # 校验 amount_match: Optional[bool] = Field(None, description="大写金额与小数金额是否一致") # ===== 响应 ===== class InvoiceResult(BaseModel): """发票识别总响应""" success: bool = Field(..., description="是否识别成功") is_invoice: bool = Field(True, description="是否被判定为发票(false = 非发票图片)") raw_text: str = Field("", description="全部 OCR 文本拼接") lines: List[OCRLine] = Field(default_factory=list, description="分行识别结果") fields: InvoiceFields = Field(default_factory=InvoiceFields, description="抽取的结构化字段") page_count: int = Field(1, description="PDF 页数 / 图片=1") engine: str = Field("paddleocr", description="使用的 OCR 引擎") elapsed_ms: int = Field(0, description="识别耗时(毫秒)") error: Optional[str] = Field(None, description="失败原因") error_code: Optional[str] = Field(None, description="错误码: not_invoice / timeout / unsupported / ocr_failed") # QR 识别相关 from_qr: bool = Field(False, description="是否从二维码取到了 3 个核心字段 (开票时间/发票号/金额)") qr_raw: Optional[str] = Field(None, description="二维码原始文本 (用于排查)") qr_error: Optional[str] = Field(None, description="二维码识别失败原因 (no_qr / bad_format)") class HealthResponse(BaseModel): status: str = "ok" version: str engine_ready: bool class PathRecognizeRequest(BaseModel): """按文件路径识别的请求体""" file_path: str = Field(..., description="服务器本地绝对路径", examples=["E:/invoice/abc.pdf"])