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