实现 M002 订单任务入站与队列规则

This commit is contained in:
andy
2026-07-07 14:29:43 +08:00
parent 7e276469ef
commit f7d77b45bd
78 changed files with 11703 additions and 1 deletions

View File

@@ -10,4 +10,9 @@
- `frontend-development-guidelines.md`:当前项目前端专属规范。
- `go-live-notes.md`当前项目上线注意事项记录上线前检查、环境变量、安全、AgentBus、验证和回滚。
- `requirements/M001-source-message-inbox-prd.md`M001 邮件来源入口 PRD记录 AgentBus 邮件 JSON 入库、历史查询、原文读取和媒体引用边界。
- `requirements/M002-order-task-workflow-v1.md`M002 订单任务主流程 V1记录 SourceMessage 之后的 SuperAgent 抽取、订单挂靠、任务处理和 OPERA/OHIP 模拟操作边界。
- `requirements/M002-order-task-workflow-v2.md`M002 订单任务主流程 V2记录 AI 过渡层、任务卡矩阵、系统主任务类型、临时订单、订单号候选和 OPERA 模拟回填边界。
- `requirements/M002-superagent-task-result-api-contract.md`M002 SuperAgent 任务结果入站接口契约,记录 HMAC 鉴权、请求响应、幂等和错误码。
- `requirements/M002-backend-data-model-design.md`M002 后端数据模型设计,记录 AI 过渡层、订单、任务、任务卡、审计和 OPERA 模拟结果表。
- `requirements/M002-backend-checkpoint-plan.md`M002 后端 checkpoint 计划,记录后端实现拆分、交付物和验收标准。
- `integrations/superagent-agentbus-project-integration-guide.md`:当前项目 SuperAgent 与 AgentBus 验证记录和项目级接入细节。

View File

@@ -82,6 +82,11 @@
- `server/src/main/resources/db/migration/V1__create_source_message_inbox.sql`
- `server/src/main/resources/db/migration/V2__create_source_message_original_access_audit.sql`
当前 M002 订单任务相关 migration
- `server/src/main/resources/db/migration/V3__create_reservation_ai_task_workflow.sql`
- `server/src/main/resources/db/migration/V4__harden_reservation_task_queue_and_manual_conversion.sql`
上线前确认:
- 目标数据库为空库或 Flyway history 与当前代码一致。
@@ -89,6 +94,31 @@
- migration 在 UAT 或测试库已经跑过。
- 表和字段中文注释能正常创建。
- 数据库时间按 UTC 写入,接口层负责返回 ISO 8601。
- 执行 V4 前,如果目标库已有 M002 试运行数据,必须先检查 ACTIVE 订单业务号重复和同订单任务队列序号重复。
V4 前置检查 SQL
```sql
SELECT hotel_id, order_key_type, order_business_key, COUNT(*) AS duplicate_count
FROM workflow_reservation_order
WHERE order_status = 'ACTIVE'
AND order_key_type IN ('GROUP_CODE', 'CONFIRMATION_NUMBER')
AND order_business_key IS NOT NULL
GROUP BY hotel_id, order_key_type, order_business_key
HAVING COUNT(*) > 1;
SELECT hotel_id, order_id, queue_participation, execution_order, COUNT(*) AS duplicate_count
FROM workflow_reservation_task
GROUP BY hotel_id, order_id, queue_participation, execution_order
HAVING COUNT(*) > 1;
```
处理要求:
- 如果任一 SQL 返回记录,不要继续执行 V4。
- ACTIVE 订单业务号重复时,先由业务确认保留哪一条 ACTIVE其他订单应转为 `LOGIC_DELETED``ENDED` 或完成任务迁移后再上线。
- 同订单任务队列序号重复时,先按来源顺序和审计证据重新分配 `execution_order`,确认前置任务关系正确后再上线。
- 不要为了让唯一索引创建成功而随意删除订单、任务或 AI 原始记录。
禁止事项:

View File

@@ -0,0 +1,316 @@
# M002 Backend Checkpoint Plan 后端开发 Checkpoint 计划
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.1 |
| 日期 | 2026-07-07 |
| 状态 | 后端开发计划草稿 |
| 适用范围 | M002 后端实现拆分、交付物和验收标准 |
| 主要读者 | 后端、测试、产品、后续协作 agent |
## 1. 文档定位
本文把 `M002-order-task-workflow-v2.md``M002-superagent-task-result-api-contract.md``M002-backend-data-model-design.md` 拆成可执行后端 checkpoint。
每个 checkpoint 都应先读项目规范,再按本项目包结构和注释要求实现。不要一次性把完整后端做完,也不要在不确定字段或目录归属时先写再重构。
## 2. 开发前必读
后端开发前必须先读:
- `AGENTS.md`
- `README.md`
- `docs/project/backend-development-guidelines.md`
- `docs/import/reusable/backend-development-guidelines.md`
- `docs/import/reusable/backend-base-structure-pagination-guidelines.md`
- `docs/import/reusable/alibaba-java-coding-guidelines-summary.md`
- `docs/project/requirements/M002-order-task-workflow-v2.md`
- `docs/project/requirements/M002-superagent-task-result-api-contract.md`
- `docs/project/requirements/M002-backend-data-model-design.md`
- `docs/import/20260706/开发AI先读_工作顺序.md`
- `docs/import/20260706/AI输出参数并集字典.xlsx`
- `docs/import/20260706/任务卡展示编辑矩阵.xlsx`
## 3. 代码组织要求
后端实现必须继续遵守当前项目包结构习惯:
- `control` 放 Controller 具体实现。
- `service` 放 Service 接口。
- `service.impl` 放 Service 实现类。
- `domain` 放 Entity。
- `mapper` 放 MyBatis Mapper。
- `repository` 放 Repository 接口和具体实现。
- `common.dto``common.request``common.result` 放通用数据载体。
- `common.enums` 放枚举。
- 外部系统适配类放在对应 `integrations.<capability>.<provider>.adapter` 包。
Controller、Service、Service 实现类的方法必须有中文注释。Entity 字段必须有中文注释。Mapper 继承 MyBatis-Plus 的自带方法不强制写注释,自定义方法按复杂度补充中文注释。
## 4. Checkpoint 1接口契约与安全骨架
名称:`m002-cp01-superagent-intake-security`
目标:
- 建立 SuperAgent 任务结果入站接口骨架。
- 完成 HMAC 鉴权、请求体大小限制、timestamp 和 nonce 防重放。
- 暂不创建完整订单任务,只返回受控错误或最小接收结果。
建议范围:
- 新增配置项:`SUPERAGENT_TASK_RESULT_HMAC_SECRET` 等。
- 新增 HMAC 校验组件。
- 新增 nonce 去重存储,第一版可用数据库或可控缓存实现。
- 新增请求 / 响应 DTO。
- 新增统一错误响应。
验收标准:
- 缺失 Header 返回 `401`
- timestamp 超时返回 `401`
- 签名错误返回 `401`
- nonce 重放返回 `409`
- 请求体过大返回 `413`
- 响应和日志不暴露 secret、签名原文或完整请求体。
不做:
- 不做业务字段合法性判断。
- 不执行 OPERA 模拟。
- 不开发前端页面。
## 5. Checkpoint 2AI 过渡层持久化
名称:`m002-cp02-ai-transition-persistence`
目标:
- 建立 AI 接收批次表和 AI 过渡表。
- 保存 `source_message_id + ai_task_results[] + extraction_warnings[]`
- 实现系统生成幂等键,保证重复提交不重复创建记录。
建议范围:
- Flyway migration。
- Entity、Mapper、Repository。
- 批次幂等键生成。
- item 幂等键生成。
- `source_message_id` 存在性校验。
- 保存 AI 原始 JSON 和常用物理列。
验收标准:
- 同一请求重复提交返回幂等重放,不重复插入。
- 同一 `source_message_id` 不同请求体第一版返回 `409`
- 每个 item 保存 `array_index``source_event_index``execution_order`
- 保存 `ai_payload_json`,用户后续修改不得覆盖该字段。
不做:
- 不创建 OPERA 操作。
- 不实现任务详情编辑。
## 6. Checkpoint 3订单、任务和任务卡最小模型
名称:`m002-cp03-order-task-card-model`
目标:
- 建立订单、任务和任务卡表。
- 将 AI 过渡记录映射成系统订单、任务和任务卡。
- 完成主任务类型和任务卡类型映射。
建议范围:
- 订单状态枚举:`TEMPORARY``ACTIVE``ENDED``LOGIC_DELETED`
- 任务状态枚举:`PENDING_CONFIRM``READY``EXECUTING``FAILED``COMPLETED`
- 系统主任务类型枚举。
- 任务卡类型枚举。
- Message Notification 创建临时订单和只读任务卡。
- Fallback / manual_review 创建临时订单和人工复核任务卡。
验收标准:
- New Booking 无业务号时创建临时订单。
- Update / Cancel 可匹配订单时挂已有订单,不可匹配时挂临时订单。
- Message Notification 挂临时订单,但 `queue_participation=false`
- 任务保存 AI 原始 `task_type`、系统主任务类型、任务卡类型和 subtype。
- 任务初始状态符合数据模型设计。
不做:
- 不实现用户编辑确认。
- 不实现 OPERA 模拟。
## 7. Checkpoint 4订单挂靠与队列可处理状态
名称:`m002-cp04-order-queue-availability`
目标:
- 实现订单挂靠规则和同订单任务执行顺序。
- 实现实时计算任务是否可处理。
- 不落库 `BLOCKED` 状态。
建议范围:
-`order_id + execution_order` 查询任务队列。
- 前置参与队列任务未完成时,后续任务只读。
- 前置参与队列任务为 `FAILED``COMPLETED` 时视为结束,不阻塞后续任务。
- `queue_participation=false` 的任务不阻塞队列。
- 数据库通过 `hotel_id + order_id + queue_participation + execution_order` 唯一约束兜底,服务层遇到并发队列序号冲突时重新取号重试。
- 联动任务识别 `parent_source_event_index``linked_task_group_id``blocked_until_parent_completed`
- 第一版可先提供任务详情接口,返回实时可处理状态和只读原因,前端页面后续再定。
验收标准:
- 同一次 AI 返回多个任务时保留数组顺序。
- 前置任务未完成时后续任务不能编辑、确认、执行或重试。
- 前置任务为 `FAILED` 时后续任务不再被阻塞。
- Message Notification 不阻塞其他任务,也不被其他任务阻塞。
- 任务切换订单后按目标订单队列重新计算可处理状态。
不做:
- 不允许用户强制完成前置任务。
- 不允许跳过失败 OPERA 操作。
## 8. Checkpoint 5任务详情、编辑和确认 payload
名称:`m002-cp05-task-detail-confirmed-payload`
目标:
- 提供任务详情读取接口。
- 支持用户编辑任务卡字段。
- 用户确认后生成 `confirmed_payload_json`
建议范围:
- 任务详情 Response 包含 AI 原始摘要、任务卡字段、可处理状态和阻塞原因。
- 第一版任务卡字段矩阵写在代码 Provider 中,避免 Controller / Service 直接硬编码。
- 保存 `draft_payload_json`
- 确认时生成 `confirmed_payload_json`
- 审计用户字段修改和确认。
验收标准:
- 不可处理任务详情只能查看,不能编辑或确认。
- 可处理任务可以保存草稿。
- 用户确认后任务进入 `READY`
- OPERA 参数不得从 `ai_payload_json` 读取。
- 用户修改不覆盖 AI 原始 JSON。
不做:
- 不接真实 OPERA。
- 不把 Excel 全量字段导入数据库。
## 9. Checkpoint 6人工复核转换和订单迁移
名称:`m002-cp06-manual-review-conversion`
目标:
- 实现 Fallback / manual_review 的人工处理。
- 支持转为 New / Update / Cancel。
- 支持任务从临时订单迁移到已有订单。
建议范围:
- 转换类型接口。
- 订单切换接口。
- 临时订单逻辑删除。
- 转换原因字段第一版允许为空,但必须记录审计。
- 转为 `UPDATE_BOOKING``CANCEL_BOOKING` 时目标订单 ID 必填。
- 审计转换、迁移和删除。
验收标准:
- 转为 `NEW_BOOKING` 时临时订单可继续保留;如果 AI 原始 `case_keys` 已有 Group Code 或 Confirmation No.,第一版可先以 `AI_CANDIDATE` 激活原临时订单。
- 转为 `UPDATE_BOOKING``CANCEL_BOOKING` 时必须选择目标订单。
- 原临时订单无有效任务后进入 `LOGIC_DELETED`
- 所有转换都记录前后快照;原因有则记录,没有则为空。
不做:
- 不允许绕过前置任务直接完成。
- 不做 AI 业务合法性二次判断。
## 10. Checkpoint 7OPERA 模拟操作与重试
名称:`m002-cp07-opera-simulation-results`
目标:
- 建立 OPERA 模拟逻辑操作和 attempt 记录。
- 支持执行、失败、重试和结果展示。
- 支持 New Booking 在必要时回填订单业务号。
建议范围:
-`confirmed_payload_json` 生成模拟操作。
- 每个任务可生成多条操作。
- 每次执行或重试新增 attempt。
- 失败任务进入 `FAILED`
- 全部必要操作成功后任务进入 `COMPLETED`
-`business_key_candidates_json` 回填订单业务号。
- 第一版先在订单表设计 `business_key_source``business_key_backfilled_at` 等字段OPERA 模拟执行模块后续再写入。
验收标准:
- 未确认任务不能执行 OPERA 模拟。
- 不可处理任务不能执行 OPERA 模拟。
- 失败操作不能跳过。
- 用户不能强制完成任务。
- 重试不覆盖历史 attempt。
- New Booking 无业务号时,模拟成功后可回填 Confirmation Number 或 Group Code 含义字段。
不做:
- 不接真实 OPERA 接口。
- 不写死真实 OPERA 返回字段路径。
## 11. Checkpoint 8审计、查询和收口
名称:`m002-cp08-audit-query-hardening`
目标:
- 补齐审计查询、列表筛选、安全收口和回归测试。
建议范围:
- 订单任务审计查询。
- 任务列表基础筛选。
- 订单详情下任务时间线。
- 安全日志检查。
- 关键接口集成测试。
验收标准:
- 能追溯 AI 接收、任务创建、订单挂靠、用户修改、确认、迁移、OPERA 模拟和重试。
- 列表不返回完整 AI payload、邮件正文、附件 URL 或 Secret。
- 所有关键路径有测试覆盖。
- `./mvnw test` 通过,或明确说明环境问题。
## 12. 建议开发节奏
建议一次只做一个 checkpoint。每个 checkpoint 完成后:
- 先自查是否符合项目包结构和中文注释规范。
- 运行相关测试。
- 做 code review。
- 再进入下一个 checkpoint。
如果实现过程中遇到以下情况,必须先暂停确认:
- 订单状态需要新增第五种。
- 任务状态需要新增新状态。
- OPERA 模拟结果字段需要写死真实路径。
- Excel 字段矩阵需要从代码配置改成数据库配置。
- 某个类不知道应该放在哪个包。
- 需要调整已确认的 SuperAgent 接口契约。

View File

@@ -0,0 +1,400 @@
# M002 Backend Data Model Design 后端数据模型设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.1 |
| 日期 | 2026-07-07 |
| 状态 | 后端数据模型草稿 |
| 适用范围 | AI 过渡层、订单、任务、任务卡、审计和 OPERA 模拟结果 |
| 主要读者 | 后端、数据库、测试、后续协作 agent |
## 1. 文档定位
本文定义 M002 第一阶段后端数据模型草案,用于支撑 SuperAgent 任务结果入站、订单挂靠、任务卡确认、队列顺序、审计和 OPERA 模拟结果。
本文不是最终建表 SQL。后续写代码前应按本项目后端规范补充 Flyway migration、Entity 中文注释、Mapper、Repository、Service 和测试。
## 2. 设计原则
- 保留 AI 原始 JSON不覆盖、不重写、不丢字段。
- 用户确认后的数据写入 `confirmed_payload_json`OPERA 模拟只读取确认后的 payload。
- 高频查询、幂等、排序和状态机字段冗余为物理列。
- 任务卡字段第一版先写在代码里,但必须通过低耦合 Provider 封装,后续可迁移为数据库配置。
- `BLOCKED` 不作为任务持久状态,而是根据同订单前置任务是否完成实时计算。
- 用户不允许强制完成任务。
- 用户不能跳过失败的 OPERA 模拟操作。
- 同订单队列可处理状态实时计算时,`FAILED``COMPLETED` 都视为前置任务已结束,不阻塞后续任务。
- OPERA 当前是模拟结构,后续真实系统接入时通过 adapter 映射,不污染核心任务模型。
## 3. 枚举
### 3.1 订单状态
| 状态 | 中文说明 |
| --- | --- |
| `TEMPORARY` | 临时订单,用于 New Booking 未取得业务号、Fallback、Message Notification 或暂无法匹配订单的任务容器 |
| `ACTIVE` | 有效订单,可承载可处理业务任务 |
| `ENDED` | 已结束订单,例如已完成、已取消或业务生命周期结束;仍可查看历史 |
| `LOGIC_DELETED` | 逻辑删除订单,主要用于临时订单迁移后废弃,不再承载新任务 |
### 3.2 任务状态
| 状态 | 中文说明 |
| --- | --- |
| `PENDING_CONFIRM` | 待用户确认订单归属和任务字段 |
| `READY` | 已确认,等待执行 OPERA 模拟 |
| `EXECUTING` | 正在执行 OPERA 模拟 |
| `FAILED` | OPERA 模拟失败,必须重试或修正后再执行,不能跳过 |
| `COMPLETED` | 任务已完成 |
说明:
- `Message Notification` 不参与执行队列,可在创建后直接进入 `COMPLETED` 或保持只读完成态。
- `manual_review` / `Fallback` 默认进入 `PENDING_CONFIRM`,等待人工转换或处理。
- 阻塞态通过查询同订单前置任务实时计算,不落 `BLOCKED` 状态。
### 3.3 系统主任务类型
| 类型 | 中文说明 |
| --- | --- |
| `NEW_BOOKING` | 新建订单任务 |
| `UPDATE_BOOKING` | 更新订单任务,下面包含多种任务卡 |
| `CANCEL_BOOKING` | 取消订单任务 |
| `MANUAL_REVIEW` | 人工复核 / Fallback 任务 |
| `INFORMATIONAL_MESSAGE` | 只读信息提醒任务,不参与执行队列 |
### 3.4 任务卡类型
第一版建议包含:
- `NEW_BOOKING`
- `UPDATE_BOOKING`
- `CANCEL_BOOKING`
- `VOUCHER_RECEIVED`
- `ROOMING_LIST`
- `AMEND_GROUP_CODE`
- `TRACE_RESERVATION_NOTES`
- `TA_RECORDER`
- `MESSAGE_NOTIFICATION`
- `FALLBACK_REVIEW`
## 4. 表设计总览
| 表名 | 中文说明 |
| --- | --- |
| `workflow_reservation_ai_batch` | AI 任务结果接收批次表 |
| `workflow_reservation_ai_transition` | AI 任务结果过渡表,一条 AI item 一行 |
| `workflow_reservation_order` | 订单表 |
| `workflow_reservation_task` | 任务表 |
| `workflow_reservation_task_card` | 任务卡确认数据表 |
| `workflow_reservation_audit_log` | 订单任务审计表 |
| `workflow_reservation_opera_operation` | OPERA 模拟逻辑操作表 |
| `workflow_reservation_opera_operation_attempt` | OPERA 模拟操作尝试记录表 |
表名前缀使用 `workflow_reservation`,表示当前属于 reservation 工作流,不放入平台中立 `platform` 模块。
## 5. AI 接收批次表
表名:`workflow_reservation_ai_batch`
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | 批次 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `source_message_id` | `BIGINT` | 关联 SourceMessage |
| `request_payload_sha256` | `CHAR(64)` | 原始请求体 SHA-256 |
| `batch_idempotency_key` | `CHAR(64)` | 系统生成的批次幂等键 |
| `client_id` | `VARCHAR(128)` | SuperAgent 调用方 ID |
| `request_id` | `VARCHAR(128)` | 调用方请求 ID可为空 |
| `received_at` | `DATETIME(6)` | 接收时间 |
| `item_count` | `INT` | AI item 数量 |
| `extraction_warnings_json` | `LONGTEXT` | 抽取警告 JSON |
| `created_at` / `updated_at` | `DATETIME(6)` | 创建和更新时间 |
索引建议:
- 唯一索引:`hotel_id + batch_idempotency_key`
- 普通索引:`hotel_id + source_message_id`
- 普通索引:`hotel_id + received_at`
## 6. AI 过渡表
表名:`workflow_reservation_ai_transition`
一条 `ai_task_results[]` item 对应一行。
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | AI 过渡记录 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `batch_id` | `BIGINT` | 接收批次 ID |
| `source_message_id` | `BIGINT` | 来源消息 ID |
| `source_event_index` | `INT` | AI current 事件序号 |
| `array_index` | `INT` | AI 返回列表中的顺序,建议从 1 开始 |
| `execution_order` | `INT` | 映射到订单任务队列的初始顺序 |
| `catalog_code` | `VARCHAR(32)` | Skill 目录代码 |
| `skill_id` | `VARCHAR(128)` | Skill 标识 |
| `result_type` | `VARCHAR(32)` | AI 结果类型 |
| `ai_task_type` | `VARCHAR(64)` | AI 原始任务类型 |
| `system_task_type` | `VARCHAR(64)` | 系统主任务类型 |
| `task_card_type` | `VARCHAR(64)` | 任务卡类型 |
| `task_subtype` | `VARCHAR(128)` | 业务动作 subtype |
| `current_or_history` | `VARCHAR(32)` | 当前或历史标识 |
| `group_code` | `VARCHAR(128)` | Group Code 候选 |
| `confirmation_number` | `VARCHAR(128)` | Confirmation Number 候选 |
| `item_payload_sha256` | `CHAR(64)` | 单个 item JSON 哈希 |
| `item_idempotency_key` | `CHAR(64)` | 系统生成 item 幂等键 |
| `manual_reason_code` | `VARCHAR(128)` | 人工复核原因码 |
| `parent_source_event_index` | `INT` | 父任务事件序号 |
| `linked_task_group_id` | `VARCHAR(128)` | 联动任务组 ID |
| `blocked_until_parent_completed` | `TINYINT(1)` | 是否等待父任务完成 |
| `ai_payload_json` | `LONGTEXT` | AI 原始 item JSON |
| `case_keys_json` | `LONGTEXT` | 订单候选键 JSON |
| `extracted_fields_json` | `LONGTEXT` | AI 业务字段 JSON |
| `manual_review_json` | `LONGTEXT` | 人工复核 JSON |
| `informational_message_json` | `LONGTEXT` | 信息提醒 JSON |
| `attachments_json` | `LONGTEXT` | 附件 JSON |
| `context_used_json` | `LONGTEXT` | 上下文 JSON |
| `created_at` / `updated_at` | `DATETIME(6)` | 创建和更新时间 |
索引建议:
- 唯一索引:`hotel_id + item_idempotency_key`
- 普通索引:`hotel_id + source_message_id + source_event_index`
- 普通索引:`hotel_id + result_type + ai_task_type`
- 普通索引:`hotel_id + group_code`
- 普通索引:`hotel_id + confirmation_number`
## 7. 订单表
表名:`workflow_reservation_order`
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | 系统内部订单 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `order_key_type` | `VARCHAR(32)` | 业务号类型:`GROUP_CODE``CONFIRMATION_NUMBER``TEMPORARY` |
| `order_business_key` | `VARCHAR(128)` | 真实业务号 |
| `active_business_key` | `VARCHAR(128)` | ACTIVE 订单唯一约束辅助键ACTIVE 业务订单时等于真实业务号,其他状态为空 |
| `temporary_order_code` | `VARCHAR(128)` | 临时订单展示编号 |
| `order_status` | `VARCHAR(32)` | 订单状态 |
| `business_key_source` | `VARCHAR(64)` | 业务号来源,例如 `AI_CANDIDATE``USER_CONFIRMED``OPERA_SIMULATION_RESULT` |
| `business_key_backfilled_at` | `DATETIME(6)` | New Booking 成功后回填真实业务号的 UTC 时间 |
| `display_name` | `VARCHAR(256)` | 前端展示名称 |
| `source_message_id` | `BIGINT` | 首次创建该订单的来源消息 |
| `created_from_task_id` | `BIGINT` | 首次创建该订单的任务 ID可为空 |
| `ended_at` | `DATETIME(6)` | 订单进入 ENDED 的时间 |
| `logic_deleted_at` | `DATETIME(6)` | 逻辑删除时间 |
| `logic_deleted_reason` | `VARCHAR(512)` | 逻辑删除原因 |
| `version` | `BIGINT` | 乐观锁版本 |
| `created_at` / `updated_at` | `DATETIME(6)` | 创建和更新时间 |
索引建议:
- 普通索引:`hotel_id + order_status + updated_at`
- 普通索引:`hotel_id + order_key_type + order_business_key`
- 唯一索引:`hotel_id + order_key_type + active_business_key`,用于保证同一 `hotel_id + GROUP_CODE` / `hotel_id + CONFIRMATION_NUMBER` 只能有一个 ACTIVE 订单
- 唯一索引:`hotel_id + temporary_order_code`
说明:
- MySQL 第一版不依赖部分唯一索引;通过 `active_business_key` 在 ACTIVE 业务订单时写入真实业务号、非 ACTIVE 或临时订单为空,配合唯一索引保证同一业务号只能有一个 ACTIVE 订单。
- `LOGIC_DELETED` 订单不得再挂新任务。
## 8. 任务表
表名:`workflow_reservation_task`
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | 系统任务 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `order_id` | `BIGINT` | 当前挂靠订单 ID |
| `source_message_id` | `BIGINT` | 来源消息 ID |
| `ai_transition_id` | `BIGINT` | AI 过渡记录 ID |
| `result_type` | `VARCHAR(32)` | AI 结果类型 |
| `ai_task_type` | `VARCHAR(64)` | AI 原始任务类型 |
| `system_task_type` | `VARCHAR(64)` | 系统主任务类型 |
| `task_card_type` | `VARCHAR(64)` | 任务卡类型 |
| `task_subtype` | `VARCHAR(128)` | 业务动作 subtype |
| `task_status` | `VARCHAR(32)` | 任务状态 |
| `queue_participation` | `TINYINT(1)` | 是否参与订单执行队列 |
| `execution_order` | `INT` | 同订单执行顺序 |
| `parent_task_id` | `BIGINT` | 父任务 ID可为空 |
| `parent_source_event_index` | `INT` | 父任务 source event index |
| `linked_task_group_id` | `VARCHAR(128)` | 联动任务组 ID |
| `blocked_until_parent_completed` | `TINYINT(1)` | 是否等待父任务完成 |
| `last_failure_reason` | `VARCHAR(512)` | 最近失败原因摘要 |
| `confirmed_at` | `DATETIME(6)` | 用户确认时间 |
| `completed_at` | `DATETIME(6)` | 完成时间 |
| `version` | `BIGINT` | 乐观锁版本 |
| `created_at` / `updated_at` | `DATETIME(6)` | 创建和更新时间 |
索引建议:
- 普通索引:`hotel_id + order_id + queue_participation + execution_order`
- 唯一索引:`hotel_id + order_id + queue_participation + execution_order`,用于防止并发创建任务时出现相同队列序号
- 普通索引:`hotel_id + task_status + updated_at`
- 普通索引:`hotel_id + source_message_id`
- 普通索引:`hotel_id + ai_transition_id`
队列规则:
- `queue_participation=false` 的任务不阻塞队列,适用于 `Message Notification`
- 是否可编辑、可确认、可执行由服务层实时计算。
- 当前任务前面存在未完成且参与队列的任务时,本任务只能查看。
## 9. 任务卡确认数据表
表名:`workflow_reservation_task_card`
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | 任务卡 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `task_id` | `BIGINT` | 所属任务 |
| `task_card_type` | `VARCHAR(64)` | 任务卡类型 |
| `field_contract_version` | `VARCHAR(64)` | 字段契约版本,第一版可写 `code-v1` |
| `ai_payload_json` | `LONGTEXT` | 任务卡使用的 AI 原始 JSON 快照 |
| `draft_payload_json` | `LONGTEXT` | 用户编辑草稿,可为空 |
| `confirmed_payload_json` | `LONGTEXT` | 用户确认后的最终 payload |
| `confirmed_by` | `VARCHAR(128)` | 确认人 |
| `confirmed_at` | `DATETIME(6)` | 确认时间 |
| `created_at` / `updated_at` | `DATETIME(6)` | 创建和更新时间 |
索引建议:
- 唯一索引:`hotel_id + task_id`
- 普通索引:`hotel_id + task_card_type`
说明:
- 第一版任务卡字段配置写在代码中,但通过 `TaskCardFieldDefinitionProvider` 之类的接口提供,避免 Controller / Service 直接依赖硬编码数组。
- 后续如果改为数据库配置,只替换 Provider 实现,不改任务核心流程。
## 10. 审计表
表名:`workflow_reservation_audit_log`
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | 审计 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `order_id` | `BIGINT` | 订单 ID可为空 |
| `task_id` | `BIGINT` | 任务 ID可为空 |
| `operation_id` | `BIGINT` | OPERA 模拟操作 ID可为空 |
| `actor_type` | `VARCHAR(32)` | 操作人类型,例如 `SYSTEM``USER``SUPERAGENT` |
| `actor_id` | `VARCHAR(128)` | 操作人标识 |
| `action` | `VARCHAR(128)` | 操作类型 |
| `reason` | `VARCHAR(512)` | 操作原因 |
| `before_snapshot_json` | `LONGTEXT` | 变更前摘要 JSON |
| `after_snapshot_json` | `LONGTEXT` | 变更后摘要 JSON |
| `occurred_at` | `DATETIME(6)` | 发生时间 |
| `created_at` | `DATETIME(6)` | 记录创建时间 |
索引建议:
- 普通索引:`hotel_id + order_id + occurred_at`
- 普通索引:`hotel_id + task_id + occurred_at`
- 普通索引:`hotel_id + action + occurred_at`
审计不得保存 Secret、Token、完整邮件正文、真实附件 URL 或不必要的个人敏感信息。
## 11. OPERA 模拟逻辑操作表
表名:`workflow_reservation_opera_operation`
一条任务可能生成多条 OPERA 模拟逻辑操作。
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | OPERA 模拟操作 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `order_id` | `BIGINT` | 订单 ID |
| `task_id` | `BIGINT` | 任务 ID |
| `operation_type` | `VARCHAR(128)` | 操作类型,例如创建预订、更新字段、取消订单 |
| `operation_status` | `VARCHAR(32)` | 操作状态:`PENDING``EXECUTING``SUCCESS``FAILED` |
| `operation_order` | `INT` | 同任务下操作顺序 |
| `confirmed_payload_json` | `LONGTEXT` | 生成该操作时使用的确认 payload 快照 |
| `business_key_candidates_json` | `LONGTEXT` | 从模拟结果解析出的订单业务号候选 |
| `last_failure_reason` | `VARCHAR(512)` | 最近失败原因 |
| `retry_count` | `INT` | 已重试次数 |
| `created_at` / `updated_at` | `DATETIME(6)` | 创建和更新时间 |
索引建议:
- 普通索引:`hotel_id + task_id + operation_order`
- 普通索引:`hotel_id + operation_status + updated_at`
## 12. OPERA 模拟尝试记录表
表名:`workflow_reservation_opera_operation_attempt`
每次执行或重试都新增一条 attempt不能覆盖历史。
| 字段 | 类型建议 | 中文说明 |
| --- | --- | --- |
| `id` | `BIGINT` | 尝试记录 ID |
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
| `operation_id` | `BIGINT` | 所属逻辑操作 |
| `task_id` | `BIGINT` | 所属任务 |
| `attempt_no` | `INT` | 第几次尝试,从 1 开始 |
| `attempt_status` | `VARCHAR(32)` | 尝试状态:`SUCCESS``FAILED` |
| `request_payload_json` | `LONGTEXT` | 本次模拟请求 payload |
| `response_payload_json` | `LONGTEXT` | 本次模拟响应 payload |
| `business_key_candidates_json` | `LONGTEXT` | 本次响应中的业务号候选 |
| `failure_reason` | `VARCHAR(512)` | 失败原因 |
| `started_at` | `DATETIME(6)` | 开始时间 |
| `finished_at` | `DATETIME(6)` | 结束时间 |
| `created_at` | `DATETIME(6)` | 记录创建时间 |
索引建议:
- 唯一索引:`hotel_id + operation_id + attempt_no`
- 普通索引:`hotel_id + task_id + created_at`
规则:
- 用户不能跳过失败的 OPERA 模拟操作。
- 失败后任务进入 `FAILED`,只能通过修正 payload 后重试或重新执行使其成功。
- 不允许用户强制把失败任务改成 `COMPLETED`
- 后续真实 OPERA 接入时,应通过 adapter 把真实响应映射到 `response_payload_json``business_key_candidates_json`
## 13. 订单业务号回填
只有 `NEW_BOOKING``confirmed_payload_json` 没有可用业务号时,才从 OPERA 模拟成功结果回填订单业务号。
回填来源暂定为 `business_key_candidates_json`,示例:
```json
{
"key_type": "CONFIRMATION_NUMBER",
"business_key": "CNF123456",
"source": "OPERA_SIMULATION_RESULT",
"raw_path": "reserved.for.future.real.opera.path"
}
```
说明:
- 当前没有真实 OPERA 返回样例,`raw_path` 只能预留。
- FIT Reservation 回填 `CONFIRMATION_NUMBER`
- Group / Block / Allotment 回填 `GROUP_CODE` 或后续确认的对应业务号类型。
## 14. 第一版不落库的内容
以下内容第一版不建议单独建表:
- 任务卡字段矩阵配置:先写在代码 Provider 中。
- 任务阻塞状态:实时计算,不落 `BLOCKED`
- OPERA 真实接口字段映射:后续真实系统接入后在 adapter 层补充。
- 全量 Excel 字段路径:引用 `任务卡展示编辑矩阵.xlsx`,不手抄成数据库。
## 15. 待确认问题
- `ENDED` 是否仅表示取消完成,还是也包含正常完成后的历史订单。
- 临时订单逻辑删除前是否需要保留空订单一段时间。
- OPERA 真实响应字段出现后,是否需要新增稳定字段而不是只放 JSON。
- 任务卡字段代码 Provider 的具体类名和包路径,后续实现时按项目规范确认。

View File

@@ -0,0 +1,334 @@
# M002 Order Task Workflow 订单任务主流程 V1
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.1 |
| 日期 | 2026-07-06 |
| 状态 | 第一版流程草稿 |
| 适用范围 | SourceMessage 之后的 SuperAgent 抽取、订单挂靠、任务处理、OPERA/OHIP 模拟操作主流程 |
| 主要读者 | 产品、后端、前端、测试、后续协作 agent |
## 1. 文档定位
本文记录 TH Hotel 当前确认的订单任务主流程第一版理解,用于承接已经完成的
`SourceMessage Inbox` 能力,并为后续 PRD、接口契约、数据模型和实现 checkpoint 提供
统一业务语义。
本文只沉淀流程和边界,不代表已经开始实现代码。后续进入开发前,仍需要补充具体任务字段、
SuperAgent 创建任务接口契约、前端页面范围、数据库模型和验收标准。
## 2. 已确认结论
- AgentBus 是当前原始消息起点,后续也可能增加其他入口。
- AgentBus 只负责原始来源事实进入本系统,当前已经通过 `SourceMessage Inbox` 保存。
- SuperAgent 是黑盒抽取能力,本系统不关心它内部如何抽取。
- SuperAgent 不直连本系统数据库,只能通过本系统后端接口查询已有订单和任务上下文。
- SuperAgent 抽取成功后,通过调用本系统后端接口创建一个或多个任务。
- SuperAgent 告知的任务类型是本系统创建任务的依据,本系统暂时不做业务合法性判断。
- 本系统只做接口鉴权、基础 JSON 可解析性、幂等键、必要技术字段等技术校验。
- 任务必须挂靠到订单下,异常任务也需要先创建临时订单作为处理容器。
- 同一次 SuperAgent 调用如果返回多个任务,必须按返回 JSON 列表顺序确定任务执行顺序。
- 同一个订单下,前一个任务未完成时,后续任务只能查看,不能编辑、确认或执行模拟操作。
- 用户在任务详情页可以查看、修改字段内容,并确认订单归属。
- 用户确认字段和订单关系后,才能执行 OPERA/OHIP 模拟操作。
- 当前 OHIP/OPERA 不接真实接口,只做模拟操作。
- 所有任务类型转换、订单切换、临时订单逻辑删除都必须记录审计和原因。
## 3. 总体流程
```text
AgentBus / 未来其他入口
→ SourceMessage Inbox 记录原始来源事实
→ SuperAgent 读取或处理原始来源数据
→ SuperAgent 抽取结构化 JSON
→ SuperAgent 通过本系统后端接口查询已有订单和任务上下文
→ SuperAgent 调用本系统接口创建一个或多个任务
→ 本系统按任务类型和返回顺序创建任务并挂靠订单
→ 用户在任务详情页查看、修改字段、确认订单归属
→ 用户确认后执行 OPERA/OHIP 模拟操作
→ 任务完成后,同订单下后续任务解锁处理
```
中文说明:
- `SourceMessage Inbox` 是来源事实层,不表达订单、任务或业务结论。
- SuperAgent 负责抽取并告知任务类型,但不直接改变本系统最终业务状态。
- Order / Task 是本系统后续业务核心,本系统必须掌握任务顺序、可处理状态、审计和模拟操作结果。
## 4. 与 M001 SourceMessage 的关系
已完成的 M001 只覆盖以下能力:
```text
AgentBus 原始消息
→ SourceMessage Inbox 落库
→ 查询安全摘要
→ 受控读取原文
→ 记录原文读取审计
```
M001 不做以下事情:
- 不创建订单。
- 不创建任务。
- 不调用 SuperAgent。
- 不判断任务类型。
- 不执行 OPERA/OHIP 模拟操作。
- 不自动 ACK、回复客户或写业务结果。
因此,本文流程和当前 SourceMessage 代码没有冲突。SourceMessage 是后续订单任务流程的上游来源事实。
## 5. 核心角色与职责边界
| 模块或外部系统 | 职责 | 不负责 |
| --- | --- | --- |
| AgentBus | 接收邮件等外部渠道消息,并把原始 payload 推送给本系统 | 不做 AI 抽取,不创建任务,不调用业务写接口 |
| SourceMessage Inbox | 保存原始来源事实、幂等、查询安全摘要、受控读取原文 | 不表达订单归属,不表达任务类型,不表达业务合法性 |
| SuperAgent | 抽取结构化 JSON判断任务类型调用本系统后端接口创建任务 | 不直连数据库,不绕过本系统权限和审计,不直接执行 OPERA/OHIP |
| Order | 承载任务队列、订单号、临时订单状态和业务订单关系 | 不保存 SuperAgent 外部协议细节 |
| Task | 承载任务类型、抽取字段、用户确认字段、处理状态和执行顺序 | 不直接替代订单,不直接保存原始邮件全文 |
| Operation Simulation | 记录用户确认后的 OPERA/OHIP 模拟操作、结果和重试 | 当前不调用真实 OHIP/OPERA 接口 |
## 6. SuperAgent 任务创建边界
SuperAgent 创建任务时,本系统只做技术接收校验,不做业务合法性判断。
技术校验包括:
- 接口鉴权和调用方身份校验。
- 请求 JSON 可解析。
- 幂等键或批次标识存在。
- 任务列表结构可识别。
- 任务类型是本系统已经约定的稳定代码。
- 任务列表顺序可保留。
暂时不做的业务判断包括:
- 不判断任务是否应该是 `NEW_BOOKING`
- 不判断任务是否应该是 `UPDATE_BOOKING`
- 不判断任务是否应该是 `CANCEL_BOOKING`
- 不根据字段缺失自动改成异常任务。
- 不判断 `CANCEL_BOOKING` 前是否真的存在已完成的 `NEW_BOOKING`
- 不判断房型、日期、金额、订单号等字段是否业务合理。
如果 SuperAgent 告知是异常任务,本系统就按异常任务创建。如果 SuperAgent 告知是其他已知任务类型,本系统就按该类型创建。
## 7. 订单模型第一版理解
订单应至少区分内部身份、临时展示编号和真实业务订单号。
| 概念 | 中文说明 |
| --- | --- |
| `orderId` | 本系统内部订单 ID永远存在用于任务挂靠和内部引用 |
| `temporaryOrderCode` | 临时订单编号或前端展示编号,用于还没有明确真实订单号的场景 |
| `businessOrderNo` | 真实业务订单号,可能来自 SuperAgent 抽取结果、用户确认字段或后续模拟操作回填 |
| `orderStatus` | 订单状态,例如临时、有效、逻辑删除等 |
| `deletedReason` | 逻辑删除原因,主要用于异常任务转移后删除原临时订单 |
`NEW_BOOKING` 任务不一定都需要等待 OPERA 模拟创建后才获得真实订单号。系统应优先查看用户确认后的字段中是否已有订单号:
- 如果确认字段中已有订单号,则优先使用该订单号作为 `businessOrderNo`
- 如果确认字段中没有订单号,则先使用内部订单 ID 或临时编号追踪,后续如果模拟操作返回真实订单号再回填。
## 8. 任务模型第一版理解
任务至少需要保存 SuperAgent 原始抽取内容和用户确认后的内容。
| 概念 | 中文说明 |
| --- | --- |
| `taskId` | 本系统内部任务 ID |
| `orderId` | 当前挂靠订单 ID |
| `taskType` | 任务类型,由 SuperAgent 或后续人工转换确定 |
| `taskStatus` | 任务状态,用于控制是否可编辑、可确认、可执行 |
| `batchId` | SuperAgent 创建任务批次 ID 或调用 ID |
| `batchItemIndex` | 同一次 SuperAgent 返回列表中的顺序,从 0 或 1 开始需后续统一 |
| `orderSequence` | 订单下的任务执行顺序 |
| `extractedJson` | SuperAgent 原始抽取 JSON不应被用户编辑覆盖 |
| `confirmedJson` | 用户修改并确认后的 JSON用于后续模拟操作 |
| `sourceMessageId` | 关联 SourceMessage ID便于追溯原始邮件或来源消息 |
任务类型当前确认:
- `NEW_BOOKING`:新建订单任务。
- `UPDATE_BOOKING`:更新订单任务。后续可按字段组在前端展示不同卡片。
- `CANCEL_BOOKING`:取消订单任务。
- `EXCEPTION`:异常任务,表示 SuperAgent 无法判断属于前三类。
关于“任务大致 5 种”的表述,当前只确认以上 4 类。后续新增类型以具体任务分类和字段定义为准。
## 9. 任务顺序与可处理状态
订单下的任务必须按顺序处理。
排序规则第一版:
```text
orderId + orderSequence
```
同一次 SuperAgent 调用创建多个任务时:
- 必须保留 SuperAgent 返回 JSON 列表中的任务顺序。
- 同一个批次里,列表前面的任务优先执行。
- 不能只依赖创建时间排序,因为同批次任务时间可能完全一样。
后续任务的可处理规则:
- 当前面存在未完成任务时,后续任务只能查看。
- 后续任务不能编辑字段。
- 后续任务不能确认订单关系。
- 后续任务不能执行 OPERA/OHIP 模拟操作。
- 后续任务不能重试模拟操作。
任务切换订单后:
- 必须记录操作人、原订单、新订单、原因和时间。
- 任务进入目标订单队列后,需要按目标订单任务队列规则重新判断是否可处理。
- 如果目标订单前面已有未完成任务,该任务切过去后也只能查看。
## 10. Task Detail 行为
任务详情页不是只读确认页,而是用户处理任务的主要工作台。
任务可处理时,用户可以:
- 查看 SuperAgent 原始抽取结果。
- 查看关联 SourceMessage 的安全摘要或受控原文入口。
- 修改字段内容。
- 确认订单归属。
- 对异常任务选择转换目标类型。
- 输入转换、切换或删除原因。
- 确认后触发 OPERA/OHIP 模拟操作。
- 查看每条模拟操作结果。
- 对失败或允许重试的模拟操作发起重试。
任务不可处理时,用户只能查看:
- 任务基础信息。
- 当前挂靠订单。
- 抽取字段和已确认字段。
- 阻塞原因,例如“同订单下前置任务未完成”。
- 前置任务信息或跳转入口。
## 11. 异常任务处理流程
异常任务也必须挂靠订单。创建异常任务时,系统创建一个新的临时订单,并将异常任务挂靠到该临时订单。
```text
SuperAgent 创建 EXCEPTION 任务
→ 系统创建临时订单
→ 异常任务挂靠临时订单
→ 用户在任务详情页处理异常任务
→ 用户选择转为 NEW_BOOKING / UPDATE_BOOKING / CANCEL_BOOKING
→ 用户填写原因并确认
→ 系统写审计
```
### 11.1 转为 NEW_BOOKING
- 临时订单继续保留。
- 任务类型从 `EXCEPTION` 改为 `NEW_BOOKING`
- 用户可以修改并确认字段。
- 系统优先查看确认字段中的订单号是否有值。
- 如果订单号有值,则将该值作为真实业务订单号。
- 如果订单号无值,则继续使用临时编号追踪,后续模拟操作如返回真实订单号再回填。
- 必须记录任务类型变更审计和原因。
### 11.2 转为 UPDATE_BOOKING
- 用户选择一个已有订单。
- 任务类型从 `EXCEPTION` 改为 `UPDATE_BOOKING`
- 任务从临时订单切换到目标订单。
- 原临时订单逻辑删除。
- 必须记录任务类型变更、订单关系变更、临时订单逻辑删除审计和原因。
- 切换后必须受目标订单任务队列顺序约束。
### 11.3 转为 CANCEL_BOOKING
- 用户选择一个已有订单。
- 任务类型从 `EXCEPTION` 改为 `CANCEL_BOOKING`
- 任务从临时订单切换到目标订单。
- 原临时订单逻辑删除。
- 必须记录任务类型变更、订单关系变更、临时订单逻辑删除审计和原因。
- 切换后必须受目标订单任务队列顺序约束。
## 12. OPERA/OHIP 模拟操作
当前阶段没有真实 OHIP/OPERA 接口,只做模拟操作。
模拟操作第一版规则:
- 只有当前订单下可处理的任务才能执行模拟操作。
- 执行前必须完成字段修改和订单关系确认。
- 一个任务可能生成多条模拟操作。
- 任务详情页需要展示每条模拟操作的结果。
- 每条模拟操作应有独立状态、失败原因、重试次数和最近执行时间。
- 重试必须可追溯,不能覆盖历史执行结果。
- 任务是否完成,需要根据该任务要求的模拟操作是否全部达到完成条件判断。
模拟操作的具体类型、字段、完成条件和重试限制后续另行定义。
## 13. 审计要求
以下行为必须有审计:
- SuperAgent 创建任务批次。
- 同批次内每个任务的创建顺序。
- 用户修改任务字段。
- 用户确认订单关系。
- 任务类型转换。
- 任务切换订单。
- 临时订单逻辑删除。
- OPERA/OHIP 模拟操作执行。
- 模拟操作重试。
审计至少应包含:
| 字段 | 中文说明 |
| --- | --- |
| `actor` | 操作人或系统调用方 |
| `action` | 操作类型 |
| `reason` | 用户填写或系统记录的原因 |
| `beforeSnapshot` | 变更前摘要 |
| `afterSnapshot` | 变更后摘要 |
| `occurredAt` | 发生时间 |
审计中不得记录 Secret、Token、完整邮件正文、附件 URL 或不必要的个人敏感信息。
## 14. 暂不包含范围
第一版流程文档暂不定义:
- 具体 SuperAgent 创建任务接口 JSON Schema。
- 具体订单表、任务表、操作表结构。
- 具体前端页面布局。
- 具体任务字段和卡片展示规则。
- 真实 OHIP/OPERA 接口调用。
- SuperAgent 内部 prompt、skill 或抽取实现。
- 业务字段合法性判断规则。
- 任务类型之外的自动分类逻辑。
## 15. 后续建议 checkpoint
建议后续按以下顺序拆分,不一次性做完:
1. 定义 SuperAgent 调本系统创建任务接口契约。
2. 定义 Order / Task / Task Audit / Operation Simulation 的最小数据模型。
3. 实现任务入站创建、幂等、批次顺序和订单挂靠。
4. 实现任务详情读取、字段编辑和订单关系确认。
5. 实现异常任务转换、订单切换和临时订单逻辑删除。
6. 实现 OPERA/OHIP 模拟操作结果和重试机制。
7. 再根据前端页面范围实现列表、详情和队列状态展示。
## 16. 待确认问题
- SuperAgent 创建任务接口的正式 JSON 格式是什么。
- SuperAgent 任务批次 ID 和每个任务 item 的幂等键由谁生成。
- `batchItemIndex` 从 0 开始还是从 1 开始。
- 订单号字段在不同任务类型的 `confirmedJson` 中具体叫什么。
- 任务状态机需要哪些稳定状态。
- 模拟操作的完成条件和失败后是否允许跳过。
- 任务完成是否允许人工强制完成,若允许需要什么权限和审计。
- 第五种任务类型是否存在,若存在具体业务含义是什么。

View File

@@ -0,0 +1,500 @@
# M002 Order Task Workflow 订单任务主流程 V2
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.2 |
| 日期 | 2026-07-07 |
| 状态 | 第二版需求草稿 |
| 适用范围 | SourceMessage 之后的 AI 过渡层、订单挂靠、任务卡、人工确认、OPERA 模拟操作主流程 |
| 主要读者 | 产品、后端、前端、测试、后续协作 agent |
## 1. 文档定位
本文是 `M002-order-task-workflow-v1.md` 的第二版修正,目标是把本项目已经讨论确认的订单任务主流程,与 2026-07-06 导入的 AI 任务卡契约对齐。
本文只定义业务边界、数据语义和后续实现约束,不代表已经开始写代码。后续开发前仍需要拆分 checkpoint并补充接口契约、表结构、状态机、前端页面和测试验收标准。
## 2. 本版核心修正
相对 V1本版有以下修正
- 增加 `AI 过渡层`:系统必须先保存 AI 原始输出,再生成订单、任务和任务卡。
- 不再使用 `EXCEPTION` 作为底层结果类型AI 第一版结果类型统一以 `normal_task``manual_review``informational_message` 为准。
- 系统主任务类型收敛为 `NEW_BOOKING``UPDATE_BOOKING``CANCEL_BOOKING``MANUAL_REVIEW`,另保留 `INFORMATIONAL_MESSAGE` 作为不参与执行队列的只读提醒任务。
-`New Booking``Cancel Booking``Fallback/manual_review` 外,其他业务处理类卡片原则上都归到 `UPDATE_BOOKING` 下的不同任务卡。
- `Message Notification` 也挂到临时订单下面,便于归档和详情查看,但不参与订单任务执行队列,不阻塞其他任务,也不被其他任务阻塞。
- 任务卡字段以 `任务卡展示编辑矩阵.xlsx` 为权威来源,不在代码里随意扩展或重命名字段路径。
- 系统必须同时保存 AI 原始 `task_type`、系统主任务类型、任务卡类型和业务动作 subtype。
- `ai_payload_json``confirmed_payload_json` 必须分离。OPERA 模拟只能读取用户确认后的 `confirmed_payload_json`
- 订单业务号候选字段和 OPERA 模拟回填边界需要明确记录,但 OPERA 返回 JSON 字段名目前未知,不能写死。
## 3. 权威输入资料
本版需求参考以下资料:
| 资料 | 作用 |
| --- | --- |
| `docs/project/requirements/M001-source-message-inbox-prd.md` | 上游 SourceMessage Inbox 边界 |
| `docs/project/requirements/M002-order-task-workflow-v1.md` | 第一版整体业务流程 |
| `docs/import/20260706/开发AI先读_工作顺序.md` | AI 过渡表、任务卡、确认 payload 和 OPERA 写入工作顺序 |
| `docs/import/20260706/AI输出参数并集字典.xlsx` | AI 输出字段路径、字段含义、建议存储方式和索引建议 |
| `docs/import/20260706/任务卡展示编辑矩阵.xlsx` | 任务卡字段展示、编辑、校验和 OPERA 写入约束 |
| `docs/import/20260706/0630AI_副本 2/01_main_agent_prompt.md` | Main Agent 输出职责和聚合 JSON 结构 |
| `docs/import/20260706/0630AI_副本 2/02_agent_workflow.md` | AI 侧事件拆分、Skill 调用和结果聚合流程 |
| `docs/import/20260706/0630AI_副本 2/03_skill_catalog.md` | Skill 目录、结果类型、任务类型和统一输出字段 |
| `docs/import/20260706/0630AI_副本 2/skill/S01-S08*.md` | 各业务任务卡的抽取规则和边界 |
| `docs/import/20260706/0630AI_副本 2/references/qbd_liantai_email_table_rules.md` | QBD / LianTai 表格邮件拆分和证据规则 |
| `docs/import/20260706/0630AI_副本 2/skill/*/references/rate_code_rules.md` | Rate Code 判断规则 |
| `docs/import/20260706/0630AI_副本 2/skill/*/references/room_type_mapping_rules.md` | 房型映射规则 |
说明:`开发AI先读_工作顺序.md` 中提到的 `AI过渡表开发契约_README.md``AI过渡表开发契约_两张样例表.xlsx` 在当前导入目录下未找到同名文件。当前以已经导入的两张独立 Excel 作为字段和任务卡矩阵来源。
## 4. 总体流程
```text
AgentBus / 未来其他入口
→ SourceMessage Inbox 记录原始来源事实
→ SuperAgent / Main Agent 读取 SourceMessage 并拆分 current 事件
→ 对每个事件调用对应 Skill
→ 聚合输出 source_message_id + ai_task_results[] + extraction_warnings[]
→ 本系统接收 AI 结果并写入 AI 过渡层
→ 本系统根据 result_type + task_type + task_subtype 生成订单、任务和任务卡
→ 用户在任务详情页查看、修改字段、确认订单归属
→ 系统生成 confirmed_payload_json
→ 用户触发 OPERA 模拟操作
→ 系统保存每条模拟操作结果、失败原因和重试记录
```
中文说明:
- `SourceMessage Inbox` 是来源事实层,不表达订单、任务或业务结论。
- AI 只输出 JSON不直接写数据库、不直接创建真实任务卡、不直接写 OPERA。
- 本系统负责保存 AI 原始 JSON、创建任务卡、接收用户修改、生成确认后的 payload、执行 OPERA 模拟并记录结果。
- SuperAgent 不直连本系统数据库,只能通过本系统后端接口查询已有订单和任务上下文。
## 5. AI 输出接收边界
导入文档已经定义了 SuperAgent / Main Agent 应输出的核心业务结构,但还没有定义完整 HTTP 接口契约。
AI 聚合输出的顶层结构应包含:
| 字段 | 中文说明 |
| --- | --- |
| `source_message_id` | 关联的 SourceMessage ID |
| `ai_task_results[]` | AI 拆分出的一个或多个任务结果,顺序必须保留 |
| `extraction_warnings[]` | 抽取警告,不直接等同于业务任务 |
`ai_task_results[]` 中关键字段包括:
| 字段 | 中文说明 |
| --- | --- |
| `source_event_index` | AI 拆分出的 current 事件序号,同一次来源消息内用于排序 |
| `catalog_code` | Skill 目录代码,例如 S01、S02 |
| `skill_id` | 具体 Skill 标识 |
| `result_type` | AI 结果类型,第一版只使用 `normal_task``manual_review``informational_message` |
| `task_type` | AI 原始任务类型,例如 `New Booking``Voucher Received` |
| `task_subtype` / 业务动作 | 更细的业务动作,用于任务卡字段路由 |
| `current_or_history` | 当前事件还是历史证据 |
| `case_keys` | 订单关联候选键,例如 `group_code``confirmation_number` |
| `visible_reason` | 给用户看的生成原因 |
| `relevant_message_excerpt` | 相关邮件片段 |
| `attachments` / `file_references` | 附件和文件引用 |
| `context_used` | AI 使用的上下文摘要 |
| `extracted_fields` | 业务字段主体 |
| `manual_review` | 人工复核结构化原因和证据 |
| `informational_message` | 信息提醒内容 |
| `additional_operations` | AI 建议的附加动作 |
| `idempotency_key` | 幂等键,最终来源和生成规则待确认 |
待补充的 HTTP 契约包括 URL、Method、Header、鉴权、幂等策略、错误响应格式、是否允许重复推送同一个 `source_message_id`
## 6. AI 过渡层数据要求
系统接收 AI 输出后,应先写入 AI 过渡层,再创建任务卡。过渡层用于追溯、幂等、筛选、人工确认和 OPERA 参数组装。
推荐保存完整 JSON
| JSON 字段 | 中文说明 |
| --- | --- |
| `ai_payload_json` | AI 原始结果,禁止被用户编辑覆盖 |
| `case_keys_json` | AI 给出的订单关联候选键 |
| `extracted_fields_json` | AI 抽取的业务字段 |
| `manual_review_json` | 人工复核信息 |
| `informational_message_json` | 信息提醒内容 |
| `attachments_json` | 附件和文件引用 |
| `context_used_json` | AI 使用的上下文 |
| `confirmed_payload_json` | 用户修改并确认后的最终参数 |
推荐冗余物理列:
| 物理列 | 中文说明 |
| --- | --- |
| `source_message_id` | 来源消息 ID |
| `source_event_index` | AI 事件序号 |
| `catalog_code` | Skill 目录代码 |
| `skill_id` | Skill 标识 |
| `result_type` | AI 结果类型 |
| `ai_task_type` | AI 原始任务类型 |
| `system_task_type` | 系统主任务类型 |
| `task_card_type` | 任务卡类型 |
| `task_subtype` | 业务动作 subtype |
| `current_or_history` | 当前或历史标识 |
| `group_code` | 冗余的 Group Code 候选 |
| `confirmation_number` | 冗余的 Confirmation Number 候选 |
| `idempotency_key` | 幂等键 |
| `manual_reason_code` | 人工复核原因码 |
| `parent_source_event_index` | 父任务事件序号 |
| `linked_task_group_id` | 联动任务组 ID |
| `blocked_until_parent_completed` | 是否等待父任务完成 |
| `execution_order` | 同订单下执行顺序 |
| `created_at` / `updated_at` | 创建和更新时间 |
不要把所有嵌套字段都建成物理列。只有高频查询、筛选、幂等、排序、队列控制和状态机需要的字段才冗余。
## 7. 任务分类模型
### 7.1 AI 结果类型
AI 第一版结果类型只接受:
| `result_type` | 中文说明 | 是否可直接 OPERA 模拟 |
| --- | --- | --- |
| `normal_task` | 可生成业务任务卡的正常任务 | 需用户确认后才允许 |
| `manual_review` | 需要人工复核的结构化任务 | 不允许直接写 OPERA |
| `informational_message` | 只读信息提醒 | 不允许写 OPERA |
不使用 `exception_task``no_action`。原来讨论中的异常任务,在本版统一落为 `manual_review`;没有业务动作的信息提醒,统一落为 `informational_message`
### 7.2 系统主任务类型
系统内部主任务类型建议为:
| 系统主任务类型 | 中文说明 |
| --- | --- |
| `NEW_BOOKING` | 新建订单任务 |
| `UPDATE_BOOKING` | 更新订单任务,下面承载多个不同任务卡 |
| `CANCEL_BOOKING` | 取消订单任务 |
| `MANUAL_REVIEW` | 人工复核或 Fallback 任务 |
| `INFORMATIONAL_MESSAGE` | 信息提醒任务,只归档展示,不参与执行队列 |
### 7.3 AI 任务类型到系统任务和卡片的映射
| AI `task_type` | 系统主任务类型 | 任务卡类型 | 说明 |
| --- | --- | --- | --- |
| `New Booking` | `NEW_BOOKING` | `NEW_BOOKING` | 新建 FIT、Group Block、Allotment / Control Block |
| `Update Booking` | `UPDATE_BOOKING` | `UPDATE_BOOKING` | 通用订单修改卡 |
| `Voucher Received` | `UPDATE_BOOKING` | `VOUCHER_RECEIVED` | 凭证类更新卡 |
| `Rooming List` | `UPDATE_BOOKING` | `ROOMING_LIST` | 名单/分房表处理卡 |
| `Amend Group Code` | `UPDATE_BOOKING` | `AMEND_GROUP_CODE` | 修改 Group Code 卡 |
| `Trace / Reservation Notes` | `UPDATE_BOOKING` | `TRACE_RESERVATION_NOTES` | Trace 或备注卡,可作为联动任务 |
| `TA Recorder` | `UPDATE_BOOKING` | `TA_RECORDER` | TA Recorder 维护卡,通常由 Rooming List 派生 |
| `Cancel Booking` | `CANCEL_BOOKING` | `CANCEL_BOOKING` | 取消 FIT、Group Block、Allotment / Control Block |
| `Message Notification` | `INFORMATIONAL_MESSAGE` | `MESSAGE_NOTIFICATION` | 挂临时订单,只读展示,不参与执行队列 |
| `Fallback` | `MANUAL_REVIEW` | `FALLBACK_REVIEW` | 人工复核任务 |
系统不得丢弃 AI 原始 `task_type`。任务落库时应同时保存 AI 原始任务类型、系统主任务类型、任务卡类型和业务动作 subtype。
## 8. Message Notification 处理规则
`Message Notification` 用于感谢、知会、已收到、转发说明、信息同步等没有明确业务执行动作的消息。
本系统处理规则:
- 创建临时订单作为归档容器。
- 创建只读信息提醒任务卡。
- 不参与订单任务执行队列。
- 不阻塞同订单其他可执行任务。
- 不被同订单其他可执行任务阻塞。
- 不允许编辑业务字段。
- 不允许确认后执行 OPERA 模拟。
- 可在详情页展示 `visible_reason``relevant_message_excerpt``attachments``informational_message`
## 9. 任务卡字段约束
任务卡字段以 `docs/import/20260706/任务卡展示编辑矩阵.xlsx` 为权威来源。系统不得直接把整个 `ai_task_results[]` 渲染成表单,也不得绕过矩阵自行扩展字段含义。
字段矩阵通过以下列控制任务详情页行为:
| 列 | 中文说明 |
| --- | --- |
| `任务卡名称` | 前端任务卡名称 |
| `result_type` | 适用的 AI 结果类型 |
| `task_type` | 适用的 AI 原始任务类型 |
| `task_subtype/业务动作` | 适用的业务动作 |
| `字段路径` | 从 AI JSON 或确认 JSON 中取值的路径 |
| `展示名` | 前端展示名称 |
| `是否展示` | 是否在任务卡展示 |
| `是否可编辑` | 用户是否可编辑 |
| `是否为输入方式编辑` | 是否普通输入 |
| `是否为下拉框方式编辑` | 是否下拉选择 |
| `是否日期选择` | 是否日期控件 |
| `是否数字输入` | 是否数字输入 |
| `是否文件展示` | 是否文件或附件展示 |
| `是否表格编辑` | 是否表格型编辑 |
| `下拉选项/枚举值` | 可选值,系统不得自行扩展 |
| `是否必填` | 用户确认前是否必填 |
| `展示条件` | 字段显示条件 |
| `校验规则` | 用户确认前校验规则 |
| `确认后写入位置` | 写入 `confirmed_payload_json` 的位置 |
| `是否参与Opera写入` | 是否允许进入 OPERA 参数 |
| `Opera API参数映射` | OPERA 参数映射意图 |
### 9.1 关键卡片字段摘要
本节只摘录核心字段,完整字段以 Excel 矩阵为准。
| 任务卡 | 关键字段 |
| --- | --- |
| New Booking | `case_keys.group_code``case_keys.confirmation_number`、入住/离店日期、房量、房型原文、Opera 房型代码、Rate Code、结算价、Fix Charge、子团房量、表格行证据 |
| Update Booking | `case_keys.group_code``case_keys.confirmation_number`、修改类型、修改前/后、Rate / 结算价结果、Fix Charge、表格行证据 |
| Cancel Booking | `case_keys.group_code``case_keys.confirmation_number`、取消对象类型、取消范围、取消生效日期、取消备注 |
| Voucher Received | 原始凭证、凭证类型、`case_keys.group_code`、确认后动作、凭证展示字段、部门流转、关联 Group Code |
| Rooming List | 名单文件、名单类型、目标 Key、文件类型判断、附件 / Sheet 绑定、后续动作意图、派生任务候选 |
| Amend Group Code | 旧 Group Code、新 Group Code |
| Trace / Reservation Notes | `case_keys.group_code``case_keys.confirmation_number`、Trace 类型、通知部门、Trace Text、入住人数更新、改价公式、父任务事件序号 |
| TA Recorder | Group Code、名单 / 分房附件、TA Recorder 状态、Rooming List 父任务 |
| Message Notification | 生成原因、邮件片段、附件、信息提醒内容 |
| Fallback / manual_review | 复核原因码、复核说明、复核记录类型、阻断点、缺失字段、冲突点、建议核对证据、建议人工动作、候选 Group Code、候选 Confirmation No. |
## 10. AI 原始值和人工确认值
AI 原始输出和用户确认值必须分离。
| 概念 | 中文说明 |
| --- | --- |
| `ai_payload_json` | AI 原始输出,作为证据和审计,不被用户编辑覆盖 |
| `confirmed_payload_json` | 用户修改并确认后的最终业务参数 |
系统规则:
- 任务详情首次展示可从 `ai_payload_json` 按矩阵字段路径取值。
- 用户编辑后必须写入 `confirmed_payload_json`
- 用户确认前必须按矩阵和后端硬校验检查必填、类型、枚举和执行条件。
- OPERA 模拟只能从 `confirmed_payload_json` 取值。
- 不允许直接从 `ai_payload_json` 组装 OPERA 参数。
- 不允许因为用户修改而覆盖 AI 原始输出。
## 11. 订单业务号和临时订单
系统不应只用一个含义模糊的“订单号”。建议至少区分:
| 字段 | 中文说明 |
| --- | --- |
| `order_id` | 系统内部订单 ID永远存在 |
| `order_key_type` | 业务号类型,例如 `GROUP_CODE``CONFIRMATION_NUMBER``TEMPORARY` |
| `order_business_key` | 真实业务号,例如 Group Code 或 Confirmation Number |
| `temporary_order_code` | 临时订单展示编号 |
### 11.1 可作为订单业务号候选的字段
订单业务号候选字段包括:
| 字段路径 | 适用场景 | 中文说明 |
| --- | --- | --- |
| `case_keys.group_code` | Group / Block / Allotment 类任务 | 主要 Group Code 候选 |
| `case_keys.confirmation_number` | FIT Reservation 类任务 | 主要 Confirmation Number 候选 |
| `extracted_fields.old_group_code` | Amend Group Code | 定位原订单 |
| `extracted_fields.new_group_code` | Amend Group Code | 修改成功后的新业务号候选 |
| `extracted_fields.group_code` | TA Recorder 等卡片 | 任务卡内 Group Code 候选 |
| `extracted_fields.target_key` | Rooming List | 目标 Key需要判断实际是 Group Code 还是 Confirmation Number |
| `related_group_codes[]` | Voucher 等场景 | 关联展示或候选,不应自动作为唯一主订单号 |
系统匹配订单时应优先使用用户确认后的 `confirmed_payload_json`;如果尚未确认,只能把 AI 字段作为候选和展示依据。
### 11.2 New Booking 的订单号处理
`NEW_BOOKING` 不一定都需要等待 OPERA 模拟创建后才有真实业务号。
规则:
- 如果用户确认后的 payload 已有可用业务号,优先使用该业务号。
- FIT Reservation 优先看 `confirmation_number`
- Group / Block / Allotment 优先看 `group_code`
- 如果确认后的 payload 没有可用业务号,先创建临时订单并使用 `temporary_order_code` 展示和追踪。
- OPERA 模拟创建成功后,如果结果中返回真实业务号,再回填订单业务号。
- 第一版尚未实现确认 payload 时Fallback 转 `NEW_BOOKING` 可先读取 AI 原始 `case_keys.group_code` / `case_keys.confirmation_number` 作为 `AI_CANDIDATE`,用于把原临时订单激活为 ACTIVE后续用户确认字段时再把来源升级为 `USER_CONFIRMED`
### 11.3 什么时候从 OPERA 模拟结果回填订单号
只有在 `NEW_BOOKING` 且确认后的 payload 没有可用业务号时,才需要从 OPERA 模拟结果回填订单号。
| 任务场景 | 回填业务含义 |
| --- | --- |
| New FIT Reservation | 从 OPERA 模拟结果中的 Confirmation No. / Reservation Confirmation Number 含义字段回填 |
| New Group Block | 从 OPERA 模拟结果中的 Group Code / Block Code 含义字段回填 |
| New Allotment / Control Block | 从 OPERA 模拟结果中的 Group Code / Block Code / Allotment Code 含义字段回填 |
当前导入文档没有定义 OPERA 模拟结果的 JSON 字段名,因此不能在需求中写死 `result.confirmationNumber``result.groupCode` 等具体路径。该字段名需要在 OPERA 模拟模块设计时另行确认。
### 11.4 非 New Booking 的订单号规则
`UPDATE_BOOKING``CANCEL_BOOKING``Voucher Received``Rooming List``Trace / Reservation Notes``TA Recorder` 等任务,原则上不应依赖 OPERA 模拟结果生成订单号。
这些任务应先通过 `case_keys` 或对应卡片字段挂靠已有订单;如果无法自动归属,则创建或使用临时订单,并等待人工确认订单关系。
## 12. 订单挂靠和临时订单
任务必须挂靠到订单下,便于详情查看、队列控制和审计。
| 场景 | 挂靠规则 |
| --- | --- |
| New Booking 有业务号 | 按确认后的业务号创建或匹配订单 |
| New Booking 无业务号 | 创建临时订单 |
| Update Booking 可匹配已有订单 | 挂靠已有订单 |
| Update Booking 无法匹配订单 | 挂靠临时订单,等待人工确认 |
| Cancel Booking 可匹配已有订单 | 挂靠已有订单 |
| Cancel Booking 无法匹配订单 | 挂靠临时订单,等待人工确认 |
| Fallback / manual_review | 先挂靠临时订单,人工处理时可转换类型和订单归属 |
| Message Notification | 挂靠临时订单,只读归档 |
任务从临时订单迁移到已有订单时,必须记录审计。若原临时订单已无有效任务,应逻辑删除,并记录删除原因。
## 13. 任务顺序和可处理状态
同一个订单下,任务必须按顺序处理。
排序依据:
```text
order_id + execution_order
```
同一次 AI 返回多个任务时:
- 必须保留 `ai_task_results[]` 列表顺序。
- 可使用 `source_event_index``execution_order` 或批次内 item index 保存顺序。
- 不得只依赖创建时间,因为同批次任务创建时间可能完全一致。
处理规则:
- 前置任务未完成时,后续任务只能查看。
- `FAILED` 视为当前前置任务已经结束,不阻塞后续任务;用户不能跳过失败的 OPERA 操作,但失败任务本身不再卡住后续任务查看和处理顺序。
- 后续任务不能编辑字段。
- 后续任务不能确认订单关系。
- 后续任务不能执行 OPERA 模拟。
- 后续任务不能重试 OPERA 模拟。
- `Message Notification` 不参与执行队列,不阻塞其他任务,也不被其他任务阻塞。
- 联动任务可通过 `parent_source_event_index``linked_task_group_id``blocked_until_parent_completed` 表达依赖。
## 14. 人工复核和 Fallback
`manual_review` 是结构化人工复核,不是空结果,也不是系统失败。
人工复核卡至少应展示:
- `manual_review.reason_code`
- `manual_review.visible_reason`
- `manual_review.review_record_type`
- `manual_review.blocking_points[]`
- `manual_review.missing_fields[]`
- `manual_review.conflicting_points[]`
- `manual_review.evidence_to_check[]`
- `manual_review.suggested_human_actions[]`
Fallback 处理规则:
- 创建 `MANUAL_REVIEW` 任务。
- 默认挂靠临时订单。
- 用户处理时可以选择转为 `NEW_BOOKING``UPDATE_BOOKING``CANCEL_BOOKING`
- 转换时必须记录审计;原因字段第一版允许为空。
- 转为 `NEW_BOOKING` 时,临时订单可以继续保留;如果已有确认 payload 则优先使用确认业务号,第一版尚未实现确认 payload 时可先使用 AI 原始 `case_keys` 中的业务号候选。
- 转为 `UPDATE_BOOKING``CANCEL_BOOKING` 时,用户必须选择目标订单;任务迁移后原临时订单无有效任务时可逻辑删除。
- 类型转换、订单迁移和临时订单逻辑删除都必须记录审计。
## 15. OPERA 模拟操作
当前阶段没有真实 OHIP / OPERA 接口,只做 OPERA 模拟操作。
允许进入 OPERA 模拟的前置条件:
- `result_type = normal_task`
- 当前任务是订单执行队列中可处理的任务。
- 用户已确认订单归属。
- 用户已确认字段,且系统已生成 `confirmed_payload_json`
- 字段矩阵中 `是否参与Opera写入``是` 或满足 `条件参与`
- 后端硬校验通过。
不允许进入 OPERA 模拟的场景:
- `manual_review`
- `informational_message`
- 未确认的 AI 原始值。
- 证据字段、只读字段、历史字段、uncertain 字段。
- S04 的 `voucher_display_fields` 等展示字段。
- 字段矩阵明确 `是否参与Opera写入=否` 的字段。
一个任务可能产生多条 OPERA 模拟操作。任务详情页应展示每条模拟操作的结果、状态、失败原因、重试次数和最近执行时间。重试必须保留历史记录,不能覆盖原始失败记录。
## 16. 审计要求
以下行为必须记录审计:
- AI 结果接收批次。
- `ai_task_results[]` 中每个 item 的创建顺序。
- AI 原始 JSON 保存。
- 订单自动挂靠或临时订单创建。
- 用户修改任务字段。
- 用户确认订单关系。
- `confirmed_payload_json` 生成。
- 任务类型转换。
- 任务切换订单。
- 临时订单逻辑删除。
- OPERA 模拟操作执行。
- OPERA 模拟操作重试。
- 从 OPERA 模拟结果回填订单业务号。
审计至少包含:
| 字段 | 中文说明 |
| --- | --- |
| `actor` | 操作人或系统调用方 |
| `action` | 操作类型 |
| `reason` | 用户填写或系统记录的原因 |
| `before_snapshot` | 变更前摘要 |
| `after_snapshot` | 变更后摘要 |
| `occurred_at` | 发生时间 |
审计中不得记录 Secret、Token、完整邮件正文、真实附件 URL 或不必要的个人敏感信息。
## 17. 暂不包含范围
本版暂不定义:
- SuperAgent 调本系统的正式 HTTP 接口契约。
- 幂等键到底由 SuperAgent 生成还是本系统生成。
- 订单完整状态机。
- 任务完整状态机。
- OPERA 模拟结果 JSON 字段名。
- 真实 OHIP / OPERA 接口地址、鉴权和返回结构。
- 前端具体页面布局和交互细节。
- 全量 158 条任务卡字段配置复制版。
- Rate Code 和房型规则的代码实现。
## 18. 待确认问题
- SuperAgent 创建任务接口的 URL、Method、Header、鉴权和错误响应格式。
- SuperAgent 查询上下文接口暂不在本 checkpoint 实现,但 SuperAgent 侧已经在整理,后续梳理未完成事项时必须持续提醒。
- `source_event_index`、批次 item index、`execution_order` 的最终编号规则是否都从 1 开始。
- `idempotency_key` 的来源和冲突处理策略。
- 订单状态机有哪些稳定状态。
- 任务状态机有哪些稳定状态。
- `Message Notification` 是否需要在前端订单列表上单独标识为只读提醒。
- OPERA 模拟结果中 Confirmation No.、Group Code、Block Code、Allotment Code 的具体字段路径。
- 临时订单在无任务后是否立即逻辑删除,还是保留一段时间便于追溯。
- 用户是否允许强制完成任务;如果允许,需要什么权限和审计原因。
## 19. 后续建议 checkpoint
建议后续按以下顺序拆分:
1. 定义 SuperAgent 调本系统的任务结果接收接口契约。
2. 定义 AI 过渡层、订单、任务、任务卡、审计、OPERA 模拟结果的最小数据模型。
3. 定义系统主任务类型、任务卡类型、`result_type`、任务状态和订单状态枚举。
4. 实现 AI 结果接收、幂等、顺序保存和任务卡创建。
5. 实现订单自动挂靠、临时订单创建和人工订单切换。
6. 实现任务详情字段展示、编辑、确认和 `confirmed_payload_json`
7. 实现任务队列阻塞规则和 `Message Notification` 只读归档规则。
8. 实现 OPERA 模拟操作结果底表、重试和订单业务号回填。
9. 根据前端页面范围实现列表、详情和只读/可处理状态展示。

View File

@@ -0,0 +1,378 @@
# M002 SuperAgent Task Result API Contract
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.1 |
| 日期 | 2026-07-07 |
| 状态 | 后端接口契约草稿 |
| 适用范围 | SuperAgent / Main Agent 调用本系统提交 AI 任务结果 |
| 主要读者 | 后端、SuperAgent 对接方、测试、后续协作 agent |
## 1. 文档定位
本文定义 SuperAgent / Main Agent 向本系统提交 `ai_task_results[]` 的第一版后端接口契约。
本文承接 `M002-order-task-workflow-v2.md`,只定义本系统入站接口、鉴权、幂等、请求响应和技术校验边界,不定义 SuperAgent 内部 prompt、Skill 实现、OPERA 真实接口或前端展示细节。
## 2. 接口概览
| 项目 | 内容 |
| --- | --- |
| Method | `POST` |
| Path | `/api/integrations/superagent/task-results` |
| Content-Type | `application/json` |
| 响应格式 | `application/json` |
| 一次请求范围 | 只能包含一个 `source_message_id` |
| 业务动作 | 接收 AI 结果、写入 AI 过渡层、生成订单 / 任务 / 任务卡 |
| 鉴权方式 | HMAC-SHA256 签名 |
中文说明:
- 该接口是服务到服务的入站接口,不给前端直接调用。
- SuperAgent 不直连数据库,只能通过本接口提交任务结果。
- 本接口只做技术校验和系统接收,不替代用户确认和 OPERA 模拟操作。
## 3. 鉴权方案
第一版使用 HMAC-SHA256 签名,做到简单、可实现、可防重放。
### 3.1 环境变量
| 环境变量 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `SUPERAGENT_TASK_RESULT_HMAC_SECRET` | 是 | SuperAgent 任务结果入站签名密钥,只能通过 Secret 注入 |
| `SUPERAGENT_TASK_RESULT_CLOCK_SKEW_SECONDS` | 否 | 请求时间允许偏移,默认 `300` 秒 |
| `SUPERAGENT_TASK_RESULT_NONCE_TTL_SECONDS` | 否 | Nonce 去重窗口,默认 `600` 秒 |
| `SUPERAGENT_TASK_RESULT_MAX_BODY_BYTES` | 否 | 请求体最大字节数,默认 `1048576` |
如果 `SUPERAGENT_TASK_RESULT_HMAC_SECRET` 为空,生产环境应拒绝接口调用。
### 3.2 请求 Header
| Header | 是否必填 | 中文说明 |
| --- | --- | --- |
| `X-TH-Hotel-SuperAgent-Client-Id` | 是 | 调用方客户端 ID用于区分不同 SuperAgent 调用方 |
| `X-TH-Hotel-SuperAgent-Timestamp` | 是 | UTC 时间ISO-8601 格式,例如 `2026-07-07T08:30:00Z` |
| `X-TH-Hotel-SuperAgent-Nonce` | 是 | 每次请求唯一随机值,用于防重放 |
| `X-TH-Hotel-SuperAgent-Signature` | 是 | HMAC 签名,格式 `sha256=<lowercase-hex>` |
| `X-TH-Hotel-Request-Id` | 否 | 调用方请求 ID用于排查和日志串联 |
### 3.3 签名串
签名使用原始请求体字节计算 SHA-256再参与 HMAC。
规范签名串:
```text
POST
/api/integrations/superagent/task-results
<X-TH-Hotel-SuperAgent-Timestamp>
<X-TH-Hotel-SuperAgent-Nonce>
<X-TH-Hotel-SuperAgent-Client-Id>
<lowercase-hex-sha256-of-raw-body>
```
签名算法:
```text
signature = HMAC_SHA256(SUPERAGENT_TASK_RESULT_HMAC_SECRET, canonical_string)
```
Header 写法:
```text
X-TH-Hotel-SuperAgent-Signature: sha256=<lowercase-hex-signature>
```
### 3.4 服务端校验
服务端必须按顺序完成以下校验:
1. 校验必要 Header 是否存在。
2. 校验 timestamp 可解析且在允许时间窗口内。
3. 校验同一个 `client_id + nonce` 在 TTL 窗口内没有被使用过。
4. 计算原始请求体 SHA-256。
5. 使用 HMAC secret 重新计算签名。
6. 使用常量时间比较签名。
7. 鉴权通过后再解析 JSON。
日志和错误响应不得输出 secret、签名原文、完整请求体、邮件正文、附件 URL 或个人敏感信息。
## 4. 请求体
请求体沿用 AI 导入文档定义的聚合结构。
```json
{
"source_message_id": "1900000000000000001",
"ai_task_results": [
{
"source_event_index": 1,
"catalog_code": "S01",
"skill_id": "S01_new_booking_skill",
"result_type": "normal_task",
"task_type": "New Booking",
"task_subtype": "new_fit_reservation",
"current_or_history": "current",
"case_keys": {
"group_code": null,
"confirmation_number": null
},
"visible_reason": "邮件正文包含新建预订请求。",
"relevant_message_excerpt": "Please create a new booking...",
"attachments": [],
"file_references": [],
"context_used": {},
"extracted_fields": {},
"manual_review": null,
"informational_message": null,
"additional_operations": [],
"idempotency_key": null
}
],
"extraction_warnings": []
}
```
### 4.1 顶层字段
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `source_message_id` | 是 | 关联本系统 SourceMessage ID一次请求只能有一个 |
| `ai_task_results[]` | 是 | AI 拆分出的任务结果列表,必须保留数组顺序 |
| `extraction_warnings[]` | 否 | 抽取警告;不直接等同于业务任务 |
第一版要求 `ai_task_results[]` 至少包含一条记录。没有业务动作时应由 AI 输出 `Message Notification``Fallback/manual_review`,而不是提交空数组。
### 4.2 `ai_task_results[]` 字段
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `source_event_index` | 是 | AI current 事件序号,建议从 1 开始 |
| `catalog_code` | 是 | Skill 目录代码,例如 S01、S02 |
| `skill_id` | 是 | Skill 标识 |
| `result_type` | 是 | 只接受 `normal_task``manual_review``informational_message` |
| `task_type` | 是 | AI 原始任务类型 |
| `task_subtype` | 否 | 业务动作 subtype有则用于任务卡路由 |
| `current_or_history` | 否 | 当前或历史标识 |
| `case_keys` | 否 | 订单关联候选键 |
| `visible_reason` | 否 | 给用户看的生成原因 |
| `relevant_message_excerpt` | 否 | 相关邮件片段,注意不要超长 |
| `attachments` / `file_references` | 否 | 附件和文件引用 |
| `context_used` | 否 | AI 使用的上下文 |
| `extracted_fields` | 否 | 业务字段主体 |
| `manual_review` | 条件必填 | `result_type=manual_review` 时应提供 |
| `informational_message` | 条件必填 | `result_type=informational_message` 时应提供 |
| `additional_operations` | 否 | 附加动作建议 |
| `idempotency_key` | 否 | 可忽略;本系统第一版自行生成幂等键 |
## 5. 技术校验边界
本接口只做技术校验,不做业务合法性判断。
### 5.1 必须校验
- 鉴权签名合法。
- 请求体大小不超过限制。
- JSON 可解析。
- 顶层只有一个 `source_message_id`
- `source_message_id` 对应的 SourceMessage 存在。
- `ai_task_results[]` 是非空数组。
- `result_type` 属于允许值。
- `task_type` 属于当前系统可识别的稳定值或可进入 Fallback 处理。
- 同一个请求内 `source_event_index` 和数组顺序可保存。
- 关键字符串长度不超过数据库限制。
### 5.2 不在本接口判断
- 不判断 AI 任务类型是否业务正确。
- 不判断房型、价格、日期、Rate Code 是否合理。
- 不判断 Cancel Booking 前是否已经有 New Booking。
- 不因为字段缺失自动改成 Fallback。
- 不直接执行 OPERA 模拟。
- 不直接把 AI 原始值写入 OPERA 参数。
## 6. 幂等设计
`idempotency_key` 第一版由系统生成,不依赖 SuperAgent 传值。
### 6.1 请求哈希
系统必须保存原始请求体的 SHA-256
```text
request_payload_sha256 = sha256(raw_request_body)
```
### 6.2 批次幂等键
批次幂等键建议:
```text
batch_idempotency_key =
sha256(
"superagent-task-result-batch:v1"
+ "|" + source_message_id
+ "|" + request_payload_sha256
)
```
作用:
- 相同请求体重复提交时识别为幂等重放。
- 同一个 `source_message_id` 如果提交了不同请求体,不会被误认为同一个批次。
### 6.3 item 幂等键
每条 `ai_task_results[]` 的幂等键建议:
```text
item_idempotency_key =
sha256(
"superagent-task-result-item:v1"
+ "|" + source_message_id
+ "|" + source_event_index
+ "|" + array_index
+ "|" + catalog_code
+ "|" + skill_id
+ "|" + result_type
+ "|" + task_type
+ "|" + task_subtype
+ "|" + item_payload_sha256
)
```
说明:
- `array_index` 按 AI 返回列表顺序保存,建议从 1 开始。
- `item_payload_sha256` 是单个 item 规范 JSON 或原始片段的 SHA-256。
- 数据库应对 `hotel_id + item_idempotency_key` 建唯一约束。
### 6.4 重复提交处理
| 场景 | 处理方式 |
| --- | --- |
| 完全相同请求体重复提交 | 返回已有 batch 和 item不重复创建任务 |
| 同一 `source_message_id` 不同请求体 | 作为潜在冲突处理,第一版建议拒绝并返回 `IDEMPOTENCY_CONFLICT`,除非后续明确支持重新抽取版本 |
| 同一请求内 item 幂等键重复 | 拒绝请求,返回 `DUPLICATE_TASK_RESULT_ITEM` |
## 7. 系统映射规则
接口接收后,应将 AI 字段映射到系统字段。
| AI 字段 | 系统字段 | 中文说明 |
| --- | --- | --- |
| `task_type` | `ai_task_type` | 保留 AI 原始任务类型 |
| `result_type` | `result_type` | 保留 AI 结果类型 |
| `task_type + result_type` | `system_task_type` | 映射为系统主任务类型 |
| `task_type + task_subtype` | `task_card_type` | 映射为任务卡类型 |
| `source_event_index + array_index` | `execution_order` | 生成同订单任务顺序 |
| 完整 item JSON | `ai_payload_json` | 保存 AI 原始 payload |
| `case_keys` | `case_keys_json` | 保存订单候选键 |
| `extracted_fields` | `extracted_fields_json` | 保存业务字段 |
| `manual_review` | `manual_review_json` | 保存人工复核结构 |
| `informational_message` | `informational_message_json` | 保存信息提醒 |
系统主任务类型映射以 `M002-order-task-workflow-v2.md` 为准。
## 8. 响应体
### 8.1 创建成功
首次成功创建时返回 `201 Created`
```json
{
"request_id": "req-20260707-0001",
"source_message_id": "1900000000000000001",
"batch_id": "1900000000000001001",
"idempotent_replay": false,
"accepted_count": 1,
"items": [
{
"source_event_index": 1,
"array_index": 1,
"ai_transition_id": "1900000000000002001",
"order_id": "1900000000000003001",
"task_id": "1900000000000004001",
"system_task_type": "NEW_BOOKING",
"task_card_type": "NEW_BOOKING",
"task_status": "PENDING_CONFIRM",
"order_status": "TEMPORARY",
"execution_order": 1
}
],
"warnings": []
}
```
### 8.2 幂等重放
相同请求体重复提交时返回 `200 OK`
```json
{
"request_id": "req-20260707-0002",
"source_message_id": "1900000000000000001",
"batch_id": "1900000000000001001",
"idempotent_replay": true,
"accepted_count": 1,
"items": [],
"warnings": [
{
"code": "IDEMPOTENT_REPLAY",
"message": "相同请求已经处理,本次未重复创建任务。"
}
]
}
```
## 9. 错误响应
错误响应统一结构:
```json
{
"request_id": "req-20260707-0003",
"error_code": "AUTH_SIGNATURE_INVALID",
"message": "签名校验失败。",
"details": []
}
```
常见错误码:
| HTTP 状态 | error_code | 中文说明 |
| --- | --- | --- |
| 401 | `AUTH_HEADER_MISSING` | 鉴权 Header 缺失 |
| 401 | `AUTH_TIMESTAMP_INVALID` | 请求时间无效或超出窗口 |
| 401 | `AUTH_SIGNATURE_INVALID` | 签名不匹配 |
| 409 | `AUTH_NONCE_REPLAY` | Nonce 重放 |
| 413 | `REQUEST_BODY_TOO_LARGE` | 请求体过大 |
| 400 | `INVALID_JSON` | JSON 不可解析 |
| 400 | `SOURCE_MESSAGE_REQUIRED` | `source_message_id` 缺失 |
| 404 | `SOURCE_MESSAGE_NOT_FOUND` | SourceMessage 不存在 |
| 400 | `TASK_RESULTS_EMPTY` | `ai_task_results[]` 为空 |
| 400 | `TASK_RESULT_UNSUPPORTED_TYPE` | `result_type``task_type` 不可识别 |
| 400 | `DUPLICATE_TASK_RESULT_ITEM` | 同一请求内 item 重复 |
| 409 | `IDEMPOTENCY_CONFLICT` | 同一 SourceMessage 出现不同请求体重复提交 |
| 500 | `INTERNAL_ERROR` | 系统内部错误 |
错误响应不得返回原始请求体、邮件正文、附件 URL、Token、签名 secret 或完整个人敏感信息。
## 10. 验收标准
第一版接口实现完成时至少满足:
- 缺失 HMAC Header 时返回 `401`
- timestamp 超出窗口时返回 `401`
- nonce 重放时返回 `409`
- 签名错误时返回 `401`
- 一个请求只能包含一个 `source_message_id`
- `source_message_id` 不存在时返回 `404`
- 相同请求重复提交不会重复创建任务。
- 同一 `source_message_id` 不同请求体重复提交返回 `409`
- 成功接收后能保存 AI 过渡层记录,并返回 batch、task 和 order 标识。
- 日志和响应不暴露 secret、签名原文、完整邮件正文或附件 URL。