如果 Microsoft-Decision-1 只是返回一个标签,比如通过、复核、拒绝,把它直接写进业务表通常不够用。落库前建议先确认三件事:异常输出有没有明确的回退动作、决策输入有没有留下可回放的摘要、人工复核和后续修改有没有审计痕迹。缺其中任何一项,出问题时你能看到的只有结论,看不到结论是怎么来的。比较稳妥的做法是先把决策记录落到独立的表,业务表只存决策 ID,等回放验证通过再决定是否合并字段。
直接落库的边界不是“字段够不够”,而是“能不能回放”。输入摘要、输出标签、回退动作、复核人和修改痕迹齐全时,落业务库可行;只有标签时,建议先落独立决策记录表并保留原始响应,验证通过后再合并。判断标准很直接:给一条记录,不看运行日志能否还原出当时为什么给出这个标签。做不到就先别把标签当业务事实写进主表。
在决策记录里标出直接落库会丢掉的字段
只存一个标签字段,丢掉的是决策的上下文。下面这些字段通常在接口响应或应用日志里出现过,但没有进业务表:输入摘要或特征快照、模型或规则版本、原始响应体、异常标记、回退动作与触发原因、复核人、复核时间、请求追踪 ID、人工覆盖痕迹。丢掉它们之后,遇到业务纠纷、重复决策结果不一致、或者审计抽查,就只能靠翻日志,而日志通常有保留周期。
对应的业务问题也很具体:同一个输入两次结果不同,无法判断是版本变更还是数据变了;回退动作只在日志里,轮转后无法证明当时执行过;人工把结论改成通过,却查不到是谁改的、为什么改。
下面是一段字段对照示例,左侧是常见的“只存标签”记录,右侧是补全后的记录,字段名可按你的表结构替换。
// 只存标签:信息不足,无法回放
{
"biz_id": "A-10293",
"decision_label": "approve"
}
// 补全后:可以还原决策路径
{
"decision_id": "d-7f31",
"biz_id": "A-10293",
"request_id": "req-88c2",
"engine_version": "decision-1@<你的版本号>",
"input_digest": "sha256:...",
"input_snapshot": {"channel":"app","amount":1200,"vip":false},
"output_label": "approve",
"output_raw": "{...原始响应...}",
"fallback_action": null,
"fallback_trigger": null,
"reviewer": null,
"reviewed_at": null,
"decided_at": "<写入时间戳>"
}
给每个输出标签补回退动作和触发条件
异常输出不能靠业务侧临时判断,建议在决策记录里给每个标签绑定一个默认回退动作,并把触发条件写成枚举,而不是自由文本。这样落库时不需要业务代码再去猜。
| 输出标签 | 默认回退动作 | 触发条件 |
|---|---|---|
| approve | 写入业务结果,标记为自动决策 | 标签在枚举内,且无异常标记 |
| review | 进入人工队列,业务结果保持待定 | 标签为复核,或关键输入字段缺失 |
| reject | 写入拒绝记录,不写通过结果 | 标签为拒绝且响应完整 |
| error / timeout | 保持原状态并转人工,禁止降级为通过 | 接口报错、超时,或重试后仍无有效响应 |
| 空响应 / 未知标签 | 隔离记录,转人工确认 | 响应为空,或标签不在已知枚举内 |
触发条件建议落到 fallback_trigger 字段,取值用固定枚举,例如 empty_response、unknown_label、timeout、missing_input。回退动作是否真的执行过,用 fallback_executed_at 记录时间戳,而不是只写在应用日志里。需要结合你的接口行为确认的一点是:超时到底返回空对象还是抛异常,这决定触发条件的分支怎么判。
设计审计字段保存输入摘要与复核人
审计字段的目标是:落库之后,不依赖运行日志也能还原决策过程。输入摘要建议存“可复算的最小集合”,而不是整个请求体——存哈希加关键字段快照通常够用,同时避免把敏感字段和过长文本带进业务库。
| 字段 | 用途 | 备注 |
|---|---|---|
| decision_id | 决策记录主键 | 业务表只引用这个 ID |
| input_digest | 输入摘要哈希 | 用于比对同输入是否被重复决策 |
| input_snapshot | 关键输入快照 | 只放可复算字段,敏感字段脱敏 |
| output_label | 最终输出标签,含人工覆盖后的结果 | 与 output_raw 区分 |
| reviewer | 复核人标识 | 自动决策时为空 |
| reviewed_at | 复核时间 | 与 decided_at 分开 |
| modified_by / modify_reason | 修改痕迹 | 覆盖原标签时必须填原因 |
| revision | 记录版本号 | 同一 decision_id 多次修改时递增 |
写入建议:原始输出先写 output_raw,再由流程写 output_label;人工覆盖时新增一条 revision 记录而不是原地更新,这样修改痕迹天然保留。
用测试样本验证落库后能否还原决策路径
字段设计完之后,建议用少量构造样本跑一遍回放,而不是只看表结构。样本至少覆盖:正常通过、人工复核、接口超时、未知标签四种。
- 从决策记录表取一条记录,只读这条记录,不看应用日志。
- 用 input_snapshot 加 engine_version,在测试环境重放同一输入。
- 比对重放得到的标签与库里的 output_label;不一致时检查是否被人工覆盖,看 revision 记录。
- 检查 fallback_action 和 fallback_trigger 是否能解释当时的处理路径。
- 检查复核人覆盖时是否有 modify_reason,没有则视为审计缺口。
缺失字段检查:如果重放时发现 engine_version 缺失,无法确认当时用的哪套规则;如果 output_raw 缺失,无法判断是模型输出异常还是落库写入异常;如果 fallback_trigger 缺失,回退动作只能靠猜。调整建议是先补最容易缺的三项——版本号、原始响应、触发原因枚举,其余字段可以随表结构迭代再加。
把落库字段与业务查询需求对照
审计字段如果查不出来,等于没存。落库前建议把实际会发生的查询场景列一遍,反过来确认字段和索引。
| 查询场景 | 所需字段 | 索引或筛选条件 |
|---|---|---|
| 按业务单号查决策历史 | biz_id、decision_id、output_label、decided_at | (biz_id, decided_at) |
| 按时间统计各标签数量 | decided_at、output_label | (decided_at, output_label) |
| 查人工复核队列 | output_label、reviewer、reviewed_at | (output_label, reviewed_at) |
| 查被回退过的记录 | fallback_action、fallback_trigger、decided_at | (decided_at) 加条件过滤 |
一个可以直接改表名使用的查询示例:
-- 查某时间段内被回退过的决策
SELECT decision_id, biz_id, output_label,
fallback_action, fallback_trigger, decided_at
FROM decision_record
WHERE fallback_action IS NOT NULL
AND decided_at >= :start_at
AND decided_at < :end_at
ORDER BY decided_at DESC;
如果业务侧经常按标签加时间范围筛选,decided_at 与 output_label 的联合索引通常比单列更合适;具体索引顺序需要结合你实际的写入频率和查询条件确认,写入压力大的表不建议一次加太多索引。