乐于分享
好东西不私藏

实操:文档录入验收,三项业务指标与校验规则

实操:文档录入验收,三项业务指标与校验规则

摘要

arXiv:2510.15727 引用了直通处理率、人工复核率、错误自动通过率这三项业务指标,并在相关工作段着重讨论,用于反映发票抽取系统的运营风险。下面给出可运行的校验规则与指标计算代码,在 Python 3.13.2 上实测执行。

供应商演示文档录入系统时给出的通常是字段准确率:发票号 99%、总额 98%、行项目 95%。三个数字看上去都很理想,却回答不了验收真正要问的问题:上线之后人工工作量减少了多少,以及有多少单据被系统当作正确自动放行、实际却出错。内部计算成立的单据不等于正确的单据,这一类错误不会出现在任何一张校验报表上。

三项指标的定义与来源

直通处理率是无需任何人工干预就完成入账的比例。人工复核率是被系统主动挑选出来交给人工查看的比例。错误自动通过率是系统判定可以自动放行、实际处理错误的比例。

前两项能够从系统日志直接统计。第三项无法统计,按照定义,系统判定它们是正确的。第三项只能依靠人工标注的对照集测量,这是整套验收里唯一需要额外投入人力的部分,也是唯一能够反映风险的部分。

三项之和不等于一。剩余部分是被系统直接拒绝、退回来源的单据。

校验分成两层

第一层是结构校验,检查必填字段是否存在、类型是否正确。第二层是业务规则校验,检查"看起来正确但实际错误"的情况。

发票场景常用的规则有六条:

  • 小计加税额约等于总额,保留 0.01 到 0.05 的容差处理进位差
  • 行项目金额之和约等于小计
  • 总额大于零
  • 发票日期不晚于到期日
  • 行项目币种一致并且与单据币种一致
  • 原文核对校验,即抽取出来的总额确实出现在原文当中,而不是模型计算得出的

原文核对校验容易被跳过,它拦截一类很具体的失败:单份文件里同时包含邮件封面页、汇款通知与真正的发票页,模型把封面页上的参考金额当成了发票总额。

可运行的校验与放行判定

from decimal import Decimalfrom datetime import dateTOL = Decimal("0.05")def check(doc: dict) -> list[str]:    """返回失败的校验项名称;空列表表示全部通过。"""    failed = []    for f in ("invoice_no", "total", "currency"):        if not doc.get(f):            failed.append(f"missing:{f}")    sub, tax, total = doc.get("subtotal"), doc.get("tax"), doc.get("total")    if None not in (sub, tax, total) and abs(sub + tax - total) > TOL:        failed.append("total_mismatch")    lines = doc.get("lines") or []    if lines and sub is not None:        if abs(sum(l["amount"] for l in lines) - sub) > TOL:            failed.append("lines_vs_subtotal")    if total is not None and total <= 0:        failed.append("nonpositive_total")    d, due = doc.get("invoice_date"), doc.get("due_date")    if d and due and d > due:        failed.append("date_order")    cur = {l.get("currency") for l in lines if l.get("currency")}    if cur and (len(cur) > 1 or (doc.get("currency") and doc["currency"] not in cur)):        failed.append("currency_inconsistent")    if doc.get("total") is not None and not doc.get("_grounded_total", True):        failed.append("not_grounded:total")    return failedHARD = {"missing:invoice_no", "missing:total", "not_grounded:total"}def gate(doc):    failed = check(doc)    if any(f in HARD or f.startswith("missing:") for f in failed):        return "reject", failed    if failed:        return "review", failed    return "auto", failed

金额一律使用 Decimal,不使用浮点数。浮点数在这里会导致一类难以排查的容差失败。

硬失败与软失败要分开处理。缺少发票号、缺少总额、原文核对校验不通过属于硬失败,直接拒绝,因为这些单据在人工复核时同样需要重新阅读原件。总额不匹配、日期顺序颠倒属于软失败,进入复核队列,复核人员查看一次即可确定。

指标计算与对照集

def metrics(cases):    """cases: [(抽取结果, 人工确认的正确记录)]"""    n = len(cases)    auto = rev = wrong_auto = 0    for doc, truth in cases:        action, _ = gate(doc)        if action == "auto":            auto += 1            if truth is None or any(doc.get(k) != v for k, v in truth.items()):                wrong_auto += 1        elif action == "review":            rev += 1    return {        "直通处理率": round(auto / n, 3),        "人工复核率": round(rev / n, 3),        "错误自动通过率": round(wrong_auto / n, 3),    }

在一个包含五张单据的最小示例上执行,实际输出如下:

INV-1 -> ('auto',   [])INV-2 -> ('review', ['total_mismatch'])None  -> ('reject', ['missing:invoice_no'])INV-4 -> ('reject', ['not_grounded:total'])INV-5 -> ('auto',   []){'直通处理率': 0.4, '人工复核率': 0.2, '错误自动通过率': 0.2}

INV-5 属于这套验收要识别的那一类。它的小计 90、税额 23、总额 113,六条业务规则全部通过:加减法成立,币种一致,日期正常。但真实的小计是 100、税额是 13。校验规则放行了它,因为校验规则只能检查内部一致性,无法检查数值本身是否正确。这一张构成了 20% 的错误自动通过率,而它在任何一张系统报表上都显示为处理成功。

对照集的构建方式

规模上,大约一百到三百张(经验量级)就足够产生有意义的比例,前提是抽样方式正确。

抽样必须分层,不能随机抽取。分层维度取自论文的主张:数字版 PDF 与扫描件、有无倾斜与噪点、有无印章与手写、语言、币种,以及是否来自未见过的供应商模板。最后一项对分销与制造企业尤其关键,整体准确率 95% 与“老供应商 99%、新供应商 71%”(示例数字)是两种不同的情况,而每月都有新客户进入。

标注方式是取已经人工录入完成的历史单据,把系统的抽取结果与人工最终录入的数值配成对照。这批数据本身已经存在,不需要新建标注流程。

对照集要固定下来作为金丝雀测试集,每次更换模型、更换提示词、供应商修改模板之后重新运行。更换模型带来的字段准确率提升,可能同时抬高错误自动通过率:模型更加激进地放行单据,校验规则却没有跟着调整。

写进合同的验收条件

按这三项指标提出验收条件,比按字段准确率提出要清楚得多。一组可以直接使用的写法如下。

在分层对照集上,直通处理率不低于某个数值;错误自动通过率不高于某个数值,并且该数值按单据金额分档设定,金额越大的单据允许的错误自动通过率越低;人工复核率不设下限,但复核队列中的单据平均处理时间需要单独测量。

最后一项容易遗漏。如果复核界面不提供原始文件、候选数值和失败的校验项,复核人员需要重新翻阅原始单据,那么复核队列里每一张单据花费的时间会比原来直接录入更长,直通率再高,总工时也降不下来。


代码在 Python 3.13.2 环境下实测运行,样例输出为实际执行结果。示例中的五张单据为构造数据,用于演示校验规则与指标计算的行为,不代表任何真实系统的准确率。三项业务指标的定义与分层统计主张由 arXiv:2510.15727 引用,并在相关工作段着重讨论;该论文自身的评测框架由三项技术指标构成(字段级精确率 field-level precision、一致性校验失败数 consistency check failures、精确匹配准确率 exact match accuracy),业务指标出自其相关工作段引用的文献 [7][8],并非该论文原创。论文提交于 2025 年 10 月,其自身实验使用 Docling 与 LlamaCloud Services 完成抽取。业务规则清单与硬失败、软失败的划分方式,综合自若干文档处理厂商的公开技术文章,各厂商宣称的准确率数字未经独立核实,未予采用。