- Update 00_README.md: refresh architecture overview - Update 02_semantic_ontology.md: expand semantic layer design - Update 04_orchestrator_and_runtime_flow.md: add runtime flow details - Update 05_development_roadmap.md: refine milestone timeline - Update 06_data_contracts_and_governance.md: add contract specifications - Update 10_evaluation_and_testset.md: add evaluation framework - Update 11_ocr_invoice_architecture.md: enhance OCR architecture - Update 14_financial_document_canonical_model.md: complete model design - Remove weekly_execution_details/: deprecated in favor of agent week plan
19 KiB
19 KiB
财务单据标准模型
1. 为什么需要标准模型
OCR、MCP、用户填写、业务数据库可能都描述同一张发票,但字段名和格式不同。
如果没有标准模型:
- 规则无法复用。
- Agent 难以解释。
- Hermes 难以批量统计。
- MCP 返回结果难以合并。
这里要区分三层:
- 标准模型:定义 Agent、规则、MCP、OCR、数据库之间统一交换的数据结构。
- 业务数据库表:定义 MVP 阶段真正落库存储、查询和统计所依赖的业务表。
- 文件存储对象:定义原始票据、预览件、OCR 中间产物、验真结果附件的存储位置与版本规则。
如果只有标准模型,没有业务表和文件资产表,User Agent 无法真正发起报销;如果只有数据库表,没有统一标准模型,语义解析、规则解释、OCR 回填和 Hermes 巡检会越来越混乱。
2. 标准对象
第一版建议定义这些对象:
Invoice
Receipt
ExpenseClaim
PaymentRequest
AccountsReceivableRecord
AccountsPayableRecord
BankTransaction
Contract
Customer
Vendor
Employee
CostCenter
DocumentAsset
RiskEvent
说明:
- 对外语义层建议统一使用
ExpenseClaim概念,不再把“报销申请”和“报销单据”拆成两个平行主概念。 - 现有代码中仍有
reimbursement_requests表,MVP 阶段不建议再继续扩张该表,而应以expense_claims作为报销主表。 reimbursement_requests可保留用于兼容旧页面或审批联动,但新能力默认挂到expense_claims。
3. Invoice 标准模型
{
"invoice_id": "",
"invoice_code": "",
"invoice_number": "",
"invoice_type": "",
"seller_name": "",
"seller_tax_no": "",
"buyer_name": "",
"buyer_tax_no": "",
"issue_date": "",
"total_amount": 0,
"tax_amount": 0,
"currency": "CNY",
"verify_status": "",
"ocr_confidence": 0,
"source_document_id": ""
}
4. ExpenseClaim 标准模型
{
"claim_id": "",
"claim_no": "",
"employee_id": "",
"employee_name": "",
"department_id": "",
"department_name": "",
"cost_center_code": "",
"project_code": "",
"expense_type": "",
"reason": "",
"location": "",
"amount": 0,
"currency": "CNY",
"status": "",
"occurred_at": "",
"submitted_at": "",
"approval_stage": "",
"items": [],
"attachments": [],
"risk_flags": []
}
说明:
reason、location、occurred_at是报销语义判断、规则解释、风险识别的最小必要字段。- 一张报销单通常包含多条费用明细,标准模型中允许聚合,数据库层必须拆到明细表。
attachments指向文件资产,不直接嵌入二进制文件。
5. AccountsReceivableRecord 标准模型
{
"ar_id": "",
"document_no": "",
"customer_id": "",
"customer_name": "",
"contract_no": "",
"invoice_no": "",
"amount_receivable": 0,
"amount_received": 0,
"amount_outstanding": 0,
"currency": "CNY",
"due_date": "",
"posting_date": "",
"status": "",
"aging_days": 0,
"risk_flags": []
}
6. AccountsPayableRecord 标准模型
{
"ap_id": "",
"document_no": "",
"vendor_id": "",
"vendor_name": "",
"invoice_no": "",
"amount_payable": 0,
"amount_paid": 0,
"amount_outstanding": 0,
"currency": "CNY",
"due_date": "",
"posting_date": "",
"status": "",
"aging_days": 0,
"risk_flags": []
}
7. BankTransaction 标准模型
{
"transaction_id": "",
"bank_account": "",
"transaction_date": "",
"amount": 0,
"currency": "CNY",
"counterparty_name": "",
"summary": "",
"matched_object_type": "",
"matched_object_id": "",
"match_status": ""
}
8. MVP 真实业务表设计
标准模型不等于数据库表,但 MVP 至少要有以下真实表,才能支撑 Day 5 用户报销对话、Day 6 风险巡检和后续审批/验真闭环。
8.1 设计原则
- 报销主数据统一落在
expense_claims,不再新建第三套“报销主表”。 - 原始票据文件二进制不进数据库,只存元数据和关联信息。
- OCR 结果、发票结构化结果、验真结果、风险事件要分表存,避免把所有字段塞进一个 JSON。
- 所有表都要能被 Agent 解释,也要能被 Hermes 批量扫表。
reimbursement_requests进入兼容态,不作为新能力主干表继续扩展。
8.2 报销主表 expense_claims
用途:
- 作为用户报销会话最终落单的主业务对象。
- 承接语义层补槽后的草稿、提交、审批、打回、归档状态。
建议字段:
id string(36) PK
claim_no string(50) UK, 报销单号
source string(30) 来源: agent/web/import/api
title string(200) 报销标题
employee_id string(64) 申请人 ID
employee_name string(100) 申请人姓名
department_id string(64) 部门 ID
department_name string(100) 部门名
company_code string(50) 公司编码
cost_center_code string(50) 成本中心
project_code string(50) 项目编码
expense_type string(50) 费用大类
reason text 事由
location string(100) 地点
amount numeric(12,2) 报销总金额
currency string(10) 币种
invoice_count int 附件票据数
attachment_count int 附件总数
occurred_start_at timestamptz 发生开始时间
occurred_end_at timestamptz 发生结束时间
submitted_at timestamptz 提交时间
status string(30) draft/submitted/approved/rejected/paid
status_changed_at timestamptz 最近状态变更时间
status_changed_by string(64) 最近状态变更人
status_change_note text 状态变更备注
approval_stage string(50) 当前审批节点
risk_level string(20) none/low/medium/high
risk_flags_json json 风险标记快照
conversation_id string(64) 对话会话 ID
created_by string(64) 创建人
updated_by string(64) 更新人
created_at timestamptz
updated_at timestamptz
说明:
- 现有模型已有一部分字段,后续只做增量扩展即可。
occurred_start_at、occurred_end_at比单一occurred_at更适合差旅、接待等跨时段报销。
8.2.1 报销状态流转建议
建议状态:
draft
submitted
approved
rejected
paid
建议流转:
语义补槽完成
-> 创建 expense_claims 草稿
-> status = draft
用户继续补充字段 / 上传附件
-> 更新 expense_claims / expense_claim_items / expense_item_documents
-> status 仍为 draft
用户明确确认提交
-> status = submitted
-> 写入 submitted_at / status_changed_at / status_changed_by
审批流结果回写
-> status = approved 或 rejected
付款完成回写
-> status = paid
边界:
- User Agent 可以创建
draft,也可以在用户确认后提交到submitted。 - User Agent 不应直接把状态改为
approved、rejected、paid。 - 所有状态变化都应写审计日志,必要时保留
status_change_note。
8.3 报销明细表 expense_claim_items
用途:
- 表达一单多明细。
- 作为 OCR 发票比对、重复报销识别、风险定位的最小粒度。
建议字段:
id string(36) PK
claim_id string(36) FK -> expense_claims.id
line_no int 明细序号
item_date date 费用发生日期
item_type string(50) 费用小类
item_reason text 明细事由
item_location string(100) 明细地点
merchant_name string(200) 商户/酒店/餐厅
customer_name string(200) 客户单位
participants_json json 参与人员
transport_type string(50) 交通方式
item_amount numeric(12,2) 明细金额
tax_amount numeric(12,2) 税额
currency string(10)
invoice_match_status string(30) unmatched/partial/matched
risk_level string(20)
risk_flags_json json
remark text
created_at timestamptz
updated_at timestamptz
说明:
- 现有
invoice_id单字段不足以覆盖多张附件挂同一明细的情况,后续应改为关联表。
8.4 票据资产主表 document_assets
用途:
- 作为所有原始附件的主索引表。
- 支持报销单、报销明细、审批、验真、风控证据等多对象挂载。
建议字段:
id string(36) PK
biz_domain string(30) expense/ap/ar/common
biz_object_type string(50) expense_claim/expense_item/approval_record
biz_object_id string(36) 业务对象 ID
document_type string(50) invoice/receipt/itinerary/contract/other
document_subtype string(50) vat_special/taxi/train/hotel/meal 等
source string(30) upload/agent/import/system
original_filename string(255)
mime_type string(100)
file_ext string(20)
page_count int
file_size_bytes bigint
sha256 string(64) 去重与追溯
storage_provider string(30) local/minio/s3/oss/cos
storage_bucket string(100) 本地模式可为空
storage_key string(500) 指向当前有效版本原件
current_version_no int
classification_status string(30) pending/success/failed
ocr_status string(30) pending/running/success/failed
virus_scan_status string(30) pending/clean/infected
retention_policy string(30) finance_default/legal_hold/manual
uploaded_by string(64)
uploaded_at timestamptz
created_at timestamptz
updated_at timestamptz
8.5 票据版本表 document_asset_versions
用途:
- 保留原始文件和后续重新上传版本。
- 允许“修正”但不允许覆盖原始证据。
建议字段:
id string(36) PK
document_id string(36) FK -> document_assets.id
version_no int 1,2,3...
is_current bool
change_reason string(100) replace/rotate/desensitize/reupload
original_filename string(255)
mime_type string(100)
file_size_bytes bigint
sha256 string(64)
storage_provider string(30)
storage_bucket string(100)
storage_key string(500)
uploaded_by string(64)
uploaded_at timestamptz
created_at timestamptz
8.6 衍生文件表 document_derivatives
用途:
- 存储缩略图、预览 PDF、逐页图片、脱敏件等衍生产物。
建议字段:
id string(36) PK
document_version_id string(36) FK -> document_asset_versions.id
derivative_type string(50) thumb/preview/page_image/desensitized
page_no int 可空
mime_type string(100)
file_size_bytes bigint
storage_provider string(30)
storage_bucket string(100)
storage_key string(500)
created_by string(64)
created_at timestamptz
8.7 OCR 结果表 document_ocr_results
用途:
- 保留每次 OCR 原始结果、模型版本、置信度和错误信息。
- 支持后续重跑 OCR 与人工纠错对比。
建议字段:
id string(36) PK
document_id string(36) FK -> document_assets.id
document_version_id string(36) FK -> document_asset_versions.id
ocr_engine string(50) paddle/aliyun/tencent/openai 等
ocr_model string(100)
run_no int 第几次 OCR
status string(30) success/failed/partial
language string(20)
raw_text text
raw_result_json json
structured_result_json json
confidence numeric(5,4)
error_message text
started_at timestamptz
finished_at timestamptz
created_at timestamptz
8.8 发票结构化表 invoice_structured_records
用途:
- 将发票核心字段标准化后独立存储,便于查重、验真、规则计算。
建议字段:
id string(36) PK
document_id string(36) FK -> document_assets.id
ocr_result_id string(36) FK -> document_ocr_results.id
invoice_code string(50)
invoice_number string(50)
invoice_type string(50)
seller_name string(200)
seller_tax_no string(50)
buyer_name string(200)
buyer_tax_no string(50)
issue_date date
total_amount numeric(12,2)
tax_amount numeric(12,2)
currency string(10)
check_code string(100)
is_red_invoice bool
is_electronic bool
ocr_confidence numeric(5,4)
normalized_status string(30) normalized/manual_corrected
duplicate_fingerprint string(100) 发票号+代码+金额+日期
created_at timestamptz
updated_at timestamptz
8.9 发票验真记录表 invoice_verification_records
用途:
- 保留每次调用税局/第三方验真服务的结果,支持追溯。
建议字段:
id string(36) PK
invoice_record_id string(36) FK -> invoice_structured_records.id
verification_channel string(50) tax_mcp/third_party/manual
request_payload_json json
response_payload_json json
verify_status string(30) verified/unverified/voided/error
voided bool
red_reversed bool
verified_amount numeric(12,2)
verified_issue_date date
error_code string(50)
error_message text
verified_by string(64)
verified_at timestamptz
created_at timestamptz
8.10 明细与票据关联表 expense_item_documents
用途:
- 解决一条明细可关联多张票据、一张票据也可能支撑多条拆分明细的场景。
建议字段:
id string(36) PK
claim_id string(36) FK -> expense_claims.id
claim_item_id string(36) FK -> expense_claim_items.id
document_id string(36) FK -> document_assets.id
relation_type string(30) evidence/invoice/boarding_pass/receipt
allocated_amount numeric(12,2) 分摊到该明细的金额
match_status string(30) unmatched/partial/matched
match_confidence numeric(5,4)
created_at timestamptz
updated_at timestamptz
8.11 风险事件表 risk_events
用途:
- 记录风险命中,而不是只在主表里塞一个
risk_flags_json。 - 作为 Agent 解释“为什么拦截”的核心依据。
建议字段:
id string(36) PK
biz_domain string(30) expense/ap/ar
biz_object_type string(50) expense_claim/expense_item/invoice
biz_object_id string(36)
risk_code string(50) duplicate_invoice/amount_mismatch 等
risk_name string(100)
risk_level string(20) low/medium/high
hit_source string(30) rule/agent/hermes/manual
evidence_json json
status string(30) open/confirmed/resolved/ignored
detected_at timestamptz
detected_by string(64)
resolved_at timestamptz
resolved_by string(64)
resolution_note text
created_at timestamptz
updated_at timestamptz
8.12 风险处置表 risk_actions
用途:
- 记录每次人工确认、驳回、忽略、要求补件等处置动作。
建议字段:
id string(36) PK
risk_event_id string(36) FK -> risk_events.id
action_type string(30) confirm/reject/ignore/request_more
action_note text
operator_id string(64)
operator_name string(100)
created_at timestamptz
8.13 文件访问审计表 document_access_logs
用途:
- 记录谁看过、下载过、导出过原始票据。
- 支撑财务审计和数据安全追溯。
建议字段:
id string(36) PK
document_id string(36) FK -> document_assets.id
document_version_id string(36) FK -> document_asset_versions.id
action string(30) preview/download/export/delete
operator_id string(64)
operator_name string(100)
operator_role string(50)
client_ip string(64)
user_agent string(255)
trace_id string(64)
created_at timestamptz
9. 表关系建议
expense_claims
└─ expense_claim_items
└─ expense_item_documents
└─ document_assets
└─ document_asset_versions
└─ document_derivatives
└─ document_ocr_results
└─ invoice_structured_records
└─ invoice_verification_records
risk_events -> 可指向 expense_claims / expense_claim_items / invoice_structured_records
risk_actions -> risk_events
document_access_logs -> document_assets / document_asset_versions
原则:
- 主业务对象和文件资产解耦。
- OCR、验真、风险都挂在文件资产或业务对象之上,不把责任塞到一个巨表。
- 文件版本和业务关系分离,避免替换附件时把历史证据冲掉。
10. 与现有表的衔接策略
当前代码中已经存在:
expense_claimsexpense_claim_itemsreimbursement_requests
建议策略:
expense_claims继续作为未来报销主表。expense_claim_items增量扩字段并替换当前单一invoice_id直连方式。reimbursement_requests暂不删除,但冻结扩表。- 如旧流程仍引用
reimbursement_requests,可在过渡期建立:request_no -> claim_no对照字段- 或由
approval_records同时支持两类来源对象
不建议做法:
- 再新建第四张“报销申请主表”。
- 把原始发票图片以 blob 方式存进
expense_claims。 - 把 OCR、验真、风控结果全塞进一个 JSON 大字段。
11. 实施顺序建议
Phase 1:
- 扩展
expense_claims - 扩展
expense_claim_items - 新增
document_assets - 新增
document_asset_versions - 新增
expense_item_documents
Phase 2:
- 新增
document_ocr_results - 新增
invoice_structured_records - 新增
invoice_verification_records - 新增
document_derivatives
Phase 3:
- 新增
risk_events - 新增
risk_actions - 新增
document_access_logs
Phase 4:
- 逐步弱化
reimbursement_requests - 将 Agent 草稿、审批、OCR、验真、风控全收敛到
expense_claims体系
12. 对 Agent 的直接收益
- 用户说“我要报销”时,Agent 能先创建
expense_claims草稿,再持续补槽。 - 用户上传票据后,系统有明确的
document_assets与expense_item_documents可挂载。 - OCR 和验真结果不是一次性临时输出,而是可追溯、可回放、可审计的长期资产。
- Agent 回答“为什么被拦截”时,可以直接引用
risk_events和票据证据,不再靠拼字符串解释。