558 lines
16 KiB
Markdown
558 lines
16 KiB
Markdown
# 查询接口后端开发契约:给开发
|
||
|
||
本文档给后端开发和开发 AI 使用,用于把 `Skill查询接口需求梳理_给开发.md` 落成可开发的只读查询接口。
|
||
|
||
本契约只定义 **Skill script 可调用的只读查询接口**。
|
||
这些接口用于给 Skill 提供判断上下文,不负责创建任务卡、不写 Opera、不改业务数据库、不确认付款、不导入名单。
|
||
|
||
字段级细节以 `查询接口返回字段字典.xlsx` 为准;本文档负责定义接口路径、请求方式、统一响应、错误码和各接口的 JSON 结构。
|
||
|
||
## 1. 接口总原则
|
||
|
||
### 1.1 只读边界
|
||
|
||
所有接口必须是只读查询:
|
||
|
||
- 不创建任务卡。
|
||
- 不写 Opera。
|
||
- 不更新预订。
|
||
- 不确认 payment。
|
||
- 不生成 receipt。
|
||
- 不导入 rooming list。
|
||
- 不维护 TA Recorder。
|
||
- 不写 Trace / Reservation Notes。
|
||
- 不把 `manual_review` 自动升级为 `normal_task`。
|
||
|
||
接口只能返回事实、候选、状态和证据。
|
||
最终业务判断仍由 Skill 根据项目规则输出 `ai_task_result`。
|
||
|
||
### 1.2 Current / History 边界
|
||
|
||
业务动作必须来自 `body_current`。
|
||
`body_thread` 只能作为 target binding evidence。
|
||
|
||
如果查询 key 来自历史邮件,调用方应传:
|
||
|
||
```json
|
||
{
|
||
"target_key_source": "body_thread_evidence",
|
||
"body_thread_used_only_as_evidence": true
|
||
}
|
||
```
|
||
|
||
后端接口可以使用该 key 查询对象,但不得因为历史邮件里出现旧动作而创建或建议创建任务。
|
||
|
||
### 1.3 统一路径建议
|
||
|
||
第一版建议路径如下:
|
||
|
||
| 逻辑接口名 | HTTP 方法 | 路径 |
|
||
|---|---|---|
|
||
| `query_case_context` | `POST` | `/api/ai-query/v1/case-context` |
|
||
| `query_object_detail` | `POST` | `/api/ai-query/v1/object-detail` |
|
||
| `query_file_parse_context` | `POST` | `/api/ai-query/v1/file-parse-context` |
|
||
| `query_parent_task_context` | `POST` | `/api/ai-query/v1/parent-task-context` |
|
||
|
||
使用 `POST` 是为了传复杂查询条件,不代表有写入动作。
|
||
|
||
## 2. 通用请求头
|
||
|
||
建议支持:
|
||
|
||
| Header | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `Content-Type: application/json` | 是 | JSON 请求 |
|
||
| `X-Request-Id` | 是 | 调用方生成的请求 ID,用于排查 |
|
||
| `X-Source-Message-Id` | 建议 | 邮件 ID,方便日志串联 |
|
||
| `X-AI-Trace-Id` | 建议 | AI 运行链路 ID |
|
||
|
||
鉴权方式由信息系统统一确定,本文不限定。
|
||
|
||
## 3. 通用响应包
|
||
|
||
所有接口统一返回:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"request_id": "",
|
||
"trace_id": "",
|
||
"data": {},
|
||
"warnings": [],
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
失败时:
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"request_id": "",
|
||
"trace_id": "",
|
||
"data": null,
|
||
"warnings": [],
|
||
"error": {
|
||
"code": "",
|
||
"message": "",
|
||
"details": {}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.1 通用错误码
|
||
|
||
| code | HTTP 状态 | 说明 |
|
||
|---|---:|---|
|
||
| `BAD_REQUEST` | 400 | 请求 JSON 格式错误或字段类型错误 |
|
||
| `MISSING_REQUIRED_FIELD` | 400 | 缺少必填字段 |
|
||
| `UNAUTHORIZED` | 401 | 未鉴权 |
|
||
| `FORBIDDEN` | 403 | 无权限访问该数据 |
|
||
| `NOT_FOUND` | 404 | 查询对象不存在;仅用于按明确 ID 查询的接口 |
|
||
| `FILE_NOT_ACCESSIBLE` | 422 | 文件无权限、下载失败或不可访问 |
|
||
| `PARSE_FAILED` | 422 | 文件解析失败 |
|
||
| `UPSTREAM_TIMEOUT` | 504 | 上游系统超时 |
|
||
| `INTERNAL_ERROR` | 500 | 未知后端错误 |
|
||
|
||
注意:`query_case_context` 中查不到对象时,通常不应返回 404,而应返回 `success=true` 且 `matched_order_records=[]`。因为 Skill 还需要继续判断是否有 pending task、workflow 或终止记录。
|
||
|
||
## 4. 接口一:query_case_context
|
||
|
||
### 4.1 用途
|
||
|
||
通过 `group_code`、`confirmation_number`、`reservation_no` 查询业务对象、待处理任务、工作流、终止记录和 key 关系。
|
||
|
||
适用 Skill:
|
||
|
||
```text
|
||
S01/S02/S03/S04/S05/S06/S07/S08
|
||
```
|
||
|
||
### 4.2 Request
|
||
|
||
```json
|
||
{
|
||
"source_message_id": "MSG-001",
|
||
"source_event_index": 1,
|
||
"group_code": "LT260417VIPA",
|
||
"confirmation_number": null,
|
||
"reservation_no": null,
|
||
"object_type_hint": "group_block",
|
||
"target_key_source": "body_current",
|
||
"body_thread_used_only_as_evidence": false
|
||
}
|
||
```
|
||
|
||
字段要求:
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `source_message_id` | 是 | 当前邮件 ID |
|
||
| `source_event_index` | 是 | 当前事件序号 |
|
||
| `group_code` | 条件 | Group / Allotment 优先 key |
|
||
| `confirmation_number` | 条件 | FIT 优先 key |
|
||
| `reservation_no` | 否 | Opera reservation no,辅助查询 |
|
||
| `object_type_hint` | 否 | `fit_reservation` / `group_block` / `allotment_control_block` / `booking_record` / `unknown` |
|
||
| `target_key_source` | 建议 | `body_current` / `body_thread_evidence` / `upstream_task_context` / `system_context` |
|
||
| `body_thread_used_only_as_evidence` | 建议 | key 来自历史证据时传 `true` |
|
||
|
||
`group_code`、`confirmation_number`、`reservation_no` 至少应有一个非空。
|
||
如果三个 key 都为空,后端可返回 `MISSING_REQUIRED_FIELD`;调用方也可选择不调用该接口。
|
||
|
||
### 4.3 Response data
|
||
|
||
```json
|
||
{
|
||
"matched_order_records": [
|
||
{
|
||
"object_id": "OBJ-001",
|
||
"object_type": "group_block",
|
||
"group_code": "LT260417VIPA",
|
||
"confirmation_number": null,
|
||
"reservation_no": null,
|
||
"block_id": "BLK-10001",
|
||
"block_name": "LT260417VIPA GROUP",
|
||
"guest_name": null,
|
||
"group_name": "LT260417VIPA GROUP",
|
||
"arrival_date": "2026-04-17",
|
||
"departure_date": "2026-04-20",
|
||
"room_items": [],
|
||
"rate_code": null,
|
||
"rate_code_price": null,
|
||
"reservation_type": null,
|
||
"status": "active",
|
||
"cancel_status": "not_cancelled",
|
||
"source_table": "opera_group_block_sync",
|
||
"last_updated_at": "2026-07-07T10:00:00+08:00"
|
||
}
|
||
],
|
||
"pending_or_open_tasks": [],
|
||
"active_workflows": [],
|
||
"terminated_records": [],
|
||
"target_object_validation": {
|
||
"status": "single",
|
||
"matched_object_id": "OBJ-001",
|
||
"matched_object_type": "group_block",
|
||
"can_create_new_booking_task": false,
|
||
"can_create_update_task": true,
|
||
"can_create_cancel_task": true,
|
||
"can_attach_voucher": true,
|
||
"can_attach_rooming_list": true,
|
||
"needs_manual_review_reason": null
|
||
},
|
||
"key_relationships": {
|
||
"group_code_and_confirmation_same_object": null,
|
||
"relationship_evidence": ""
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 后端处理规则
|
||
|
||
- 查不到对象时返回空数组,不要直接报错。
|
||
- 必须同时查:已写入对象、pending/open task、active workflow、terminated record。
|
||
- 如果查到多个可能对象,`target_object_validation.status = "multiple"`。
|
||
- 接口不要返回“应该 normal_task / manual_review”的结论,只返回事实和校验摘要。
|
||
- 对 S01,若同 key 已有有效对象或 pending/open task,Skill 会倾向 `manual_review`。
|
||
- 对 S04/S05,如果无已写入对象但有可承接的 pending New Booking / 上游任务,仍可作为目标候选返回。
|
||
|
||
## 5. 接口二:query_object_detail
|
||
|
||
### 5.1 用途
|
||
|
||
按 `object_id` 查询更完整的对象快照,供 S02 建 before/after、S03 判断取消状态、S07 extra bed 取 `rate_code_price`、S04/S05/S08 做目标展示和硬校验。
|
||
|
||
适用 Skill:
|
||
|
||
```text
|
||
S02/S03/S04/S05/S07/S08
|
||
```
|
||
|
||
### 5.2 Request
|
||
|
||
```json
|
||
{
|
||
"object_id": "OBJ-001",
|
||
"object_type": "group_block"
|
||
}
|
||
```
|
||
|
||
字段要求:
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `object_id` | 是 | `query_case_context` 返回的对象 ID |
|
||
| `object_type` | 建议 | 对象类型,可帮助后端选择查询表 |
|
||
|
||
### 5.3 Response data
|
||
|
||
```json
|
||
{
|
||
"object_id": "OBJ-001",
|
||
"object_type": "group_block",
|
||
"group_code": "LT260417VIPA",
|
||
"confirmation_number": null,
|
||
"reservation_no": null,
|
||
"block_id": "BLK-10001",
|
||
"block_name": "LT260417VIPA GROUP",
|
||
"status": "active",
|
||
"arrival_date": "2026-04-17",
|
||
"departure_date": "2026-04-20",
|
||
"nights": 3,
|
||
"guest_count": null,
|
||
"room_items": [
|
||
{
|
||
"room_type": "TWN",
|
||
"pms_room_type_code": "RM2",
|
||
"room_quantity": 2,
|
||
"rate_code": "WHO2",
|
||
"rate_code_price": 2000
|
||
}
|
||
],
|
||
"rate_code": "WHO2",
|
||
"rate_code_price": 2000,
|
||
"reservation_type": "TA",
|
||
"can_update": true,
|
||
"can_cancel": true,
|
||
"hard_validation_warnings": []
|
||
}
|
||
```
|
||
|
||
### 5.4 后端处理规则
|
||
|
||
- `object_id` 明确不存在时可返回 404 `NOT_FOUND`。
|
||
- `rate_code_price` 如果后端当前拿不到,应返回 `null`,并在 `hard_validation_warnings` 说明原因。
|
||
- S07 extra bed 不会自己计算最终房价;后端需提供能否取得 `rate_code_price` 的事实。
|
||
- `room_items[]` 可先返回摘要,第一版不要求完整 Opera 明细字段。
|
||
|
||
## 6. 接口三:query_file_parse_context
|
||
|
||
### 6.1 用途
|
||
|
||
查询附件、图片、PDF、OCR、Excel / workbook、QBD / LianTai 表格、voucher、rooming list 文件解析结果。
|
||
|
||
适用 Skill:
|
||
|
||
```text
|
||
S01/S02/S03/S04/S05/S06/S07/S08
|
||
```
|
||
|
||
### 6.2 Request
|
||
|
||
```json
|
||
{
|
||
"source_message_id": "MSG-001",
|
||
"source_event_index": 1,
|
||
"file_reference": "file://attachment-001",
|
||
"parse_need": "voucher"
|
||
}
|
||
```
|
||
|
||
`parse_need` 枚举建议:
|
||
|
||
| 值 | 用途 |
|
||
|---|---|
|
||
| `voucher` | S04 voucher / payment slip |
|
||
| `rooming_list` | S05 / S08 名单文件 |
|
||
| `qbd_liantai_table` | S01/S02/S03/S06/S07 表格行证据 |
|
||
| `ocr` | 图片/PDF OCR |
|
||
| `generic` | 通用解析 |
|
||
|
||
### 6.3 Response data
|
||
|
||
```json
|
||
{
|
||
"file_reference": "file://attachment-001",
|
||
"filename": "voucher.pdf",
|
||
"download_status": "ok",
|
||
"display_available": true,
|
||
"file_type": "pdf",
|
||
"ocr_text": "",
|
||
"workbook_summary": {},
|
||
"sheet_summaries": [],
|
||
"highlighted_rows": [],
|
||
"row_level_evidence": [],
|
||
"voucher_evidence": {
|
||
"voucher_subtype": "lian_tai_credit_voucher",
|
||
"lian_tai_code_candidate": "LT260417VIPA",
|
||
"code_field_read_status": "clear",
|
||
"bank_slip_detected": false
|
||
},
|
||
"list_file_evidence": {},
|
||
"parse_warnings": []
|
||
}
|
||
```
|
||
|
||
QBD / LianTai 表格场景可返回:
|
||
|
||
```json
|
||
{
|
||
"row_level_evidence": [
|
||
{
|
||
"sheet_name": "BOOKING 06-2026",
|
||
"row_number": 12,
|
||
"row_label": "12",
|
||
"highlight_status": "yellow",
|
||
"action_status_cell_value": "NEW BOOKING",
|
||
"raw_date_range": "17/04/26-20/04/26",
|
||
"raw_room_type": "TWN",
|
||
"raw_room_quantity": "2",
|
||
"raw_rate": "2000THB+500",
|
||
"before_after_evidence": null,
|
||
"strikethrough_state": null,
|
||
"cell_reference": "H12",
|
||
"ocr_evidence": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Rooming List 场景可返回:
|
||
|
||
```json
|
||
{
|
||
"list_file_evidence": {
|
||
"list_evidence_type": "rooming_list",
|
||
"file_type_validation": {
|
||
"is_guest_level_list": true,
|
||
"positive_headers": ["English Name", "Passport No", "Nationality"],
|
||
"negative_booking_table_evidence": []
|
||
},
|
||
"target_key_candidates": [
|
||
{
|
||
"key_type": "group_code",
|
||
"key_value": "HD260510A-1",
|
||
"source": "sheet_name"
|
||
}
|
||
],
|
||
"shared_evidence_sheets": ["总名单"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.4 后端处理规则
|
||
|
||
- 文件不可访问时返回 `success=false`、`FILE_NOT_ACCESSIBLE`,或 `success=true` 且 `download_status=failed`;项目第一版建议后者,方便 Skill 输出 structured `manual_review`。
|
||
- S04 不应从 bank slip OCR 的金额、memo、银行账号、付款人等字段自动生成 group_code。
|
||
- S05 不能因为文件是 Excel 就判断为 rooming list,必须返回正向名单证据或负向 booking table 证据。
|
||
- QBD / LianTai 多个高亮行应作为多个 `row_level_evidence[]` 返回,Skill 再按行拆分任务。
|
||
- 接口只返回证据,不负责决定路由到 S01/S02/S03/S05。
|
||
|
||
## 7. 接口四:query_parent_task_context
|
||
|
||
### 7.1 用途
|
||
|
||
查询 linked task 的父任务上下文。
|
||
主要用于:
|
||
|
||
- S07 从 S01 / S02 / S06 派生 Trace / Extra Bed linked task。
|
||
- S08 从 S05 Rooming List 派生 TA Recorder linked task。
|
||
|
||
### 7.2 Request
|
||
|
||
```json
|
||
{
|
||
"source_message_id": "MSG-001",
|
||
"parent_source_event_index": 1,
|
||
"linked_task_group_id": "LTK-001",
|
||
"source_rooming_list_task_id": null
|
||
}
|
||
```
|
||
|
||
字段要求:
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `source_message_id` | 建议 | 当前邮件 ID |
|
||
| `parent_source_event_index` | 条件 | 父事件序号 |
|
||
| `linked_task_group_id` | 条件 | 联动任务组 ID |
|
||
| `source_rooming_list_task_id` | S08条件 | S05 Rooming List 父任务 ID |
|
||
|
||
至少应提供 `parent_source_event_index`、`linked_task_group_id`、`source_rooming_list_task_id` 中一个。
|
||
|
||
### 7.3 Response data
|
||
|
||
```json
|
||
{
|
||
"parent_task": {
|
||
"task_id": "TASK-001",
|
||
"task_type": "Rooming List",
|
||
"task_status": "confirmed",
|
||
"case_keys": {
|
||
"group_code": "HD260510A-1"
|
||
},
|
||
"source_event_index": 1
|
||
},
|
||
"linked_tasks": [],
|
||
"execution_order": [
|
||
{
|
||
"task_id": "TASK-001",
|
||
"execution_order": 1
|
||
},
|
||
{
|
||
"task_id": "TASK-002",
|
||
"execution_order": 2
|
||
}
|
||
],
|
||
"parent_confirmed_payload": {
|
||
"target_object_id": "OBJ-001",
|
||
"file_reference": "file://rooming-list.xlsx"
|
||
},
|
||
"parent_status": "confirmed",
|
||
"attachment_binding": {
|
||
"file_reference": "file://rooming-list.xlsx",
|
||
"workbook_name": "HD260510A.xlsx",
|
||
"sheet_name": "HD260510A-1"
|
||
},
|
||
"file_reference": "file://rooming-list.xlsx",
|
||
"workbook_name": "HD260510A.xlsx",
|
||
"sheet_name": "HD260510A-1"
|
||
}
|
||
```
|
||
|
||
### 7.4 后端处理规则
|
||
|
||
- 这个接口不能创建 linked task,只能返回父任务和已存在 linked task 上下文。
|
||
- S07 linked task 必须等父 S01/S02/S06 完成并确认目标对象后才能真正执行。
|
||
- S08 linked task 必须跟随 S05 已确认的目标 `group_code`,不得从 sheet 名自行猜 `group_code`。
|
||
- `parent_status` 未完成时,Skill 可输出 `blocked_until_parent_completed=true`。
|
||
|
||
## 8. 推荐调用时序
|
||
|
||
### 8.1 普通路由级查询
|
||
|
||
```text
|
||
Main Agent 从 body_current 提取 group_code / confirmation_number
|
||
-> POST /api/ai-query/v1/case-context
|
||
-> routing_context 传给 Skill
|
||
-> Skill 判断 normal_task / manual_review
|
||
```
|
||
|
||
### 8.2 current 无 key,但 history 有唯一目标 key
|
||
|
||
```text
|
||
body_current 有明确业务动作
|
||
-> body_thread 仅提取唯一 target key 作为 evidence
|
||
-> POST /api/ai-query/v1/case-context
|
||
target_key_source=body_thread_evidence
|
||
body_thread_used_only_as_evidence=true
|
||
-> Skill 输出中保留 target_key_source 和 hard validation 标记
|
||
```
|
||
|
||
### 8.3 文件类任务
|
||
|
||
```text
|
||
当前邮件有附件 / 图片 / PDF / Excel / link
|
||
-> POST /api/ai-query/v1/file-parse-context
|
||
-> 如解析出 key,再 POST /api/ai-query/v1/case-context
|
||
-> Skill 判断任务
|
||
```
|
||
|
||
### 8.4 linked task
|
||
|
||
```text
|
||
S01/S02/S06 主任务 + S07 linked task
|
||
或 S05 Rooming List + S08 TA Recorder
|
||
-> POST /api/ai-query/v1/parent-task-context
|
||
-> 返回父任务状态、目标对象、附件绑定和执行顺序
|
||
```
|
||
|
||
## 9. 与两张字典表的关系
|
||
|
||
### 9.1 与 `查询接口返回字段字典.xlsx`
|
||
|
||
本文定义接口级契约。
|
||
字段名、字段路径、数据类型、是否数组、是否必传/必返,以 `查询接口返回字段字典.xlsx` 为准。
|
||
|
||
### 9.2 与 `AI输出参数并集字典.xlsx`
|
||
|
||
`AI输出参数并集字典.xlsx` 只定义 Skill 最终输出到过渡表的字段。
|
||
查询接口返回字段不要求全部进入 AI 输出 JSON。
|
||
|
||
如果某个查询字段只用于 Skill 判断,可只进入 `context_used` 摘要,不必作为 AI 输出字段展开。
|
||
|
||
当前建议重点关注这些查询字段:
|
||
|
||
- `reservation_no`
|
||
- `object_id`
|
||
- `object_type`
|
||
- `block_id`
|
||
- `block_name`
|
||
- `rate_code_price`
|
||
|
||
这些字段虽属于查询上下文,但后续任务卡展示、Opera API 定位、底表展示和硬校验都会用到,开发应在接口层明确返回。
|
||
|
||
## 10. 后端验收清单
|
||
|
||
后端完成后,至少应满足:
|
||
|
||
- 四个接口都可通过 POST JSON 调用。
|
||
- 所有接口都使用统一响应包。
|
||
- 查询不到对象时,`query_case_context` 返回空数组而不是直接报错。
|
||
- 每次查询都能带上 `source_message_id`、`source_event_index` 或请求追踪信息。
|
||
- 支持 target key 来自 `body_thread_evidence`,但不从 history 触发任务。
|
||
- `query_case_context` 同时返回已写入对象、pending/open task、active workflow、terminated record。
|
||
- `query_file_parse_context` 能区分 voucher、rooming list、QBD/LianTai table 证据。
|
||
- `query_parent_task_context` 能返回父任务状态和执行顺序。
|
||
- 所有接口不发生写入动作。
|