🤖 第六章 应用实战:采购订单智能识别与ERP自动化填报助手
📄 一句话概括

前五章,AI 的产出都是【给人看的内容】;这一章,AI 的产出第一次变成【给系统用的数据】。这两件事的容错率不在一个量级:内容错了最多被限流,数据错了就是账不平。

📝 本节和前面几节的关系

本节是第六章的起点,也是全课【案例篇】的最后一个起点。第五章的产出物是一篇文章,本章的产出物是一张采购订单——形状完全不同,所以整套流程要重新设计一次。但骨架没变:仍然是 1.7.2 那条【看清场景 → 设计流程 → 造 Agent → 造 RPA → 收束】的七步。

一、先把场景说清楚:一张采购订单是怎么进来的

采购订单(Purchase Order,PO)是企业向供应商下单的凭据。 它的上游是采购需求,下游是收货、对账、付款。本章要自动化的,是它从【一张纸/一张图】变成【ERP 里的一条记录】的那一段。

先看这张单是从哪儿来的——这段最值得看,因为它决定了后面所有技术选择:

来源 形态 对识别意味着什么
供应商传真的订单 扫描件 分辨率低、有噪点
供应商邮件发的 PDF / Excel 电子版最好认,但格式各家不同
业务员微信发来的 截图 图小、常带聊天界面元素
打电话下单后补的 手写送货单 字迹潦草,是识别最难的一类
供应商系统对接的 EDI 报文 最规整,但小供应商不会用

然后它要进哪儿去:ERP 的采购模块,在那里创建一张采购订单(供应商、物料、数量、单价、税率、金额、交期)。

中间那一段,就是本章要动的地方:一个人,对着屏幕,一个字段一个字段地抄。

📝 为什么这一段一直没被自动化

不是没人想动,而是它同时踩了两个难点: 一是【输入不规整】——上游给什么格式的都有,靠传统 OCR 只会认字、不会理解"哪一列是数量"。 二是【终点不开放】——ERP 通常不给外部程序写数据的接口(尤其老系统),走接口要厂商配合、要走很长的流程。 这两件事正好是 Agent 和 RPA 各自的长项:Agent 解决"看懂一张不规整的图",RPA 解决"不需要对方配合就能把数据填进去"。

二、这一章和前五章的根本不同:产出物从【内容】变成了【数据】

这是本章最重要的一张表。 前面五章换了好几个平台,但产出物的性质一直没变;本章第一次变了:

前面五章(内容侧) 本章(数据侧)
AI 的输入 一个主题、一句话、一段素材 一张图片 / PDF / 扫描件
AI 的产出 一篇文章、一篇笔记 一单结构化字段
终点 公开平台(小红书/公众号/头条/知乎) 公司内部系统(ERP)
谁在看 读者、平台算法 财务、审计、仓库、供应商
出错的样子 限流、减少分发、折叠、删除 账不平、对不上、审计问题
什么时候发现错 立刻(发不出去 / 被处置) 平均几天后(对账时)
错了能改吗 改完重发,代价有限 改它要改一连串下游单据

最后两行,是本章全部设计压力的来源。

三、最要命的那一条:错误是【延迟暴露】的

一句来自采购主管的原话,值得完整读一遍:

一批物料几十项,录错了不容易发现,等仓库收货对不上账才发现问题,再回头改单据,流程已经被卡住了。

这句话里有三个信息,一个比一个重要:

  1. 发现错的地方不在录单环节——是仓库收货时对不上账才暴露的;
  2. 从录错到发现,中间隔了好几天——这几天里,单据已经往下游走了;
  3. 改的时候要往回改——不只是改这一张单,还有已经发的货、已经对过的账。

对比一下前五章:发一篇文章,发不出去当场就知道;被限流了第二天看数据也知道。但录错一个数量,没人会当场告诉你——它安安静静躺在系统里,直到有一天对不上。

⚠️ 所以本章的检查必须【前置】

内容侧的错误,可以在【发布前】拦(5.4.3 的发布前预检就是干这个的)——因为发布这个动作本身就是个天然闸门。 但数据侧没有这个闸门:填进 ERP 的那一刻,它就已经生效了。 所以本章的所有校验都必须做在【点保存之前】——不是"填完再查一遍",而是"查完才允许填"。

四、手工填一张单,时间花在哪:一次真实拆解

要设计流程,先得知道时间花在哪。 把录单这件事拆成五段:

段 做什么
找单 从邮箱/群里翻出那张单,打开,看清是哪家供应商
读字 认清楚每一行的物料名、数量、单位、单价
查编码 在 ERP 里把【物料名】换成【物料编码】——这一步常被忽略,但它往往最慢
填单 把字段填进采购订单界面
核对 把填完的合计和单据上的合计对一遍

第三段特别值得注意:人看的是"内六角螺栓 M6×20",但 ERP 认的是"M1001"。中间要查一次——而且每一行都要查一次。

下面用 Demo 把它算出来。

五、动手 Demo:手工录单工时拆解器

这是本节真正属于你自己的那部分:把一批单据的耗时逐段拆开,看看时间到底花在哪。

# -*- coding: utf-8 -*-
"""6.1.1 Demo:手工录单工时拆解器(纯标准库)
回答一个问题:填一张采购订单,时间到底花在哪一段上。
"""
import unicodedata

# 每张单:编号 / 来源 / 物料行数 / 清晰度(clear/fuzzy/shot/excel/hand)
DOCS = [
    {"id": "D1", "src": "PDF 电子单",   "lines": 10, "q": "clear"},
    {"id": "D2", "src": "传真扫描件",   "lines": 25, "q": "fuzzy"},
    {"id": "D3", "src": "微信截图",     "lines": 5,  "q": "shot"},
    {"id": "D4", "src": "Excel 报价单", "lines": 3,  "q": "excel"},
    {"id": "D5", "src": "手写送货单",   "lines": 8,  "q": "hand"},
]

# 单段工时(分钟)
FIND = 0.5            # 找单 / 打开 / 对文件名
READ = {"clear": 0.4, "fuzzy": 0.9, "shot": 0.6, "excel": 0.7, "hand": 1.2}  # 每行读字
LOOKUP = 0.6          # 每行查物料编码(在 ERP 里搜)
ENTRY = 0.3           # 每行填单
CHECK = 1.0           # 核对合计

SEGS = ["找单", "读字", "查编码", "填单", "核对"]

def wlen(s):
    """按显示宽度算长度(中文算 2)"""
    return sum(2 if unicodedata.east_asian_width(c) in "WF" else 1 for c in s)

def pad(s, w):
    return s + " " * max(0, w - wlen(s))

def cost(d):
    read = READ[d["q"]] * d["lines"]
    look = LOOKUP * d["lines"]
    ent = ENTRY * d["lines"]
    return {
        "找单": FIND,
        "读字": round(read, 1),
        "查编码": round(look, 1),
        "填单": round(ent, 1),
        "核对": CHECK,
    }

print("=" * 78)
print("一、逐单工时拆解(单位:分钟)")
print("=" * 78)
print("  %s %s %5s  %7s %7s %7s %7s %7s   %7s" % (
    pad("编号", 4), pad("来源", 14), "行数", "找单", "读字", "查编码", "填单", "核对", "合计"))
tot = {s: 0.0 for s in SEGS}
grand = 0.0
rows_total = 0
for d in DOCS:
    c = cost(d)
    s = round(sum(c.values()), 1)
    for k in SEGS:
        tot[k] += c[k]
    grand += s
    rows_total += d["lines"]
    print("  %s %s %5d  %7.1f %7.1f %7.1f %7.1f %7.1f   %7.1f" % (
        pad(d["id"], 4), pad(d["src"], 14), d["lines"],
        c["找单"], c["读字"], c["查编码"], c["填单"], c["核对"], s))

print("  " + "-" * 76)
print("  %s %5d  %7.1f %7.1f %7.1f %7.1f %7.1f   %7.1f" % (
    pad("合计", 19), rows_total, tot["找单"], tot["读字"], tot["查编码"], tot["填单"], tot["核对"],
    round(grand, 1)))

print()
print("=" * 78)
print("二、时间花在哪一段(按占比排序)")
print("=" * 78)
rank = sorted(SEGS, key=lambda s: -tot[s])
for s in rank:
    pct = tot[s] * 100.0 / grand
    bar = "█" * int(round(pct / 2))
    print("  %-6s %7.1f 分钟  %5.1f%%  %s" % (s, tot[s], pct, bar))

machine = tot["读字"] + tot["查编码"]
print()
print("  读字 + 查编码 = %.1f 分钟,占 %.1f%% —— 这两段恰好都是【机器擅长的】" % (
    machine, machine * 100.0 / grand))
print("  剩下 %.1f 分钟里,找单与填单同样是确定性动作,核对里的【异常判断】才真正需要人" % (
    grand - machine))

print()
print("=" * 78)
print("三、一处错要花多少:返工成本(错误延迟暴露)")
print("=" * 78)
FIELDS_PER_LINE = 4          # 每行 4 个字段:物料/数量/单价/税率
field_total = rows_total * FIELDS_PER_LINE
RATE_MANUAL = 0.005          # 人工字段错误率 0.5%
REWORK_MIN = 25              # 一处错返工 25 分钟(改单 + 通知下游 + 更正账)
DELAY_DAYS = 5               # 平均 5 天后(仓库收货对账)才暴露
errs = field_total * RATE_MANUAL
print("  这批 %d 行 = %d 个字段;按人工错误率 0.5%% 计,预计错 %.1f 处" % (
    rows_total, field_total, errs))
print("  每处返工 %.0f 分钟 → 这批单预计花 %.1f 分钟在返工上" % (REWORK_MIN, errs * REWORK_MIN))
print()
print("  注意:这 %.1f 分钟的返工不是当场发生的,而是平均 %.0f 天后才暴露。" % (
    errs * REWORK_MIN, DELAY_DAYS))
print("  这正是采购订单录入最麻烦的地方 —— 与【发内容】不同,错了没人当场告诉你。")

实际运行输出(真跑,不是示意):

==============================================================================
一、逐单工时拆解(单位:分钟)
==============================================================================
  编号 来源              行数       找单      读字     查编码      填单      核对        合计
  D1   PDF 电子单        10      0.5     4.0     6.0     3.0     1.0      14.5
  D2   传真扫描件        25      0.5    22.5    15.0     7.5     1.0      46.5
  D3   微信截图           5      0.5     3.0     3.0     1.5     1.0       9.0
  D4   Excel 报价单       3      0.5     2.1     1.8     0.9     1.0       6.3
  D5   手写送货单         8      0.5     9.6     4.8     2.4     1.0      18.3
  ----------------------------------------------------------------------------
  合计                   51      2.5    41.2    30.6    15.3     5.0      94.6

==============================================================================
二、时间花在哪一段(按占比排序)
==============================================================================
  读字        41.2 分钟   43.6%  ██████████████████████
  查编码       30.6 分钟   32.3%  ████████████████
  填单        15.3 分钟   16.2%  ████████
  核对         5.0 分钟    5.3%  ███
  找单         2.5 分钟    2.6%  █

  读字 + 查编码 = 71.8 分钟,占 75.9% —— 这两段恰好都是【机器擅长的】
  剩下 22.8 分钟里,找单与填单同样是确定性动作,核对里的【异常判断】才真正需要人

==============================================================================
三、一处错要花多少:返工成本(错误延迟暴露)
==============================================================================
  这批 51 行 = 204 个字段;按人工错误率 0.5% 计,预计错 1.0 处
  每处返工 25 分钟 → 这批单预计花 25.5 分钟在返工上

  注意:这 25.5 分钟的返工不是当场发生的,而是平均 5 天后才暴露。
  这正是采购订单录入最麻烦的地方 —— 与【发内容】不同,错了没人当场告诉你。

读结果,四个值得注意的地方:

  1. 单张单的耗时差异极大:D4 只要 6.3 分钟,D2 要 46.5 分钟——七倍多。 差在哪?差在行数和清晰度。D2 是 25 行的传真扫描件,读字单价 0.9、查编码要查 25 次;D4 是 3 行的 Excel,一行都不含糊。 这直接决定了一件事:本章的"批量"和第四章的"批量"含义不同——第四章的批量是【篇数】,本章的批量是【行数 × 单据数】。十张小单可能不如一张大单耗时。

  2. 占比最大的两段是【读字】和【查编码】,合计 75.9%——而这两段恰好都是机器擅长的。 读字是识别要解决的(本节现状一:企业接入后识别准确率 99%+); 查编码是确定性查表——"内六角螺栓 M6×20"对应"M1001"这件事,系统里本来就写着。 而剩下那 24.1% 里,找单和填单同样是确定性动作。 真正必须留给人的,其实只有核对里那一小撮【异常判断】(数量不对、税率奇怪、这个词系统里没有)。

  3. 第三段那个 25.5 分钟很不起眼,但它才是本章的重点。 204 个字段、0.5% 错误率 → 预计错 1 处。1 处错、25 分钟返工——看起来完全可以接受。 但请注意两件事:第一,它的暴露时间是 5 天;第二,25 分钟只是【改这一张单】的成本——如果货已经收了、账已经对过了,返工要往回追好几步。 所以真正贵的不是那 25 分钟,是那 5 天里已经流出去的东西。

  4. 手写单(D5)的"读字"单价是 1.2,是清晰 PDF 的三倍——但它只有 8 行,总耗时还不到 D2 的一半。 这提醒一件事:"最难识别的单据"和"最耗时的单据"不是一回事。 难识别的是手写单,耗时的是长单。设计流程时要想清楚优化哪一个——本节结尾的现状一给了一个重要提示:AI 至今仍吃力的是手写连笔、差扫描件、旋转、没见过的版式。

六、哪几段能交出去:三档归属

沿用全课的判断方式,但本章第三问换了:

段 归属 判据
找单 全交 重复且无判断(扫描收件目录即可)
读字 全交 重复,且识别已经够准
查编码 全交 有规则(查主数据表,是确定性的)
填单 全交(填完停在保存前) 重复且无判断
核对里的异常判断 留人 需要担责
📝 前三章问的是【需要人味吗】,本章问的是【需要担责吗】

第五章的第三问是"需要人味吗"——因为那个平台的判据是"内容像不像真人写的"。 本章的第三问是"需要担责吗"——因为这一章的错误落在【账】上,而账是要有人签字的。 这一问的答案决定了:"填"可以交给机器,"确认"不行。 哪怕机器填得百分百对,那一下"确认"也必须是人的动作——不是因为不信任机器,是因为需要一个能负责的主体。

七、⚠️ 2026 现状:这一侧的三件事

教材 2026-06 出版,但这三件事是本课写作时的最新情况,会影响你的技术选型。

现状一:识别准确率已经很高,但它的边界很清楚

企业接入后的公开数据:订单信息提取准确率 99% 以上、人工录入工作量释放约 80%、财务对账效率提升 5~10 倍。

但"AI 至今仍然吃力"的四类情况,必须记住: - 手写连笔(尤其潦草的字迹); - 扫描质量差的件; - 旋转、倾斜的文档; - 没见过的版式。

⚠️ 这四条为什么必须记住

因为它们是评估指标无法告诉你的东西。 99% 的准确率是【平均准确率】——它可能意味着"一百张干净 PDF 全对",也可能意味着"干净的对、脏的全错"。 所以本章后面所有 Demo 都在做同一件事:不去相信那个平均值,而是【一张一张地把不确定的挑出来】。

现状二:财务侧的合规正在快速数字化,"单据影像"第一次有了法定地位

这对本章的直接影响有两处: 1. 单据原图必须存下来,而且要能跟系统里的那张订单【关联起来】——后面 6.4.3 的"回挂"就是干这个的; 2. 只存影像件是不够的——以电子凭证入账的还须保存原生文件与元数据。所以别随手把原始 PDF 删掉。

现状三:RPA 在财务场景有一个真实的合规门槛

数据:超过 40% 的企业在 RPA 规模化部署阶段会遭遇不同程度的审计与合规阻碍。

四大核心风险(这几条正好是本章第五节那四条纪律的来处):

风险 典型表现
账号权限与越权 机器人用高权限账号;机器人账号与自然人账号混用——违反职责分离原则
数据隐私泄露 运行日志里明文记录密码、身份证号,或屏幕截图暴露敏感数据
审计追踪缺失 无法区分某次操作是人做的还是机器人做的,缺步骤快照
规则偏离 监管规则变了,脚本没更新,继续按旧规则批量执行

对应的标准做法:给机器人独立唯一的身份、最小权限、凭证进保险箱(不进代码)、日志不记敏感数据、高风险节点留人工确认。

📝 这四条会在 6.1.2 与 6.4.3 各用一次

6.1.2 会把它算成"合规账"(第三笔价值); 6.4.3 会把它落成"四条红线"(填报环节的具体纪律)。 先记住它们,后面两节就不用再解释了。

30 秒实验

把 Demo 里 D2(传真扫描件)的 lines 从 25 改成 50,再跑一次。

会发生什么:

结论:录单的成本几乎完全由【行数】决定,而不是由【单据张数】决定。

顺着这个思路想一层:这对流程设计意味着什么?意味着"按张分任务"不是一个好办法——一张 50 行的传真件和一张 3 行的 Excel,工作量差十倍,但都是"一张单"。 更合理的做法是"按行分任务"或者"按预估工时排序"——这也正是 6.2 那节要解决的排期问题。

再想一层:这也解释了为什么"识别"这一步必须分批、必须能重跑——因为最耗时的单往往也是最容易识别失败的单(长 + 不清晰),而它们恰恰最不该阻塞整批。

常见误区

  1. 【这一章就是前面几章换个平台而已】——错,产出物的性质变了。 前五章产出的是给人看的内容,本章产出的是给系统用的数据。这个差别决定了后面所有设计:终点从"公开平台"变成"公司内部系统"、错误的代价从"限流"变成"账不平"、发现错误的时间从"立刻"变成"几天后"。

  2. 【识别准了就没问题】——错,识别只是三段里的一段。 本节 Demo 已经拆开了:读字(43.6%)+ 查编码(32.3%)+ 填单(16.2%)。识别解决第一段;第二段是查表、第三段是点界面,各自有各自的坑。后面 6.4.2 会专门讲"识别对了但业务上不对"这一类问题。

  3. 【录错了会马上知道】——错,这是本章最危险的地方。 错误是延迟暴露的:录错一个数量,不会有人当场告诉你,要等仓库收货对不上账才发现。而那时单据已经往下游走过了。所以本章的所有校验都必须前置到"点保存之前"。

  4. 【单数多才值得做自动化】——半对,决定成本的是【行数 × 单数】。 30 秒实验已经演示过了:把一张单的行数翻倍,整批工时多出近一半。所以"值不值得做"要按【月总行数】估,不是按【月单量】估——6.1.2 的收益估算器就是按这个口径算的。

  5. 【ERP 填报就是点几下鼠标,没什么风险】——错,它是本章唯一真的动【账】的一步。 它涉及钱(金额、税率)、涉及审计(可追溯性)、涉及合规(职责分离)。2026 年有超过 40% 的企业在 RPA 规模化时撞上过审计或合规问题——这不是小概率事件。

自测三问(点开对答案) 1. **这一章的产出物与前五章有什么根本不同?为什么这个不同决定了整套流程的设计?** 前五章的产出是**给人看的内容**(文章、笔记);本章的产出是**给系统用的数据**(ERP 里的一张采购订单)。 它决定了三件事:① **终点不同**——不是公开平台,是公司内部系统,所以要面对的不是平台风控,而是**内控、审计与合规**;② **出错的样子不同**——不是被限流,是**账不平**;③ **发现错误的时间不同**——不是立刻,是**平均几天后**(对账时),而那时下游已经动过了。 **第 ③ 条最关键**:内容侧有【发布】这个天然闸门,数据侧没有——**填进 ERP 的那一刻它就已经生效了**,所以校验必须全部前置到"点保存之前"。 2. **手工录单里占比最大的是哪两段?为什么说它们"恰好都是机器擅长的"?** 是**读字(43.6%)**和**查编码(32.3%)**,合计 **75.9%**。 **读字**要靠识别——而 2026 年的识别准确率已经能做到企业级 99%+; **查编码**是纯确定性查表——"内六角螺栓 M6×20"对应"M1001",这件事**系统里本来就写着**,不需要任何智能。 再加找单(2.6%)和填单(16.2%)也都是确定性动作,**真正必须留人的只剩"核对里的那一小撮异常判断"**。这就是本章为什么值得做的算术依据。 3. **为什么"错误延迟暴露"会让这一章的检查必须前置?** 因为**内容侧有一个天然闸门(发布),数据侧没有**。 发文章时,发布前预检能拦住一切——因为不点发布会,什么都不会发生。但**填进 ERP 的动作本身就是生效动作**,没有"再确认一次"的机会。 而延迟暴露又意味着:**等发现时,返工范围已经扩散**(货收了、账对了、票据开了)。所以唯一可行的做法是**把校验全部做在"点保存之前"**——不是"填完再查",而是"查完才允许填"。

相关笔记