Files
th-hotel-simple/docs/project/requirements/booking-business-agent-rules-v1.md
T
鲨鱼辣椒 694c4317a3 checkpoint: complete recoverable V2 pre-separation baseline
Complete the selective V2 checkpoint with its minimal AgentBus, object-storage, replay persistence, and validated-workbench shared dependency closure.
2026-08-20 17:09:00 +08:00

358 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Layer 5 Booking Business Agent v1.0 业务规则
| 项目 | 内容 |
| --- | --- |
| 文档状态 | 权威业务规则;V2 durable runtime 已本地实现;当前外部发布复核与运行 `HOLD` |
| 业务版本 | `booking-business-agent-v1.0` |
| 输出契约 | Agent输出`booking-business-agent-compact-decision-v1`;信息系统组装`CandidateDecisionV2` |
| 支持渠道 | QBD、普通 LianTai;不含 LianTai-FIT sender/profile |
| 决策依据 | ADR-012(修订 ADR-011) |
| 本期终点 | Layer 5 候选;不创建最终任务、不执行 PMS/Opera |
## 1. 产品定位
只要白名单发件人的入站邮件已经进入预订处理流程,每一封都必须经过 Booking Business Agent。系统不得因为
某封邮件“看起来很清楚”而用本地 New/Update/Cancel 规则绕过 Agent。
Layer 5 只负责三件事:
1. 根据当前邮件和 Layer 3/4 已整理的业务材料识别业务类型与业务目标;
2. 按物理来源和业务目标拆分业务决定,并选择 Booking Type 与必要来源引用;
3. 对无法安全识别类型或必要目标的事项输出局部 Risk,把未被任何业务承接的当前原文放入未归类原文区。
Layer 5 不负责页面展示方式、数据库订单/任务历史、重复 New、当前订单是否允许 Update/Cancel、用户如何确认,
也不负责创建最终 TaskCard。
## 2. 七层边界
| 层 | 提供或决定的内容 | 明确不做 |
| --- | --- | --- |
| Layer 3 | 当前正文业务材料、逐物理来源事实、来源动作原文/规范值、中立 structured mention、target binding、relationship、证据 | 不决定 New/Update/Cancel/Trace/Rooming/Allotment |
| Layer 4 | 来源订单上下文、canonical Room Type、结构性覆盖 FIT/GROUP 的 Rate/早餐方案、Rooming List typed resolution | 不决定业务类型;不查询数据库订单/任务历史;不按房量选择最终 Rate 方案 |
| Layer 5A Agent | 业务类型、目标拆分、Booking Type、语义事项、linked/derived action、局部 Risk、未归类原文 | 不复制固定字段、不组装完整Candidate、不读原始Excel、不做Room/Rate映射 |
| Layer 5B 信息系统 | 从canonical Layer3/4复制日期、房型、房量、价格、对应Rate/早餐和证据,组装完整Candidate | 不重新判断或改变Agent给出的业务类型、目标和Booking Type |
| Layer 6 | schema/evidence/第二道门槛、Layer 4 等值和幂等校验 | 不查询生命周期/重复 New/前序任务;不改业务类型,不替 Layer 5 补造业务事实 |
| Layer 7 | 按订单展示候选与校验结果、接收员工确认/补充 | 不重新分类、不重算候选、不执行 PMS |
Layer 5 可读取当前邮件以及完整有序的邮件线程 History 正文。邮件 History 用于理解本次指令,不等于数据库
订单状态;History 附件、数据库当前订单、任务历史和生命周期结论不进入 Agent 输入。
Layer 5 只消费 Layer 4 已经给出的 Room Type、双 Rate/早餐方案和 Rooming List 结果。它先独立判断 Booking Type,
再只能取适用于该类型的唯一方案。`GRPA1`、价格到 Rate Code、原始房型到 Room Type、名单 Excel 的文件判定
都不属于本规则。
## 3. 两道门槛
### 3.1 第一道门槛:识别“是什么任务、针对谁”
一项业务只有同时满足以下条件才通过第一道门槛:
- 当前材料中存在该业务的明确语义或受支持信号;
- 能识别该业务的必要目标。
无法安全决定业务类型、业务范围或必要目标时,输出受影响事项的 Risk。Risk 只隔离不明确事项;同封其他
明确候选继续保留。
来源行动作与当前正文都可证明业务语义,但适用范围不同:
- 每个物理来源行以该行自己的动作为准;同封邮件标题或“整体语义”不能覆盖多行;
- 某行已有动作时,当前正文不能把它改成另一动作;二者冲突且不能消解时,该行进入 Risk;
- 某行没有动作时,只有明确指向该行或该 Tour Code 的当前正文指令才能补足;
- `Dear Reservation / Update Booking` 等通用标题,或“处理所有标色行”等整体描述,不能替代每行自己的动作;
- 标色行本身是固定渠道 Parser 的选材规则,不是邮件会明确写出的业务指令。
### 3.2 第二道门槛:该类型候选需要哪些参数
业务类型与必要目标已经明确,但候选字段缺失、非法或与规范不一致时:
- 保留原业务类型;
- 原样保留已知值,缺失值保持空;
- 由 Layer 6 标记员工需要补充或确认的字段;
- 不改成 General,也不因为普通字段缺失把整项改成 Risk。
例外是“同一物理来源单元包含两组及以上住离周期”:系统无法安全判断房型应属于哪个周期,New 与 Update
都直接将该来源单元作为 Risk 交人工,不自动拆卡或分配房型。
## 4. 目标身份与来源拆分
### 4.1 目标身份
- GROUP:必须有 Tour Code。
- FIT:Tour Code 或真实客人姓名至少一个存在。
- 姓名只保存真实客人姓名,不能用 Tour Code/Group Code 补姓名。
- QBD/LianTai 固定渠道只有一套参数提取规则:Parser 不预判 FIT/GROUP,也不为 FIT 改读姓名或另一组字段;
先完成相同的 Tour Code、动作、日期、房型、房量、价格等参数提取和绑定,再由 Booking Agent 判型。
- New/Update 的 Booking Type 是第二道门槛字段,由该 Tour Code 全部有效房量计算:1–4 为 FIT,5 及以上为
GROUP;姓名、人数、渠道/公司、sender 和邮件中的 FIT/GRP 字样均不得参与或覆盖判型。无法计算时保留类型并
让员工确认 Booking Type 与 Rate Code,不得从 Rate option 反推类型。
- 真实客人姓名如由其他独立来源提供,仍按姓名字段保存;它不是固定渠道 FIT 专用提取项,也不是 Booking Type
判断依据。
- Booking Type 确定后,FIT 只能取 FIT option,GROUP 只能取 GROUP option;QBD 单一方案同时适用两者。
对应 option unresolved 时 Rate Code/早餐保持空并转人工,不允许改取另一个方案。
- Cancel 不要求填写 FIT/GROUP,也不携带 Booking Type。
### 4.2 拆分与合并
- Tour Code 是订单归属,不是自动合并键。
- 不同 Tour Code 原则上属于不同订单。
- 不同物理来源行永远不合并;即使 Tour Code、业务类型、日期和房型完全相同,也分别输出候选供员工确认。
- 同一物理来源单元、同一住离周期内的多个房型属于同一 New/Update 候选。
- 同一来源房型即使映射成相同 canonical Room Type,也保留各自来源明细,不自动聚合。
- 同一 Tour Code 可以有多个候选;每个 New/Update 候选只有一组入住/离店日期,可以有多个房型。
- 互斥动作或正文/行内动作冲突无法消解时,受影响来源进入 Risk。
## 5. NEW_BOOKING
### 5.1 第一道门槛
同时识别到当前明确的 New/New Booking/新增语义和符合身份规则的目标,才成立 `NEW_BOOKING`。
- 来源行明确写 New/新增可作为信号。
- 当前正文明确指向某行或某 Tour Code 的新增指令可作为信号,或补足该行缺失动作。
- 只有通用邮件标题、没有逐行或逐目标 New 语义时,不能把多行统一识别为 New。
- New 语义明确但必要目标无法识别时,输出 Risk:`NEW_TARGET_UNRESOLVED`。
### 5.2 候选与第二道门槛
每个通过识别的物理来源单元输出一项 New。候选必须结构性承载:
- target identity;
- `source_unit_ref`;
- Booking Type;
- 成对的入住日期和离店日期;
- 至少一项来源房型明细:来源房型、canonical Room Type、精确正整数房量与 evidence;
- Tour Code 级 Rate Code;
- Tour Code 级早餐结果:`breakfast_included=true/false`;为 true 时餐厅只能是 `LEELA/BUALUANG`,为 false 时餐厅为空。
价格只做来源展示:有值就原样保留,没有就不展示;价格不是必填项,不做数字校验。候选不包含人数/总人数。
房量为 0、负数、小数或缺失,日期只出现一半,Room Type/Rate/早餐缺失等,均保留 New 并交员工补充。
入住和离店日期必须成对出现;一半缺失时直接要求人工补充,不猜另一半。
一个 Tour Code 有且只有一个 Rate Code;Layer 5 只消费 Layer 4 结果并判断有无,不检查代码内容、不映射。
### 5.3 不属于本期自动判断的事项
系统不查询该 Tour Code 是否已有 New,也不检查当前订单/任务卡生命周期。Layer 5 只按当前邮件与邮件线程材料
识别本次指令;不存在“由 Layer 6 稍后补做重复 New 检查”的隐含链路。
## 6. UPDATE_BOOKING
### 6.1 第一道门槛
同时识别到当前明确的 Update/Amend 语义和符合身份规则的目标,才成立 `UPDATE_BOOKING`。受支持信号包括:
- 来源行 `UPDATE`、`AMEND`、`AMD BOOKING`、`AMD ALLOTMENT`、`AMEND TO`;
- Layer 3 给出的 before→after 变更事实;
- 当前正文明确指向某行或 Tour Code 的更新指令。
邮件正文的 `Update Booking` 大标题只表达整封邮件的整体主题,不覆盖每一行自己的动作。
Update 有两种同等有效形式:
1. before→after:Layer 3 同时提供旧值和新值;候选使用目标新值;
2. after-only:没有 before,但存在 `AMEND TO/AMD BOOKING/AMD ALLOTMENT` 等当前更新语义;候选只使用本次目标值。
Layer 3 若只保留最新动作,Layer 5 就只判断收到的最新动作,不追溯被覆盖动作。
Update 语义明确但必要目标无法识别时,输出 Risk:`UPDATE_TARGET_UNRESOLVED`。有目标但没有目标值时已经通过
第一道门槛,仍保留 Update,并由 Layer 6 标记 `UPDATE_TARGET_VALUE_REQUIRED`。
### 6.2 候选与第二道门槛
Update 与 New 使用相同的 target identity、source unit、Booking Type、住离日期、房型/正整数房量、Rate Code、
早餐和可选价格字段规则。不同物理来源行不合并。
- 当前房型列表是整套目标值,替换当前列表;不表达“增加/减少几间”的差量。
- before 与 after 相同仍保留 Update,并让员工确认;Layer 5 不静默丢弃。
- 一个 Tour Code 可有多个不同物理行的 Update 候选,但每个候选只允许一组住离日期。
- 同一来源单元有两组及以上住离周期时直接 Risk,不自动拆分。
本期不查询当前订单是否存在/已 Cancel,也不检查较早任务;Layer 5 不得用邮件 History 冒充这些数据库事实。
## 7. CANCEL_BOOKING
### 7.1 第一道门槛
当前来源行明确 `CANCEL/CXL`,或当前正文明确指向某行/Tour Code 的取消指令,并能识别目标时,成立
`CANCEL_BOOKING`。`CXL` 表示整笔预订取消。
- GROUP 必须有 Tour Code;FIT 为 Tour Code 或姓名至少一个。
- Cancel 语义明确但目标无法识别时输出 Risk:`CANCEL_TARGET_UNRESOLVED`。
- 行内已有其他动作而正文明确取消同一目标时属于动作冲突,不由正文直接覆盖,进入局部 Risk。
### 7.2 候选与第二道门槛
Cancel 的候选只需要 `target_identity` 与 `payload_type=CANCEL_BOOKING`。不要求 Booking Type、住离日期、
房型、房量、Rate Code、早餐、价格、取消原因或取消日期。
不同物理来源行永远不合并。同一 Tour Code 两行 CXL 仍输出两项 Cancel,供员工分别确认。
## 8. TRACE_RESERVATION_NOTES
### 8.1 第一道门槛
识别到至少一项当前、明确、具体的服务事项,并能识别目标时,成立 Trace。无需出现 Trace/备注等任务名称;
“请准备举牌”“安排接机”“加床”“排房”“房间布置”本身都可以是信号。
- 只有“添加特殊要求”等空泛名称、没有具体服务内容时,不成立 Trace。
- “名单/信息稍后提供”“可能需要”等未来或不确定表达不成立 Trace。
- 明确服务事项但完全无法识别目标时,输出 Risk:`TRACE_TARGET_UNRESOLVED`。
- 空泛或未来内容若未被其他任务承接,原文进入未归类区。
目标规则与预订类相同:GROUP 必须 Tour Code;FIT 为 Tour Code 或姓名至少一个。
### 8.2 目标关联与合并
- Trace 依附于一个 Tour Code/目标;不存在跨 Tour Code 的共享 Trace 候选。
- 当前正文的公共服务事项可关联同封已经识别的 Tour Code。
- 同封有多个 Tour Code、公共事项未单独限定其中一个时,为每个 Tour Code 各生成一项 Trace,并复制相同原文。
- 同一封、同一目标的多个具体 Trace 服务事项合并成一个 Trace。
- 跨封永远新建,不与历史 Trace 合并。
### 8.3 候选与第二道门槛
Trace 必须承载目标、至少一项具体服务事项的完整 `service_text` 及 evidence、整项唯一部门。日期、时间、人数、
航班号、举牌文字/图片等只在来源提供时写入 `service_text`,不生成自由 `parameters`。普通事项和 Extra Bed 都必须
保留 `service_text`。
部门使用已批准映射:
- 一般服务、接机、举牌、会议室、排房:`FO`;
- 涉及房间布置:`FO_HSK`;
- 同封合并事项同时含 FO 与房间布置时,整项使用 `FO_HSK`;
- 不生成单独 `HSK` 部门候选。
Extra Bed 是 Trace 服务项,不是房型预订:必须有目标 Room Type;未写数量时默认 1,明确数量必须是正整数;
Room Type 或合法数量仍缺失时保留 Trace,交员工补充。Room Type 与 Quantity 继续是 Extra Bed 的固定字段,
不并入 `parameters`,也不因删除 `parameters` 而取消 Layer 6 校验。
## 9. ROOMING_LIST
### 9.1 第一道门槛
Layer 5 只消费 Layer 4 的 typed resolution。以下三项必须同时成立:
1. 当前附件被 Layer 4 标记为 Excel;
2. Layer 4 标记它是 `Rooming List`;
3. Layer 4 给出该 Excel 对应的唯一 Tour Code。
Layer 5 不打开 Excel,不判断文件是不是名单,不读取客人姓名,不自行绑定 Tour Code。
当前 Layer 4 的确定性来源是当前附件文件名型识别:只接受完整匹配批准 `LLT...xlsx` 规则、且 OOXML 包级
安全结构检查通过的附件。识别过程不打开 shared strings、不提取普通单元格、姓名、同行关系、证件、名单行或
人数。文件名命中但技术检查失败时,Layer 3 可保留候选与 Evidence,Layer 4 必须保持
`tour_code=null / target_status=UNRESOLVED`;History 附件与普通 QBD/LianTai 更新表不参与该判定。
### 9.2 候选与异常
- 一个 Tour Code 对应一份 Rooming List;成功候选只包含该 Tour Code 和一个当前 Excel attachment ID。
- 同封不同 Tour Code 各有一份有效 Excel时,分别生成候选。
- Excel+Rooming List 标签成立但缺 Tour Code:Risk,`ROOMING_LIST_TARGET_UNRESOLVED`。
- 同一 Tour Code 同封有两份有效 Rooming List Excel:Risk,不生成成功候选。
- 一份 Excel 对应多个 Tour Code:Risk,不生成成功候选。
- 只有正文写 Rooming List、没有 Layer 4 的有效标签 Excel:不生成 Rooming List;原文未被其他任务承接时
进入未归类区。
- PDF、图片、链接本期不满足 Rooming List;未被其他任务承接时进入未归类区。
Rooming List 没有额外第二道字段。它不改变预订状态、DEF,不派生饮食/疾病/房间任务,不调用 PMS。
本地 decoder 与 Layer 6 仍会独立核对成功 target 是否精确对应唯一 resolved resolution、相同 attachment、相同
Tour Code 和材料 Evidence;Agent 不能用输出绕过上述边界。
## 10. ALLOTMENT 派生动作
### 10.1 第一道门槛
只有当前邮件正文出现明确的 Allotment 操作语义才处理 Allotment。语义可以是扣减 Allotment 或取消整个
Allotment。只有 `Allotment` 名称、行内 `AMD ALLOTMENT` 或 Layer 3 source→actual 关系都不足以单独触发。
- 名称-only 且无其他任务时,原文进入未归类区。
- 明确操作但 source Tour Code 缺失时,输出局部 Risk:`ALLOTMENT_SOURCE_TARGET_UNRESOLVED`。
- 多个 source 存在时必须能逐目标明确对应;不能明确时输出局部 Risk。
目标行仍按自己的当前动作形成 New、Update 或 Cancel;Allotment 不作为 target `business_type`。
`ALLOTMENT_SOURCE_TO_ACTUAL` 关系只用于形成 Allotment 派生动作,不得写入普通 New/Update/Cancel 决定的
linked关系;普通决定的linked关系只允许指向当前目标的 `GROUP_CODE_REPLACEMENT`。
### 10.2 定量扣减 source
正文明确扣减时,每个符合条件的物理目标行独立派生一项 source deduction,不按 source 或 Tour Code 聚合。
每项只包含:
- source Tour Code;
- 所引用的一个目标 decision/source unit;
- canonical Room Type 与需扣减的正整数房量;
- evidence。
它不包含 source old/remaining 余额、住离日期、Rate、早餐或价格,也不查询数据库库存。
派生规则:
- 直接目标值、没有 before→after 的 New/AMD 行:按该行完整当前房型列表派生扣减;
- 有 before→after 的行:只形成目标 Update,不派生 source 扣减,也不计算差额;
- CXL/Cancel 目标行:只形成目标 Cancel,不派生 source 扣减;
- 同封混合行逐行处理,只为符合条件的直接目标值行派生;
- 明确扣减但完全没有目标行:Risk,`ALLOTMENT_DEDUCTION_TARGET_REQUIRED`;
- 已有目标行但房型/房量缺失或非法:保留目标任务与 deduction 形状,由员工补充,不升级整封 Risk。
### 10.3 取消整个 source Allotment
正文明确取消整个 Allotment 时,每个唯一 source Tour Code 形成一项 whole-cancel derived action;只需要
source Tour Code,不要求目标行,也不受目标行 before→after 影响。同一 source 同封只形成一项。
whole-cancel必须使用材料中的真实source Tour Code,不得使用`T1/T10`等单次调用短目标别名;来源引用只指向
直接表达整块取消的Current正文。
系统绝不根据“扣完了”自动取消 source,也不计算余额来推导完整 split。
## 11. 未归类原文、Risk 与错误方向
### 11.1 未归类原文区(General)
General 不是任务类型,不生成 TaskCard,也不是“判断出一种业务”。Layer 5 在识别所有受支持任务后,把当前
材料中未被任何候选或 Risk 承接的原文,按原顺序放入 `unclassified_source_texts[]`。
- 未归类原文可与正常任务和局部 Risk 同时存在。
- 不按 Tour Code 拆卡、不要求确认成业务任务。
- 付款内容本期一律作为未归类原文;Payment-only 邮件没有任务,根结果汇总为 General;Payment 与 New/Update
等同封时,正常候选照常输出,付款原文同时保留,不生成 Payment/Trace/Risk。
### 11.2 Risk
Risk 只用于第一道门槛或不能安全拆分的结构性异常:业务类型、必要目标、动作冲突、来源适用范围或业务目标
结构无法安全决定。每个 Risk item 保留受影响原文/目标线索、稳定原因码和 evidence。
已经识别出类型与目标,仅第二道参数缺失时不是 Risk。
### 11.3 Ignored Direction
正常白名单入站邮件永远不走 `IGNORED_DIRECTION`。它只作为酒店外发、内部方向等错误方向邮件意外进入
Layer 5 时的安全兜底,并与所有正常候选/Risk/未归类原文互斥。
## 12. Candidate 汇总不变量
- 有任一正常 target/derived task 时,根 `result_disposition=IN_SCOPE_TASKS`,即使同时存在 Risk 或未归类原文。
- 无任务但有 Risk 时为 `RISK_NOTIFICATION`。
- 只有未归类原文时为 `GENERAL_NOTIFICATION`。
- `IGNORED_DIRECTION` 必须独占。
- `risk_items[]` 与 `unclassified_source_texts[]` 不是 TaskCard,也不是可确认业务任务。
- Layer 6 和 Layer 7 不得修改 Candidate 的类型、参数、拆分或 hash。
## 13. 明确排除范围
- Payment 任务及付款到账核对;
- LianTai-FIT sender/profile;
- 原始 Excel/Rooming List 文件解析;
- Room/Rate/GRPA1/早餐目录推导;
- 当前库存、source allotment 余额与自动完整取消;
- 数据库订单/任务历史、生命周期准入、重复 New、任务顺序;
- 页面订单工作台设计、最终 TaskCard 写入;
- SuperAgent 真实 Profile/Prompt/Secret/Provider 调用;
- PMS、Opera、OHIP、发邮件、库存或订单状态变更。
## 14. 发布门禁
本规则授权仓库内契约、Prompt、Skill source、合成 fixtures、fake adapter、Validator 与离线测试实现。Main Prompt
必须与 Skill 分开交付且不得进入 `.skill` archive。Skill artifact 状态保持 `HOLD/INTERNAL_ONLY`。
真实 SuperAgent 发布、安装 Skill、使用 Secret、连接真实数据库、切换 `AUTHORITATIVE` 或生产启用均需单独批准。