Files
reimburse/README.md
T
2026-09-17 15:43:40 +08:00

14 KiB
Raw Blame History

贴票台 · macOS 原生版

SwiftUI 原生报账材料工作台。无需浏览器、Java、Spring Boot、HTTP 服务或用户单独安装 Python。RapidOCR 模型、解释器及 Office 导出依赖随应用内的处理引擎打包,扫描和导出仅通过本地子进程的标准输入/输出交换数据,不监听网络端口。

开发环境

  • Xcode 26.3macOS 15.7 或更高版本。
  • 当前构建为 Apple Siliconarm64);Intel Mac 需要另行构建对应架构的处理引擎。
  • 本机 Xcode/Users/yuxiaoting/Downloads/Xcode.app。无需修改系统默认开发工具目录。
  • 原工程 ../fapiao 不会被修改,也不是运行依赖。

在 Xcode 中运行

打开 reimburse.xcodeproj,选择 reimburse scheme 和 My Mac,运行即可。构建阶段先核对引擎源码、打包脚本、模板、依赖声明与已打包二进制的指纹;缺失或不一致时自动重新打包,并运行打包引擎的报销单导出回归测试。验证成功才将 native-engine/dist/receipt-engine-helper.app 放进应用的 Contents/Helpers,并为辅助进程添加沙盒继承权限。没有本地打包依赖时构建会明确失败,不会继续使用旧引擎。

当前开发目录已经准备了本地依赖和引擎。重新下载源码、切换架构或修改 Python 代码后,需先重建引擎:

python3.12 -m venv .build-tools
.build-tools/bin/python -m pip install -r native-engine/requirements.txt
bash native-engine/build-engine.sh

依赖安装只发生在开发打包时,最终用户不需要上述工具。dist、依赖环境及构建产物不提交到版本控制;发布流程需先生成引擎,再构建应用。

DEVELOPER_DIR=/Users/yuxiaoting/Downloads/Xcode.app/Contents/Developer \
xcodebuild -project reimburse.xcodeproj -scheme reimburse \
  -configuration Debug -destination 'platform=macOS' \
  -derivedDataPath .derived-data build

使用流程

  1. 选择包含 发票付款截图实物照片 子目录的材料根目录,或从访达将一个材料根文件夹拖入“材料扫描”顶部文件夹卡片。拖入时卡片高亮;不接受单个文件、应用程序或多个文件夹,处理中不接受重复导入。

  2. 材料复制到应用自己的本地工作区,原始文件不会修改。再次导入会先确认是否替换当前工作区;取消确认保留当前材料和核对结果。

  3. 本地 OCR 提取信息,按旧项目规则自动匹配并分类。扫描显示真实处理进度。

  4. 在“人工配对”两侧多选材料,选择分类后确认;识别失败的材料仍可人工配对。

  5. “已核对”中可改分类、预览材料、撤销及导出。首列勾选一组或多组材料,或使用表格上方“全选当前分组”,再点击“导出所选 N 组 PPT”或“所选报销单”;切换分组保留勾选,取消当前分组全选不影响其他分组,未勾选时不能导出这两类文件。“全部”页可全选所有已核对组。行程 Excel 仍使用全部已核对材料。自动核对结果不代表已人工复核。

  6. 工作区自动持久化到应用沙盒内的 Application Support/ReceiptDesk;清空只删除工作区副本,不删除原始材料。

自定义个人报销单模板

在工具栏点击“报销模板”,或点击材料扫描页、个人报销单弹窗中的“选择模板 / 查看、更换”。也可以直接从访达将一个 .xlsx 文件拖到模板卡片或模板窗口上方的虚线区域;拖入时高亮,处理中不接受重复导入。不接受文件夹、多个文件或网页链接,失败时原模板保持不变。

模板窗口左侧显示 Excel 原表,右侧显示识别到的工作表、明细范围和收款位置。核对后点击“确认位置,使用此模板”。不再默认展示整屏地址输入框;仅在识别不正确时打开“调整填写位置”。未确认的新模板不会覆盖正在使用的模板,可随时放弃更换。

模板只保存本机副本,重启后仍有效,不修改导入的原始文件。仅影响个人报销单,不影响 PPT、行程表及勾选范围;仍按所选费用类型合并金额、单据数,每类型一行。保留模板样式、合并区域、打印设置、其他工作表和未映射内容;明细超出确认范围时复制报销工作表分页。合计和其他公式在 Excel / WPS 打开后重新计算。

导入旧报销单时,务必将全部历史明细行包含在填充范围内,并指定收款字段及额外需要清空的单元格;未映射的位置会保留模板原文。映射的明细旧值会先清空;收款字段没有本机资料时也会清空,账号以文本填入。公司、项目等固定文字可预先在 Excel 中修改后再导入。

“清空重置”可选择“仅清空本次报销数据(保留模板)”或“报销数据和导入模板一起清空”。两者均不删除原始材料、原始 Excel 和钥匙串中的收款资料。模板独立保存于当前 macOS 用户的应用沙盒,不随项目管理登录账号切换。模板损坏或丢失时会阻止导出并提示重新导入,不会静默改用内置模板。

支持最多 20 MB 的未加密、无宏、无活动外部工作簿链接的 .xlsx;旧 .xls 需先另存为 .xlsx。不同格式需要确认填充位置,受保护的报销工作表不支持填充,包含 Excel 表对象的工作表不支持自动复制分页。内置模板仍保持原先导出方式。

导出前预览

在“已核对”勾选材料 → “所选报销单” → 编辑用途、签字及收款资料 → “预览报销单”。应用先按当前模板生成实际 Excel,再从该文件读取列宽、行高、合并区域、文字和边框样式进行原生表格预览。默认适合宽度且不自动放大,可切换整页、100%、手动缩放及工作表,不单独挤压列宽。可返回修改,确认无误后点击“确认并保存”选择位置;保存的字节与预览文件一致,不重新生成另一份报销单。取消保存仍留在预览页面。

预览优先使用模板字体,未安装的字体使用本机同类字体替代。图片、图表、条件格式及部分特殊数字格式会提示在 Excel / WPS 中核对,不保证这些高级内容逐像素一致;模板导入窗口仍使用系统 Quick Look。

预览会刷新受支持公式的缓存(求和、同表引用、基本四则运算、条件判断、今日日期及内置人民币大写公式),保留公式本身。复杂自定义公式不猜测结果:移除旧缓存,明确列出尚未计算的单元格,由 Excel / WPS 打开后重算。预览属于屏幕表格展示,不是分页打印校样;打印设置保留在 Excel 中。

修改所选数据、用途、收款资料或模板后需要重新预览。保存前核对输入及文件指纹,防止保存与预览不一致的内容;返回或关闭预览时清理临时文件。临时报销单仅存于应用本地沙盒,包含完整收款信息,不上传。

已核对列表的“匹配”列会显示简要核对项,点击百分比查看每项实际得分、未得分原因及核对建议。证据评分采用真正的 100 分制:单张发票与付款截图的权重为金额 40 分、商户 20 分、日期 10 分、单号 15 分、批次金额唯一性 15 分;满分之和为 100,实际得分之和就是百分比,各项未得分之和就是差额。例如仅金额一致且批次唯一时为 55/100 分,即 55%。日期相差不超过 3 天得 10 分,4–7 天得 5 分;商户达到 35% 相似度后按相似度 × 20 分四舍五入。其他组合规则的满分原本就是 100 分,仍保留;多笔付款固定 95 分会明确说明保留分。百分比不是金额匹配比例,也不是成功概率。人工配对不显示自动评分。

旧工作区启动时自动升级为新版评分,无需重扫,不改变配对、分类及导出内容。内部候选筛选、歧义排除沿用原规则及原 score,新版百分制证据评分单独存于 explanation.rawScoreversion: 2),界面不再将内部筛选分数当作百分比。历史依据不足时不显示未经验证的百分比。

收款信息

在本地贴票工具栏点击“收款信息”,填写收款人、开户行(建议包含支行)、账号,以及可选的制单人。保存后重启仍可使用,也可在个人报销单弹窗内修改或删除。账号按文本保存,保留前导零和长账号;界面默认隐藏完整账号。

资料独立保存于当前 macOS 用户的本机钥匙串,不写入工作区 JSON,不上传、不云同步;导入新材料和清空工作区不会删除资料。它不随项目管理的登录账号切换,共用同一 macOS 用户时需自行核对收款人。

个人报销单每一页自动填写已保存资料;制单人未填写时使用收款人。没有保存资料时,相应单元格留空,不沿用模板的示例姓名、开户行及账号。PPT 和行程 Excel 不携带这些资料。导出的报销单包含完整收款账号,请妥善保管。删除本机资料不会修改已导出的文件。

功能对应

原项目 原生实现
App.vue、三个页面 ContentView、ScanPage、PairPage、MatchedPage
WorkspaceState / FileItem / MatchPair Models.swift
前端请求、状态操作 WorkspaceStore.swift
OcrService / worker.py native-engine/engine.py + 打包 RapidOCR
OcrExtractor / WorkspaceService native-engine/domain.py
PptExportService native-engine/exports.py / export_ppt
TravelExcelExportService native-engine/exports.py / export_travel
PersonalExpenseExportService native-engine/exports.py / export_expense

保留一对一评分、多发票合计、多付款合计、同程多人归组、歧义判断、七类关键词分类、人工多对多、撤销、预览、金额统计和三类 Office 文件导出。

个人报销单只使用打开弹窗时勾选的材料,按费用类型合并明细。例如勾选 6 组交通,只生成一条“交通”,金额和发票数量累计;未勾选材料不计入。弹窗显示合并后的类型、组数、单据数和金额,可按类型填写用途。金额沿用原报销单口径(每张发票 OCR 金额的绝对值最大值),不改为付款金额;混合票据类型会一并列明。按首次出现的类型顺序输出,内置模板每页最多 13 个类型,自定义模板按确认的明细范围分页。

保存个人报销单前还会核对引擎返回的分类合并版本和明细行数;旧引擎或行数与预览不一致时停止保存,不覆盖用户选定的输出文件。打包引擎通过 RECEIPT_ENGINE_BINARY 运行 test_exports.pyengine_process 测试。最终应用的引擎带沙盒继承签名,不能直接从普通终端启动;应使用 Debug 应用的 --verify-local-engine 自检入口,从真实应用内验证两条汇总并检查生成的 grouped-expense.xlsx,而不能仅验证 Python 源码。

特意保留的业务口径

  • 文件名不参与匹配;PDF 只识别、预览和导出首页。
  • 一对一分数达到 90 时可通过歧义检查;其他候选需要至少领先 8 分。
  • 一张发票对应多笔付款的唯一合计组合不另设商户/日期门槛。
  • 页面发票汇总、OCR 首金额和个人报销单最大金额的不同计算口径沿用旧代码,未擅自合并。
  • 订单截图与同金额支付凭证去重沿用旧规则,因此仍需人工检查不同订单同金额的情况。
  • “按类型分类”PPT 沿用先日期、后类别排序,并非重新设计分类封面或类别分区。
  • PPT 使用竖版 3:4(720×960 pt),纯白页面,不加封面或贴票类型标题。所有类别(包括交通、住宿)均预留实物照片位置,不自动填入实物照片。
  • 一张发票配一张付款截图:发票上方整幅,付款截图左下,右下留可编辑、可删除的实物照片提示,不另加照片页。
  • 一张发票配多张付款截图:首张发票页下方左右最多放两张付款,剩余付款按每页三列两行、最多六张续页,最后加一张手工照片页。
  • 多张发票:先按每页两张上下排列;若剩一张,放在下一页上方,下方容纳前两张付款。剩余付款每页最多六张,最后加一张手工照片页。只有发票或只有付款的组也能完整导出并预留照片页。
  • 不同核对组或不同审批申请不合页、不按比例猜测发票付款对应关系;原图等比缩放,不裁切、不重复、不漏图,PDF 仍只用首页。
  • 个人报销单沿用原始 XLSX 模板,直接修改工作表 XML,保留样式、打印设置、公式和关联资源;每页 13 条明细,最多六个签字岗位。删除全部岗位时按旧规则回退默认岗位。

必须说明的环境差异

RapidOCR 主版本、模型及关键推理依赖与旧环境对齐,但 Windows x64 与 macOS arm64 的底层运行库不同。PDF 首页由本地 PDFium 栅格化,替代原来的 PDFBox。因此不能保证所有文件的 OCR 文本逐字或浮点结果完全一致,仍需用实际报账材料做回归验收。应用不会悄悄换用苹果 Vision 或云端 OCR。

验证

.build-tools/bin/python -m unittest discover -s native-engine/tests -v
.build-tools/bin/python native-engine/tests/smoke_engine.py \
  --binary "$PWD/native-engine/dist/receipt-engine-helper.app/Contents/MacOS/receipt-engine-helper" \
  --output "$PWD/.validation/packaged"

覆盖原铁路多人凭证案例、火车与航班提取、金额歧义、分类、去重、PPT 页面安排、个人报销单分页与公式保留、行程表等。端到端测试使用 ../fapiao/demo-materials 作为开发测试数据,运行中的应用本身不依赖该目录。

Debug 构建还提供应用沙盒内自检:用 --verify-local-engine 参数启动应用,生成独立测试图片,经过真正的 Swift → 本地引擎链路验证 OCR 和三种导出。结果写入沙盒 Application Support/ReceiptDesk/diagnostics/report.json,不替换用户工作区;自检后该应用进程自动退出。Release 不包含此入口。

发布说明

当前是本机开发运行构建,并非已经完成 Developer ID 签名和苹果公证的公开安装包。正式分发时需用同一 Developer ID 对辅助引擎的所有嵌套 Mach-O 库、辅助程序和主应用按由内到外顺序签名,再进行公证。请同时随发布包保留第三方依赖许可声明。