Files
th-hotel-simple/docs/import/20260706/查询接口后端开发契约_给开发_副本.md
2026-07-09 11:59:34 +08:00

558 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 查询接口后端开发契约:给开发
本文档给后端开发和开发 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 taskSkill 会倾向 `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` 能返回父任务状态和执行顺序。
- 所有接口不发生写入动作。