# M012 / Layer 3 QBD 确定性 Parser Change Request v1 > **Migration notice(2026-08-11)**:本文记录已验证的旧 QBD Parser 实现。其工作簿业务规则和 > 真实样本证据继续有效,但公共 `FieldObservation` 状态、reason code、`GROUP_IN`、 > `SOURCE_RATE_CODE`、`ROOM_QUALIFIERS` 与字段 Recovery 输出边界已被 ADR-008 的冻结目标契约取代。 > 代码迁移完成前不得把本文的“Approved”误读为新 Parser 契约已实现。 | 项 | 内容 | | --- | --- | | 状态 | Approved — implementation verified locally | | 日期 | 2026-08-10 | | 提出人 | 用户确认的 QBD 程序解析规则与 sender 映射 | | 关联契约 | Parser 历史实现见 `booking-email-architecture-v0.4.md`;当前跨层边界见 `booking-email-architecture-v0.5.md`、`booking-email-contracts-v2.md`;Parser/Recovery 细节见原讨论稿 | | Checkpoint | `M012-qbd-deterministic-parser-v1` | | 影响范围 | Backend / Contracts / Parser / Test / Docs | ## 1. 变更背景 现有固定渠道 Parser 主要按附件文件名、Sheet 和表头评分识别渠道,并把行内容直接聚合为当前事实。它无法完整表达 QBD 月表已经确认的 `GROUP IN`、CXL 日期补全、富文本划线 BEFORE/AFTER、原始房型/价格、数量默认来源、字段状态和稳定 evidence;现有 C03 还把 `StayFact.nights` 固定写为 null。 本变更先只实现 QBD。普通LianTai在后续独立CR中接入,不能复用或猜测其模板规则。确定性 Parser 只输出不可变 `ParserObservationSet` 与 AFTER/CURRENT 初始 facts;完整 Layer 3 仍按共用契约经过 RecoveryRequestBuilder、字段 Recovery、本地 Validator/Assembler 后形成 `EffectiveFactView`。Parser 不执行 Agent、TaskCard、PMS、Opera、付款、库存扣减或部门流转。 ## 2. 权威输入与版本 ### 2.1 Sender registry | 精确 sender | Profile | 本 checkpoint 状态 | 模板处理 | | --- | --- | --- | --- | | `op.qbdtravel@gmail.com` | `QBD` | ACTIVE | 启用 QBD v1 Parser | | `op.liantaitravel@gmail.com` | `LIANTAI` | REGISTERED_PENDING | fail-closed,不解析附件 | sender 先经既有 `BookingSenderNormalizer` 规范化,再做精确唯一匹配。不得按前缀、显示名或相似域名猜 Profile。 新邮件存在非空 sender 时必须走 registry:未知 sender 为 `NOT_APPLICABLE`;两个 pending Profile 返回明确 `CHANNEL_PROFILE_IMPLEMENTATION_PENDING`。为兼容早期 contracts-v1 重放,只有 sender 缺失的历史/测试输入暂时保留 legacy Parser 路径;该兼容路径不得产生 QBD v1 observations。 ### 2.2 真实样本 真实 XLSX 不进入仓库,只保留哈希和 opt-in 验收参数。 | 月份 | SHA-256 | 主业务 Sheet | 已确认橙色行 | | --- | --- | --- | ---: | | 2026-08 | `b81bd95df6acaafa1c3a457f7047f4d15966aafafe3989d10744b0d8ad65d918` | `2026年8月|AUGUST_26` | 8 | | 2026-07 | `f2bb739a4f511348155c47cdf540aac1fdd608dca71f32a2a23833cbb3a08210` | `2026年7月|JULY_26` | 1 | 两份样本合计 9 个 row unit、16 个 detail segment:7 对 BEFORE/AFTER,2 个 CURRENT。 ### 2.3 子契约版本 - observation:`fixed-channel-observation-v1` - sender registry:`fixed-channel-sender-profile-v3` - QBD profile:`qbd-profile-v1` - QBD rule bundle:`qbd-rules-v1` - QBD parser:`booking-email-parser-qbd-v1` 稳定 ID 必须包含来源消息 ID/revision、attachment id/hash、Sheet、行号、segment/item span/order 和 observation version;不得包含当前时间、标准化结果或 Agent 答案。 ## 3. 范围与非目标 ### 3.1 本次实现 - sender registry 与 ACTIVE/PENDING/UNKNOWN fail-closed 路由; - QBD 标题、唯一业务 Sheet、唯一语义表头和必需列二次验证; - 真实橙色变更行选择; - C/E/H/J 的无损来源观察;QBD F 完全忽略; - E 富文本字符 span、BEFORE/AFTER/CURRENT、房型/早餐/Q10/数量/价格; - J/E/H 日期规则、CXL 和 `nights=departure-arrival`; - AFTER/CURRENT 到 `facts[]` 的当前事实投影; - additive `parser_observations`、稳定 ID、field status/origin/patchability/evidence; - synthetic 测试和两份真实 XLSX 的本地 opt-in 验收。 ### 3.2 明确不做 - 不在本CR中实现普通LianTai模板; - 不实现 Recovery Agent、RecoveryRequest/Patch/Overlay; - 不让现有 Booking Agent 读取 raw/BEFORE observations; - 不决定最终 Allotment source/actual、库存或扣减; - 不从 QBD F 列读取 Rate Code; - 不新增 Controller、权限、数据库表、前端流程或用户可编辑字段; - 不调用 PMS、Opera、OHIP、外部 Agent 或邮件发送。 ## 4. Profile 与模板双门禁 QBD sender 命中后,附件仍必须通过以下模板验证: 1. 当前材料中存在可读取的 `.xlsx`; 2. 唯一业务 Sheet 名符合 `2026年<月>月 | <英文月>_<两位年>`,兼容半角/全角竖线; 3. A1 标题同时包含 `QBD`、月份年份和 `WYNDHAM JOMTIEN PATTAYA`; 4. 前 10 行只有一个表头行能唯一绑定 C/E/H/J 语义; 5. 必需列为 Tour Code、Raw Hotel Detail、操作备注、GROUP IN;F 无论表头或值是否存在均完全忽略,不参与模板门禁、observation、issue、Recovery、Rate Code 或 nights; 6. 损坏、加密、超限、多 QBD 模板附件、多业务 Sheet、重复/歧义表头均 fail-closed。 sender 命中但模板不匹配输出 `PROFILE_TEMPLATE_MISMATCH`,不能回退用文件名强套旧 QBD Profile。 ## 5. QBD 行与字段规则 ### 5.1 选行 - 只选择核心语义单元格 C/E/H/J 使用同一 QBD 变更橙色的行; - 真实橙色 OOXML 为 solid `theme=9,tint=0.8`;允许测试 fixture 使用受控等价 ARGB; - F/G/I/K 不要求填色; - 任意其他非白色不等于橙色。真实 August 第 22 行是 solid theme 0,必须排除; - 表头、普通历史行、其他状态色行不生成 row unit。 ### 5.2 团号与操作 - Tour Code 完整去除首尾空白后写入 `group_code`,不得拆成客人姓名; - H 同时保存 `cell_raw`、`operation_date`、`operation_target_raw`、`operation_kind`; - 受控动作包括 `NEW BOOKING`、`AMD BOOKING`、`AMD ALLOTMENT`、`CANCEL`、`CANCEL BOOKING`、`AMD GROUP CODE`; - C03 `action_hint` 只投影 `NEW_BOOKING / UPDATE_BOOKING / CANCEL_BOOKING`,原动作继续保存在 observation; - J 为 CXL/CXL: 时 operation kind 强制 CANCEL,但不丢失 H 原文。 ### 5.3 日期与 nights - J 为真实日期:arrival=J;E 首部 start day 必须交叉验证;end>=start 时离店为同月,end