提交一次全量代码

This commit is contained in:
andy
2026-07-09 11:59:34 +08:00
parent 855396e553
commit 9fa608f602
58 changed files with 8439 additions and 116 deletions

View File

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