差旅费报销单生成技能
一、技能说明
本技能用于从用户提供的行程截图中,自动提取信息并生成差旅费报销单(Word .docx)和报销明细表(Excel .xlsx)。
用户只需提供:
1. 行程截图(按案件分文件夹存放)——可以是独立图片文件,也可以是Word文档内嵌的截图
2. 预支凭证截图(如有)
3. 口头补充信息(如有)
Agent自动完成:
- 扫描文件夹内所有资料(图片、Word文档、Excel等),不遗漏任何凭证来源
- 如文件夹内含Word文档(.docx),自动提取内嵌截图并读取
- 逐张读取截图,提取日期/时间/起点/终点/金额/车次
- 同一案件多批次行程自动合并汇总(如1月出差+7月出差属同一案件,合并为一份报销单)
- 按时间排序并编号
- 生成Word报销单(明细表+预支凭证+原始凭证附件+页脚页码)
- 生成Excel明细表(含费用结算区)
- 文件名自动包含核心数据
二、铁律规则(必须严格遵守)
规则1:信息来源(最重要)
行程单的所有信息只能来源于:
1. 截图(第一来源)- 直接从截图读取
2. 用户对话(第二来源)- 用户明确告知的信息
- 截图没有 + 对话没说 → 写"—"表示无
- 绝对不允许自行推测或编造任何信息
- 截图上有的信息优先归为"截图来源",即使对话中也提到了
- 对话与截图信息不一致时,以截图为准,并提醒用户确认
规则2:逐张读取截图
收到截图后第一反应:
1. 逐张仔细读取上面的所有信息(日期、时间、起点、终点、金额、车次)
2. 每条信息标记来源(screenshot/dialog/inferred)
3. 先读完所有截图,再开始整理数据
4. 截图无时间 → 不要编,写"—"
5. 同图多日期陷阱(极易犯错,必看):一张订单列表截图(如12306"待开票")常含多个"乘车日期"表头。每条订单必须对照它自身紧邻的日期表头填日期,绝对禁止用截图首日期套整图所有条目。录入后必须用 validate_data.py 的"按截图分组复核"逐图验证。
6. 唯一要丢弃的时间:手机状态栏系统时间。完整手机截图最顶端的状态栏(有电池图标、信号格、运营商名称、WiFi图标),通常只显示时分(如"15:30")不显示日期,是截图时刻手机系统时间,与行程无关→丢弃。注意:不是每张截图都有状态栏(全屏/裁剪/APP隐藏时没有)。
除状态栏外,所有时间都保留——下单时间、购票时间、支付时间、乘车时间等,都是确定行程日期/时分的依据,绝不丢弃。
拿不准某个时间是不是状态栏时间时:不要擅自决定。两种处理方式:①向用户确认要不要去掉;②在文档末尾用红字标注该时间,问用户"此时间是否需要去掉"(参见规则4的"待确认问题清单"机制)。
- 不同平台时间表述不同,全部保留,优先用行程时间。不同平台对时间的标签不同,但不要因为标签是"下单/购票/支付"就丢弃——如果截图只有这个时间,它就是确定行程日期/时分的唯一依据。常见情况:
- 12306高铁票:有"乘车日期"+"发车时间"→优先用;也有"下单时间"→备注保留
- 滴滴/网约车:有"行程开始/出发时间"→优先用;只有"下单时间"→用下单时间确定日期和时分
- 飞机票:有"起飞时间/出发日期"→优先用;只有"购票时间"→用购票时间确定日期
- 支付宝/微信账单(顺风车/个人付款):只有"支付时间"→用支付时间(付款即乘车),收款方名称填入type或note
- 原则:所有时间信息都保留不丢弃;优先取行程发生时间(乘车/出发/起飞);没有行程时间时用下单/购票/支付时间代替;唯一丢弃的是状态栏系统时间(见规则6)
规则3:排序规则
- 添加新记录后立即按(date, time)排序并重新编号
- 有时间的在前按时间排,无时间的排当天末尾
- 同日多记录按地点首尾相连(行程连贯性)
规则4:异常发现与确认(必须主动做)
发现以下情况必须标记并向用户确认,绝不擅自处理:
1. 同一时间同一车次多条记录 → 可能是改签
2. 同一时间同一人在两个地方 → 逻辑不可能
3. 金额异常(改签费但无对应票)→ 需确认改签逻辑
4. 行程不连贯 → 需确认是否有遗漏
5. 座席矛盾(同车次一等座+二等座)→ 改签特征
确认方式:
- 备注栏用红字写明疑问
- 文档末尾附"待确认问题清单"
- 用户确认后删掉确认文字,生成最终版
规则5:图片处理
- 不准拉伸图片:必须保持原图的宽高比
- 直接从图片文件头读取实际尺寸(不依赖外部缓存)
- 多订单截图如需裁剪:
- 裁剪前用图像分析定位订单边界
- 裁剪后逐张验证:内容完整 + 无残留其他订单信息
规则6:总额计算
- 总额必须由程序自动计算(sum of items)
- 不依赖手动填写的total字段
- 生成文档后验证:sum(items) === case.total
规则7:文档结构
- docx页脚:第 X 页 共 Y 页
- 费用结算四项:预支/实际/补交/退还(不出现负数)
- 文件名包含核心数据:预支X_实际Y_补交Z 或 退还Z
- 附件顺序:一、明细表 → 二、预支凭证 → 三、原始凭证
- 无预支时"二"写明"无预支凭证"
规则8:文件夹全量读取(核心原则,极易遗漏)
用户给了文件夹路径,就必须读取文件夹内的所有文件,不能只读独立图片。无论什么格式,都要打开看里面有没有截图或行程数据。
- 扫描全部文件,不挑格式:文件夹内可能有图片(
.jpg/.png/.jpeg/.bmp/.gif/.webp)、文档(.docx/.doc/.wps/.pdf/.odt)、表格(.xlsx/.xls/.csv)、演示文稿(.pptx/.ppt)、压缩包(.zip/.rar/.7z)等。所有文件都要处理,不能只挑图片。 - 从文档中寻找截图:各种格式的文档里都可能内嵌了行程截图:
- Word文档(.docx/.doc):内嵌图片在word/media/下,用extract_docx_images.py提取
- PDF文件(.pdf):可能含行程单截图,用PDF解析工具提取图片
- Excel文件(.xlsx):可能含已整理的行程数据表,也可能插入了图片
- WPS文档(.wps/.et/.dps):先转为对应微软格式再提取
- 压缩包(.zip/.rar):先解压,再扫描解压出的文件
- 其他格式:尝试用对应工具打开,寻找截图或行程数据 - 同时提取文档文字内容:文档中可能已有行程汇总表(日期/路线/金额),这些文字是可靠数据来源,应与截图交叉验证。
- 同一案件多批次合并:文件夹内不同日期的行程(如1月出差+7月出差)如果属于同一案件,必须合并为一份报销单,不能因为日期不同就判定"无关"而丢弃。
- 判断标准:在同一案件文件夹内 = 同一案件 = 合并报销
- 例外:用户明确说明某文件不属于本次报销范围 - 已有报销文档的处理:文件夹内可能已有之前整理好的报销文档(如"XX案差旅费报销,合计XXXX元.docx")。这些文档代表已整理但尚未报销的行程,应提取其内嵌图片和数据,与新截图合并汇总。
规则9:模型不支持图片时禁止编造(致命错误防范)
- 读取图片时如收到"当前模型不支持图片"或"内容已过滤"等提示:
- 立即停止,不要继续编造数据
- 第一时间告知用户:"当前模型无法读取图片,请切换到支持图片的多模态模型,或提供OCR方案"
- 绝对禁止在无法读取图片的情况下自行编造车次、时间、金额等信息 - 这不是可选项:编造数据违反规则1(绝对不允许自行推测或编造任何信息),是最严重的错误。
- 替代方案(按优先级):
- ① 切换到支持图片的多模态模型(推荐)
- ② 使用OCR技能识别图片文字(如腾讯云OCR)
- ③ 请用户口述截图内容 - Word文档中的文字内容不受此限制:即使模型不支持读图,docx中的文字(行程表、金额等)可以通过文本提取读取,是可靠数据来源。
规则10:高铁一等座备注规则
- 当行程
type字段含"一等座"(高铁/动车一等座)时,无论note是否为空,必须在备注栏注明购买原因: - 标准表述(书面正式):
二等座车票已售完,故购买一等座。 - 适用于购票时二等座车票已售完、仅能购买一等座的情形(区别于"该车次本身不售二等座"),统一用此表述,不自行改写措辞。
- 若
note已有其他说明,将标准表述追加其后,以全角分号";"连接。 - 二等座、商务座、飞机、汽车、轮船等不适用此规则,备注按实际情况填写。
- 实现方式:
generate_docs.js与generate_excel.py在生成阶段自动检测type含"一等座"并补入标准备注,无需在expense_data.json中手动填写(保持 JSON 为原始数据)。validate_data.py打印亦同步显示该自动备注。
三、数据结构
[
{
"case_short": "01_案件简称",
"case_name": "案件全称",
"items": [
{
"seq": 1,
"date": "2026-05-05",
"time": "07:15",
"type": "高铁-二等座 G265",
"from": "岳阳东",
"to": "宁波",
"amount": 649.00,
"img": "docx_01.png",
"note": "备注信息",
"date_source": "screenshot"
}
],
"prepayment": {
"amount": 5000.00,
"date": "2026-01-01",
"note": "当事人预支差旅费",
"img": "prepayment.jpg"
},
"total": 0,
"item_count": 0
}
]
date_source字段
screenshot:信息直接从截图读取(正常黑色显示)dialog:用户对话告知(红色字体+浅橙底色)inferred:推测(红色字体+浅橙底色+"(推测)")- 不填:默认为截图来源
四、工作流程
步骤0:扫描文件夹全部文件(必做,不能跳过)
- 运行扫描脚本(自动列出所有文件并分类):
bash python scan_folder.py "文件夹路径"
脚本会自动:列出所有文件 → 按类型分类 → 提示哪些需要提取截图 → 自动对.docx调用extract_docx_images.py - 如不使用脚本,手动检查:列出文件夹内所有文件(包括子文件夹),无论什么格式:
- 图片文件(.jpg/.jpeg/.png/.bmp/.gif/.webp)→ 独立截图,直接读取
- Word文档(.docx/.doc/.wps)→ 可能含内嵌截图,执行步骤0.5提取
- PDF文件(.pdf)→ 可能含行程单截图,用PDF工具提取图片
- Excel文件(.xlsx/.xls)→ 可能含已整理数据和插入图片
- 演示文稿(.pptx/.ppt)→ 可能含截图
- 压缩包(.zip/.rar/.7z)→ 先解压,再扫描
- 其他文件 → 尝试打开,寻找截图或行程数据 - 判断是否有已整理但未报销的行程:如文件夹内已有报销文档(如"XX案差旅费报销,合计XXXX元.docx"),说明之前的行程已整理但未报销,需与新截图合并。
- 确认案件范围:同一文件夹 = 同一案件 = 合并报销。不同日期的行程只要在同一文件夹内,都属于同一案件。
步骤0.5:从文档中提取内嵌截图(如有非图片格式的文档文件)
- Word文档(.docx)最常见,运行提取脚本:
bash python extract_docx_images.py "案件文档.docx" --output images/案件简称/docx_extracted/ - 其他格式文档也需提取内嵌图片:
- PDF:用pdfimages或 PythonPyMuPDF提取图片
- Excel:用openpyxl读取xl/media/下的图片
- WPS:先转为对应微软格式再提取
- 或手动解压(docx/xlsx/pptx本质都是ZIP),图片在对应 media 目录下 - 同时提取文档文字内容(行程表、金额等),作为数据交叉验证来源
- 将提取的图片重命名为规范名称(如
docx_01_日期路线_车次.jpg),放入images/案件简称/目录 - 解析图片与文字的对应关系:通过文档XML中的图片引用顺序,确定每张图片对应的行程
步骤1:收取截图
- 按案件分文件夹存放截图到 images/ 目录
- 收到截图后第一时间逐张读取所有信息
- 每条信息标记来源
步骤2:录入数据
- 将每条行程录入 expense_data.json
- 每条记录包含:seq, date, time, type, from, to, amount, img, note
- 添加新记录后立即按(date, time)排序并重新编号
- 录入完成后运行校验:
python validate_data.py,特别关注"按截图分组复核"输出,逐图确认每条日期与截图日期表头一致(同图多日期时极易填错)
步骤3:异常检查
检查以下异常,发现后向用户确认:
- 同一时间同一车次多条记录
- 同一时间同一人在两个地方
- 金额异常
- 行程不连贯
- 座席矛盾
步骤4:生成文档
# 安装依赖(首次使用)
npm install docx
pip install openpyxl Pillow
# 步骤0:扫描文件夹内所有文件(自动分类+提示哪些需要提取截图)
python scan_folder.py "文件夹路径"
# 步骤0.5:如文件夹内有Word文档,提取内嵌截图(scan_folder.py会自动调用,也可手动运行)
python extract_docx_images.py "案件文档.docx" --output images/案件简称/docx_extracted/
# 生成前先校验数据(推荐,可发现日期填错等问题)
python validate_data.py
# 生成Word文档
node generate_docs.js
# 生成Excel明细
python generate_excel.py
步骤5:验证
- 检查每个案件的total是否等于items求和(程序会自动校正)
- 检查图片是否拉伸(程序直接从文件头读取尺寸)
- 检查页脚是否有页码
- 检查文件名是否包含核心数据
五、费用结算逻辑
- 有预支:
- 实际 > 预支 → 需补交(差额)
- 实际 < 预支 → 待退还(差额)
- 实际 = 预支 → 两清
- 无预支:
- 需补交 = 实际金额
- 待退还 = 0
六、文件命名规则
- 有预支+补交:
案件名报销单_预支X_实际Y_补交Z.docx - 有预支+退还:
案件名报销单_预支X_实际Y_退还Z.docx - 无预支:
案件名报销单_未预支_实际Y_补交Y.docx
七、历史错误反思(避免重犯)
错误0:发现异常不主动确认
同一时间同一车次出现两条记录(一等座+二等座),没有发现逻辑矛盾。改进:发现异常必须向用户确认。
错误1:编造时间数据
截图没读就编造时间。改进:收到截图第一反应逐张读取。
错误2:同日多订单时间互换
多订单截图时间对应错误。改进:逐个订单确认时间。
错误3:添加记录后未排序
序号与时间顺序不一致。改进:每次添加后立即排序+重新编号。
错误4:对话日期标成截图来源
截图上没有日期却标为screenshot。改进:逐条逐张判断来源。
错误5:对话与截图矛盾未发现
用户说9月30日,截图是10月30日。改进:以截图为准并提醒用户。
错误6:图片裁剪后未验证残留
裁剪后图片仍包含其他订单信息。改进:裁剪后逐张验证。
错误7:图片裁剪过度
裁剪掉了下单时间等重要信息。改进:裁剪前定位完整边界。
错误8:图片拉伸
不同案件同名图片尺寸混淆。改进:直接从文件头读取尺寸。
错误9:总额计算错误
手动算错总额。改进:程序自动求和+全量验证。
错误10:同图多日期误套首日期
一张12306截图含两个乘车日期(顶部02-03 + 底部02-01),转录时把同图所有条目都套用了首日期02-03,导致02-01的订单日期填错。改进:每条订单对照自身紧邻的"乘车日期"表头填日期,录入后用 validate_data.py 的"按截图分组复核"逐图验证。
错误11:忽略文件夹内文档,未合并多批次行程
真实案例:用户给了文件夹路径,里面有3张7月截图 + 1个Word文档(含1-2月6条行程截图)。Agent只整理了3张独立截图,把Word文档判定为"无关文件"丢弃。实际上Word文档内的行程是已整理但未报销的,应与7月新行程合并为一份报销单(9条行程,¥5071.03)。
根因:把"日期不同"等同于"案件不同",且只读图片不读文档,缺乏"同一文件夹=同一案件=合并报销"的意识。
改进:
- 收到文件夹路径后,必须扫描全部文件,无论什么格式(规则8)
- 文档内的截图必须提取(步骤0.5),不只是.docx,所有格式都要处理
- 同一文件夹内不同日期的行程必须合并汇总
- 新增规则8"文件夹全量读取"
错误12:模型不支持图片时编造数据
真实案例:模型不支持图片读取(系统返回"内容已过滤"),但Agent没有告知用户,而是自行编造了车次号、发车时间、金额等具体数据,生成了看似完整实则全部虚构的报销单。这直接违反了规则1"绝对不允许自行推测或编造任何信息"。
根因:收到"不支持图片"提示后未停止,而是选择编造数据来"完成任务"。
改进:
- 收到"不支持图片"提示后必须立即停止,告知用户切换模型
- 绝对禁止在无法读取图片时编造任何数据
- 新增规则9"模型不支持图片时禁止编造"
- Word文档中的文字内容不受此限制,可通过文本提取读取
八、技术要点
docx-js 9.x API
- 页脚必须用
footers: { default: docFooter },不是footer: docFooter - Footer中用
PageNumber.CURRENT和PageNumber.TOTAL_PAGES显示页码 - 图片用
ImageRun+transformation: { width, height },尺寸按原始比例计算
图片尺寸读取
直接从文件头读取,不依赖外部缓存:
- PNG:IHDR偏移16-23
- JPEG:扫描SOF标记(0xFFC0-0xFFCF)
- BMP:偏移18-22
- GIF:偏移6-8
总额自动校正
generate_docs.js 和 generate_excel.py 启动时:
const actualTotal = Math.round(items.reduce((s, it) => s + it.amount, 0) * 100) / 100;
if (caseData.total !== actualTotal) {
caseData.total = actualTotal; // 自动校正
}