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