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

21 KiB
Raw Blame History

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 或生产启用均需单独批准。