Files
th-hotel-simple/docs/project/requirements/M012-booking-email-confirmation-e2e-v01.md
T

24 KiB
Raw Blame History

M012 酒店预订邮件识别到人工确认端到端 V0.1

项目 内容
文档状态 V0.1 已实现并验收;2026-08-08 补充 BR00 订单卡双区块后端收口
业务基线 BR00-BASELINE-1,高于本仓库旧样例、旧 prompt 和旧字段语义
适用范围 员工上传真实 .eml 或 AgentBus 投递邮件后,到任务卡关键参数被用户确认为止
非适用范围 确认后的 Opera / PMS API 调用、客户回复、Invoice、独立 Manual RateCode、Rooming List 名单解析
Catalog 版本 booking-catalog-v20260808
Parser 版本 booking-email-parser-v0.1

本次“已实现并验收”指三封指定真实邮件覆盖的固定渠道纵向切片:普通员工手工导入、LIANTAI/QBD workbook 确定性解析、V4 建卡、字段复核与人工确认。第 4 节仍完整保存 BR00 业务语义,但未被这三封样本覆盖的 Allotment、独立 Rooming List、独立 Payment、图片语义和统一 Booking Agent fallback,不因本次切片通过而宣称已完成;其现状和边界见第 15 节。

1. 背景与架构位置

AgentBus 同时承担邮件消息入口和 Agent 平台。本项目不能把“附件解析”孤立成一次文件读取:邮件正文、当前回复、历史往来、附件版本和本次附件中的更新行共同决定业务动作。

本期沿用七层架构,并把邮件入口耦合进去:

  1. 信息系统接入与编排:接收 AgentBus 邮件或员工上传的 .eml,建立 SourceMessage、附件摘要、幂等键和处理批次。
  2. 材料预处理:拆分 current 与 quoted history,识别附件版本,对固定渠道 Excel 只选择本次候选业务行,并保留 sheet/行号/底色等证据。
  3. 确定性 Parser + Catalog:用版本化业务目录把渠道字段、动作别名、房型写法和数值编码归一为标准事实;不能可靠解析的字段保持未解决,不猜测。
  4. 当前态与邮件上下文:按会话读取历史 SourceMessage 和既有业务任务,向业务识别提供“当前邮件事实 + 必要历史事实”,历史内容不重复触发动作。
  5. 业务识别:程序先处理固定渠道可确定部分;失败或存在语义歧义时,把隐私最小化的材料交给 Booking Agent。输出业务事件、关联事件和通知,不直接调用 PMS。
  6. 确定性校验与人工确认:同一 Catalog 校验程序或 Agent 输出,生成可编辑任务卡、缺失字段、告警和证据;即使全部字段符合目录,也必须由用户确认。
  7. 执行适配器:确认后调用 PMS/Opera,明确不在 M012 V0.1 范围。

Catalog 不单独构成业务层。它是第 3 层的版本化确定性知识,同时被 Parser、第 5 层 Agent reference 和第 6 层校验器消费。三者必须记录同一个 Catalog 版本,避免同一来源词在不同环节被翻译成不同业务代码。

2. 目标

  • 提供普通员工可用、受现有权限保护的 .eml 导入 API 和可点击页面,不依赖 Debug Key。
  • 保存一封实际邮件一次;相同实际邮件重复投递命中幂等,分别发送的邮件分别处理。
  • 对固定渠道 Excel 按“业务范围内整行均有非白色底色”选择候选行,而不是任一单元格有颜色。
  • 将候选行确定性解析成统一事实,生成 New / Update / Cancel 及 Trace、Rooming List、Payment 或 General/Risk 通知。
  • 复用 SourceMessage 与 Reservation V4 任务卡、目录 lookup、人工复核、确认和审计主线。
  • 在 UI 展示邮件证据、解析证据、告警、关键字段和确认阻断;用户可修正允许字段并逐卡确认。
  • 用三封 2026-08-08 指定真实邮件做只读验收输入,并用去隐私合成 fixture 建立自动化回归。
  • 提供 PostgreSQL 项目专属 Schema th_hotel_booking 的版本化迁移与验证脚本;远程执行前必须只读确认无同名冲突及权限边界。

3. 非目标

  • 不在本期执行 PMS/Opera 操作,也不伪造执行成功。
  • 不自动回复邮件、发送通知或修改 AgentBus 外部状态。
  • 不以 Tour Code 代替 Group Code,也不根据样例猜 Account / Market / Source / Rate Code 映射。
  • 不把价格作为前端结构化确认字段;价格只参加未来 Rate Code 映射并留在来源证据中。
  • 不解析 Rooming List 旅客名单、不导入旅客信息、不做名单差异比对。
  • 不将真实邮件、真实附件、旅客信息、邮箱地址或数据库密码提交到 Git。

4. 统一业务语义

4.1 生命周期与任务

订单生命周期事件:

  • NEW_BOOKING
  • UPDATE_BOOKING
  • CANCEL_BOOKING

同订单可附带的独立任务卡:

  • TRACE_RESERVATION_NOTES
  • ROOMING_LIST
  • PAYMENT

来源邮件级通知:

  • S10 General:整封当前邮件没有任何支持的业务任务时最多一条。
  • S99 Risk:当前邮件存在无法分类或高风险歧义时最多一条;已识别的清晰任务仍继续创建。

已知类型但缺字段时保留原类型卡并进入 REVIEW_REQUIRED,不得降格成 Risk。

4.2 名称和订单字段

  • Tour Code 是 Name 的一个可能来源值。
  • Name of Group、Group Name 在本期统一为 Group Name = Block Name。
  • Group Code 是独立字段,不能由 Tour Code 推导。
  • 一个清晰预订分组生成一个订单任务;多个 Name、多个房型、多个日期段可以属于同一订单,不按 Name 拆卡。
  • New Booking 的实际总房数 < 5 为 FIT,>= 5 为 GROUP。
  • 所有订单相关任务均有 Basic Information 与 Room Information。
  • Basic Information 只允许由已确认发件人映射生成;映射未配置时 Account/Market/Source 留空并阻断确认。

4.3 New Booking 确认条件

通用必填:

  • Booking Type
  • 一个或多个 Name
  • Arrival Date、Departure Date;Nights 由日期派生
  • 至少一个 Room Type + Quantity
  • 一个订单级 Rate Code

Group 额外必填:

  • Group Code
  • Group Name / Block Name

Rate Code 根据公司、房型、价格、早餐信息映射,但当前映射仍待配置。零候选或多候选都保留 New Booking,让用户选择并阻断确认;系统不得自动选择。

4.4 Update、Cancel 与关联任务

  • AMEND、AMD、AMEND TO 等归一为 Update;旧 Group Code → 新 Group Code 仍视为同一订单链。
  • 每封实际 Update 邮件、每个订单生成一张新的 Update 卡,不按历史内容做业务去重。
  • Cancel 只在未来 PMS 执行成功后结束订单;本期只确认 Cancel 动作参数。
  • Allotment:N 个实际团生成 N 张 New 卡,并生成一个共享来源扣减/取消动作;来源动作失败不阻断实际团。
  • Extra Bed 是 Trace,不是 Room Type;同一 Group Code、同一当前邮件的多个 Trace item 合并一张卡。
  • Trace 的 Department 必须由用户选择 FO、HSK 或 FO+HSK 才能确认。
  • Rooming List 与 Payment 的原生事项卡仍是通知型卡,不解析名单、不核验付款;其 companion Room 只补订单上下文,不改变该业务边界。

4.5 订单任务双区块与 companion Room

  • 每个可定位订单的 Order Task 固定包含一张 BASIC_INFORMATION;New、Update、Cancel、Trace、Rooming List、Payment 都必须同时具备 ROOM_INFORMATION,General/Risk 来源通知不适用。
  • New、Update、Cancel 继续由生命周期 event 直接生成 Room Information。若同一 source_message + order_ref 已有任一生命周期 event,不再为同组 Trace、Rooming List、Payment 重复生成 Room。
  • 若一个 Order Task 只有 Trace、Rooming List、Payment,则以后端接收顺序中的首个关联 event 生成一张共享 companion Room,并保留该 event 的类型、transition 和 source_event_index 以便追溯;不能伪装成新的 UPDATE_BOOKING event。
  • companion Room 只展示同订单已确认完成态或前置 New 计划完成态:current_values 与 final_values 使用该投影,proposed_values 为空,change_summary 为空;辅助 event 自身字段不得被当成房间变更。
  • 同一 Order Task 的多张业务卡仍分别确认;Basic Information 保持前置门禁。Trace、Rooming List、Payment 专属卡、附件安全边界和确认副作用不因 companion Room 改变。
  • 本增量只改变新建订单任务的 intake 行为;已持久化且已有卡片的历史 V4 Order Task 受现有幂等门禁保护,不在重放时隐式补卡。若存量也要补齐,必须另做可审计、可回滚的受控回填。

5. 邮件与上下文边界

5.1 current/history

  • 当前邮件的正文、当前附件和明确的当前指令可以触发业务。
  • quoted history 只用于解释当前语义、继承订单身份和检测重复,不可再次触发旧动作。
  • 没有附件时不经过 Excel 预处理,直接进入上下文组装和业务识别;并非跳过 SourceMessage 接入。
  • 当前正文仅有标题/签名、但附有明确更新附件时,附件是本次动作核心证据。

5.2 附件版本

  • 当前邮件明确写 REV.n、use this file 等版本指令时,优先当前附件。
  • 当前版本没有严格候选行或只有部分底色时,可以和同会话前版本做差异,差异只作为复核证据,不直接成为自动执行事实。
  • 同一事实再次出现时仍展示重复/陈旧告警,由用户判断,不静默吞掉新邮件。

6. 固定渠道预处理

6.1 共同底色口径

  • 只认单元格底色,不认字体颜色、批注、筛选或条件格式推测。
  • 白色、默认色、无填充不算。
  • 只检查渠道 Catalog 定义的业务列范围;该范围内每个业务列都必须有非白色底色,才是 STRICT_CURRENT_CANDIDATE。
  • 一条渠道业务记录可以由 anchor row 与后续 continuation rows 组成;Tour Code/酒店明细在 anchor,当前动作在后续行时,以该 logical record block 的业务列底色并集判断完整性,并同时记录 anchor/action row。不得要求所有字段出现在同一物理行。
  • 只有部分业务列有底色时记为 PARTIAL_FILL_REVIEW_EVIDENCE,不自动生成事实。
  • 表头颜色不触发业务行。

6.2 渠道 Profile

Profile 表头 业务列 动作列 主要来源列
LIANTAI_FIT 3 A:F F Tour Code=B;酒店明细=D/E
QBD_MONTHLY 3 B:H H Tour Code=C;酒店明细=E
LIANTAI_UPDATE 2 A:I G Tour Code=A;酒店明细=D;状态=H;备注=I

动作别名必须由 Catalog 版本管理,至少包含:

  • New:NEW BOOKING
  • Update:UPDATE、AMEND、AMD BOOKING、AMEND TO
  • Cancel:CANCEL、CXL、ยกเลิก

不得为了“程序识别”无限穷举整句。Parser 先分离动作核心 token、动作日期和自由文本,再用有限别名字典归一;不能可靠归一时降级 Agent 或人工复核。

6.3 当前日期与异常年份

  • 候选行的动作 marker 优先按日/月与邮件接收或发送日期匹配。
  • FIT 文件若因 Excel 自动填充出现 2026、2027……2041 的异常连续年份,日/月匹配的行仍保留为候选,并加 ACTION_DATE_YEAR_ANOMALY。
  • 异常年份不能作为真实业务年;入住/离店年份以邮件业务日期和 sheet 月份解析,并在不唯一时留待复核。
  • QBD 中严格底色但动作日期早于当前邮件日期的行保留证据并加 STALE_STRICT_CANDIDATE,不得静默当成本次唯一事实。

7. Parser 与 Agent 分工

7.1 确定性 Parser 输出

Parser 输出事实,不输出 PMS 命令:

{
  "catalog_version": "booking-catalog-v20260808",
  "parser_version": "booking-email-parser-v0.1",
  "source": {
    "attachment_name": "safe-name.xlsx",
    "profile": "LIANTAI_UPDATE",
    "sheet": "safe-sheet",
    "row_number": 57,
    "selection_status": "STRICT_CURRENT_CANDIDATE"
  },
  "action": "NEW_BOOKING",
  "names": ["TOUR-CODE-VALUE"],
  "group_code": null,
  "group_name": null,
  "arrival_date": "2026-08-12",
  "departure_date": "2026-08-15",
  "room_items": [
    {
      "source_label": "U-TWN8.5",
      "room_type_code": "RM3",
      "room_count": 12
    }
  ],
  "rate_code": null,
  "warnings": ["RATE_CODE_UNRESOLVED"]
}

固定渠道优先走程序:

  1. Profile、严格候选行、动作和字段均可确定时,由 Parser 生成标准事实。
  2. 某个字段失败时保留已确定字段,标注具体 warning;不丢弃整行。
  3. 需要自然语言语义、图片、复杂历史关系或未知格式时,将最小必要材料交给 Booking Agent。
  4. Agent 输出仍必须经过相同 Catalog 版本的确定性校验和人工确认。

7.2 初始房型映射

当前可确定映射:

  • U-DBL、Sup DBL → RM2
  • U-TWN、Sup TWN → RM3
  • U-TRP、Sup TRP、TRP → RM2
  • DBL SUITE → SU1
  • 明确 TWN SUITE → SU2
  • FAM 6+4 → RM4
  • Family 3/4 → SU3

ST:1卧双标 TWN 等未被当前目录唯一覆盖的变体必须留空并复核。即使不同原始房型映射到同一 PMS code,也必须分别保留 raw label 与数量,不能按 PMS code 合并。

8. 后端接口

8.1 普通员工 EML 导入

POST /api/reservation/booking-email-intakes

  • multipart/form-data
  • file:必填,只接受 .eml
  • hotel_id:可选,仍以当前登录用户酒店上下文校验
  • 权限:RESERVATION_TASK_EDIT
  • 文件大小遵循 Spring multipart 和本模块安全上限
  • 不接收 Debug Key,不直接接受外部附件 URL

成功返回 HTTP 201:

{
  "source_message_id": "123",
  "duplicate": false,
  "status": "TASKS_CREATED",
  "catalog_version": "booking-catalog-v20260808",
  "parser_version": "booking-email-parser-v0.1",
  "order_task_ids": ["501"],
  "source_notification_ids": [],
  "warnings": [
    {
      "code": "RATE_CODE_UNRESOLVED",
      "message": "Rate Code 需要人工选择"
    }
  ]
}

重复投递返回同一 SourceMessage 和已有任务/通知,不重复创建。

8.2 内部 V4 契约增量

  • names[] 是 Agent/Parser 的标准数组;V0.1 前端投影为换行分隔 names_text 便于编辑。
  • group_code 与 group_name 分别保存;兼容读取旧 group_block_name / fit_name,新入口不再错误默认。
  • booking_type、group_code、group_name、names_text、日期、房型数量、Rate Code 进入 Room Information 的最终值。
  • nights 为派生只读字段。
  • block_id 不展示。
  • Confirmation Number 仅当当前邮件明确提及时展示。
  • recognition 安全块保留 profile、sheet、anchor row、action row、版本、选择状态和 warning code,不包含完整原始行、附件 URL 或旅客隐私。
  • 新订单在确认前允许 order_id / target binding 未解决;人工复核不得强制用户输入已存在本地订单 ID。Update/Cancel 等既有订单动作仍要求可靠归属。
  • V4 intake 必须按 4.5 的规则保证每个订单任务具有 Room Information;这是同一 Order Task 的展示/确认上下文补齐,不要求 Parser 或 Agent 在 Trace、Rooming List、Payment event 中复制完整房间字段。
  • GET /api/reservation/order-tasks/{orderTaskId} 对 companion Room 返回与生命周期 Room 相同的安全 display_payload.room_information 和 fields[] 结构;辅助 event 采用 current-only 模型,不返回 Agent target_order、邮件正文、附件 URL 或原始 evidence。

9. 前端验收

新增路由:/reservation/email-intake,权限 RESERVATION_TASK_EDIT。

页面必须是真实可操作页面并连接后端:

  • 点击或拖放选择一封 .eml;显示文件名、大小和移除/重新选择。
  • 导入按钮有 idle、uploading、success、partial warning、duplicate 和 error 状态。
  • 成功后展示 SourceMessage ID、解析版本、任务数、通知数和 warning。
  • 每个创建结果可点击进入现有订单任务详情或来源通知详情;可返回任务队列。
  • 任务列表提供“导入邮件”入口。
  • 任务详情展示识别证据和 warning;Basic Information 必须先确认,随后业务卡可编辑/确认。
  • New Booking 未绑定既有订单时隐藏或明确标注“无需现有订单 ID”,不得用不可理解的强制输入阻塞用户。
  • 具备键盘焦点、可见 label、错误关联、响应式布局及 loading/empty/error 状态。

10. 安全、隐私与审计

  • 原始 EML 与附件内容仅在受控 SourceMessage 原文能力或临时解析内使用;普通任务 API 只返回安全摘要。
  • 日志、错误、前端结果和 migration 不输出原始正文、真实邮箱、完整原始行、OSS 签名 URL、数据库密码或其他 Secret。
  • 真实样本不得复制到 server/src/test/resources;测试 fixture 必须合成并去隐私。
  • 用户对 Basic、Room、Trace 等卡的复核/确认继续写现有审计日志。
  • 业务操作记录保留要求为 3 个月;V0.1 先保存 retention_until/可清理时间语义,清理调度不在本期强制范围。

11. PostgreSQL 项目专属 Schema

  • Schema 固定为 th_hotel_booking。
  • migration 必须显式 CREATE SCHEMA IF NOT EXISTS th_hotel_booking 并限定 search_path 或使用全限定名。
  • 不修改 public,不复用旧脚本中的 booking schema,不删除或改名其他项目对象。
  • 远程执行顺序:只读读取 current_database/current_user → 列出同名 schema/对象 → 检查 CREATE/USAGE 权限 → 创建本 schema → 只在本 schema 运行 migration → 对比其他 schema 对象计数/更新时间。
  • 密码只通过进程环境或交互输入,绝不写入仓库、planning 文件、命令回显或交付文本。
  • 当前 Spring 应用仍以 MySQL/H2 为运行基线;PostgreSQL migration 是项目专属目标模型,数据库方言切换不在本期暗中完成。

12. 测试与完成定义

12.1 自动化

  • EML:Message-ID、Conversation-ID、current/history、无附件和多个附件。
  • Excel:三种 Profile、整行业务列有非白底色、部分底色、白色、表头颜色、异常年份、陈旧严格行、多个日期段。
  • Parser:动作别名、房型/价格/数量、New 房量阈值、Update、Cancel、Extra Bed Trace、未解决字段。
  • 业务:一单多 Name/房型/日期段不误拆;三封样本中的生命周期/linked Trace;无确定事实时单封通知;重复投递幂等。Allotment、独立 Rooming List/Payment 与图片语义不属于本次固定渠道切片的完成证据。
  • API:权限、文件类型/大小、201、重复结果、错误安全、任务创建。
  • V4:新订单不要求既有 order ID;Basic 先确认;关键字段阻断;确认审计。
  • V4 双区块:独立 Trace、Rooming List、Payment 各自创建 Basic + companion Room + 专属卡;同组已有 New/Update/Cancel 时不重复 Room;详情返回 current-only Room 模型。
  • 前端:上传成功/失败/重复、结果链接、字段渲染、warning、复核与确认请求。

12.2 三封真实邮件验收

真实样本只从用户指定绝对路径读取:

  1. FIT LIANTAI:动作日/月候选可被选中;异常连续年份产生 warning,不被当成 2041 年业务日期。
  2. QBD REV.2:当前严格行与陈旧严格行被区分;同一 Tour Code 的多个日期段和房型保持一个订单任务。
  3. Update Booking ครั้งที่2:严格底色行生成 9 New/1 Update;Honeymoon 和 Extra Bed 分别生成同订单 Trace;未确认映射保持待复核。

12.3 完成定义

只有以下条件同时满足才可宣布 V0.1 完成:

  • 普通员工可在可点击页面上传三封真实邮件。
  • 后端真实创建 SourceMessage、订单任务/通知和任务卡,重复上传不重复建任务。
  • 页面可查看证据、warning、关键参数,并能完成允许范围内的修正与确认。
  • 没有执行 PMS/Opera。
  • 后端和前端自动化通过;本地全栈 smoke 通过。
  • PostgreSQL migration 通过本地语法/作用域验证;若远程测试库凭据可安全注入且只读检查无冲突,则项目 schema 已创建并证明未触碰其他 schema。
  • 自主推断规则、应用代码位置、未解决业务映射和测试证据已记录。

13. 与 M011 的关系

M011 仍描述“给 SuperAgent 的通用 Excel 高亮证据增强”,其 V1 规则是任一业务单元格有底色即可抽取。M012 在固定渠道、本期业务入口中采用更严格且已获业务确认的“业务范围整行均有非白底色”规则,并直接形成标准事实候选。

两者不应静默混用:

  • M011 通用 extraction 继续供 Debug/AgentBus 证据预览。
  • M012 deterministic parser 使用独立的版本化 Channel Profile 和严格行选择器。
  • 后续若把 M012 规则推广到 M011,需单独 Change Request 和回归验证。

14. 待配置而非待猜测

  • 发件人/渠道 → Account Code / Account Name / Market Code / Source Code 映射。
  • 公司 + 房型 + 价格 + 早餐 → 唯一 Rate Code 映射。
  • TWN SUITE 的正式房型代码。
  • Allotment 来源扣减动作在未来 PMS 适配器中的命令结构。
  • AgentBus 生产投递的认证/重试配置和 Booking Agent profile 版本。

这些项不阻止识别和建卡,但相关卡必须保持 REVIEW_REQUIRED 或明确 warning,不能用样本值代填。

15. 实施与验收结果

2026-08-08 已完成以下纵向闭环:

  • 普通员工页面 /reservation/email-intake 可选择或拖放 .eml,调用真实 POST /api/reservation/booking-email-intakes,显示解析结果、warning,并跳转到真实 V4 订单任务或来源通知。
  • 三封外部真实 EML 的 API 验收结果依次为 20、1、10 张订单任务;业务构成为 19 Update + 1 Cancel、1 Update、9 New + 1 Update,并在第三封中生成 3 张 linked Trace;重复导入不重复建任务。
  • 浏览器全栈 smoke 已从任务队列进入导入页,上传第三封真实邮件,打开首张任务,选择 Account 并成功确认 Basic Information;桌面和 390px 宽度无横向溢出,fresh session 无 console warning/error。
  • 后端全量测试 441 项通过,0 failure、0 error、1 skipped;真实样本 opt-in 验收 1 项通过。
  • 前端 29 个测试文件共 255 项通过,TypeScript typecheck 和生产 build 通过;lint 0 error,本轮文件无新增 warning。
  • 测试库已仅创建 th_hotel_booking:9 张表、8 个 identity sequence、30 个索引/约束索引;迁移前后其他项目 schema 对象计数不变,无跨 schema 外键;回滚型 smoke 后无残留测试行。
  • 本期没有调用 PMS/Opera、发送外部邮件或修改 AgentBus 外部状态。

V0.1 的已知运行边界:

  • 普通员工手工导入路径在请求内解析附件,SourceMessage 和任务保存附件元数据与安全识别证据,但不长期保存可下载的原始附件字节;如需原件归档/下载,应接入既有 AgentBus/OSS 原文链路后另做 checkpoint。
  • 固定渠道确定性 Parser 无法形成事实时,当前手工导入路径生成 S10/S99 来源通知供人工处理;不会在该请求内自动调用外部 Booking Agent。既有 AgentBus → SuperAgent 路径继续独立存在,生产启用和统一 fallback 编排需后续配置与联调。
  • Account/Market/Source、Rate Code 和未唯一覆盖的房型映射仍按本章第 14 节由用户确认,不以真实样本值自动猜测。
  • 确认后的 PMS/Opera 执行仍明确不在 V0.1。

16. 2026-08-08 BR00 双区块增量追踪

需求项 后端状态 前端状态 测试状态 文档位置 当前状态
独立 Trace 补 Basic + companion Room Done Done Passed 本文 4.5、8.2、12.1 Implemented
独立 Rooming List 补 Basic + companion Room Done Done Passed 本文 4.5、8.2、12.1 Implemented
独立 Payment 补 Basic + companion Room Done Done Passed 本文 4.5、8.2、12.1 Implemented
同组生命周期 Room 去重 Done N/A Passed 本文 4.5 Implemented
General/Risk、权限、酒店隔离与敏感数据边界不变 N/A N/A Passed(既有回归) 本文 4.5、10 Verified