# 输出契约 ## 用途 定义 `booking-desk-event` 的统一 JSON 输出,以及 Main Agent 的 `S10/S99` 源邮件查看通知结果。所有业务事件都先是候选 `MessageEvent`,不是系统事实,不是真实 TaskCard。 ## 输出交付形式 - 最终输出必须是当前调用直接返回的结构化 JSON 参数对象,不是 JSON 字符串或结果文件。 - JSON 根对象必须直接使用本契约定义的顶层结构,不得增加 `booking_data`、`file`、`filename`、`artifact`、`download_url` 或其他文件包装层。 - 不得创建、写入、上传、附加或返回 `booking_data.json` 或任何其他结果 JSON 文件。 - 不得用结果文件名、文件路径、下载链接、artifact 或文件引用代替最终 JSON 参数对象。 - 最终返回值不得使用 Markdown 代码块,也不得在 JSON 前后附加解释性文字。 - 不得为交付形式增加 `output_mode`、`filename` 等非业务字段。 - 输入附件引用仍可保留在事件的 `attachments` 或 `file_references` 中,但只能作为输入证据,不能替代最终 JSON 参数对象。 ## Source Message Identity - 所有合规的业务输出、`S10` 和 `S99` 都必须包含 `source_message`。 - `source_message.source_message_id` 必须是上游提供的非空值,用于通知系统关联和展示原邮件;不得猜测、生成或从其他编号替代。 - subject、from、cc、received_at 等其他元数据不可得时使用 `null` 或空数组。 - 上游没有提供 `source_message_id` 时属于本业务契约之外的输入或基础设施错误,不能产生一个声称合规的 `S10` 或 `S99`。 ## Main Agent Notification Outcome `S10` 和 `S99` 共用以下源邮件查看通知结构: ```json { "source_message": { "source_message_id": "", "subject": null, "from": null, "cc": [], "received_at": null, "source_channel": "Email" }, "route_code": "S10 | S99", "handler_type": "main_agent_outcome", "result_type": "source_message_review_notification", "current_or_history": "current", "agent_assessment": { "status": "no_booking_action_detected | material_package_unavailable", "reason_code": "", "automation_action": "none" }, "notification": { "required": true, "notification_type": "source_message_review", "show_source_message": true, "requires_user_decision": true, "visible_message": "" }, "manual_review": null } ``` - `S10` 固定使用 `status=no_booking_action_detected`、`reason_code=no_booking_action_detected`、`manual_review=null`。为保持信息系统兼容,字段和值不变;其业务含义是输入可理解但未匹配当前 Agent 支持的业务事件,不表示用户无需查看、回复或处理源邮件。 - `S99` 固定使用 `status=material_package_unavailable`;为保持信息系统兼容,字段和值不变。它表示当前输入不足、无法判断是否匹配支持业务事件;`agent_assessment.reason_code` 必须与 `manual_review.reason_code` 一致,`manual_review.review_record_type=main_agent_entry_review`。 - 两者均使用 `requires_user_decision=true`,不得增加 `action_required` 替用户判断是否介入。 - `S99` 只用于输入不足、无法完成支持范围分类。已经匹配支持业务事件但参数或目标不安全时,使用业务事件 `Need Manual Review`。 `S99.manual_review` 必须使用: ```json { "reason_code": "material_package_unavailable", "visible_reason": "当前输入不足,无法完成支持业务事件范围分类。", "review_record_type": "main_agent_entry_review", "missing_fields": [], "blocking_points": [], "conflicting_points": [], "suggested_human_actions": ["review_source_message"], "evidence_to_check": ["source_message"], "known_fields": {} } ``` ## 业务事件顶层结构 ```json { "source_message": { "source_message_id": "", "subject": null, "from": null, "cc": [], "received_at": null, "source_channel": "Email" }, "message_events": [], "case_candidates": [], "extraction_warnings": [], "unhandled_current_intents": [] } ``` 所有业务输出都必须固定包含以上五个顶层字段。没有未覆盖当前意图时,`unhandled_current_intents` 使用空数组。该字段只属于业务输出;`S10` 和 `S99` 保持既有入口通知结构,不增加该字段。 ## Unhandled Current Intents `unhandled_current_intents` 用于展示同一邮件中意图清楚、具有当前业务意义,但现有事件目录或任务卡无法承接的内容。它不是 `MessageEvent`,信息系统不得据此自动创建 TaskCard。 每项固定使用: ```json { "category": "unhandled_current_business_content", "current_or_history": "current", "reason_code": "requires_business_approval_or_unsupported_task_card", "text_raw": "<当前证据原文>", "visible_message": "当前邮件包含未被现有任务类型覆盖的业务意图:<忠实中文概述>。请查看原邮件并决定后续处理。", "requires_user_decision": true, "case_keys": { "group_code": null, "confirmation_number": null, "reservation_number": null, "block_code": null }, "attachments": [], "file_references": [] } ``` 固定规则: - 仅由当前正文、当前附件、当前 OCR、当前表格或当前继续处理指令产生;history-only 内容不得进入。 - `text_raw` 原样保留;`visible_message` 提供忠实中文说明,不得增加批准、拒绝、执行或业务结论。无法安全翻译时使用“当前邮件包含未被现有任务类型覆盖的业务意图,请查看原文并决定后续处理。” - `requires_user_decision` 固定为 `true`。 - `case_keys` 始终包含四个键。仅在当前证据或允许的历史证据唯一支持时填写;否则使用 `null`,不得猜测。 - `attachments` 和 `file_references` 只保留当前证据引用;历史附件不得带入。 - 一个独立意图一个 item,按当前证据顺序输出;连续多句组成同一请求时保持一个 item。 - HTML/plain MIME alternatives、重复 OCR、quoted thread 和已被事件完整承接的内容不得重复输出。 - 清楚但不支持的业务意图使用本字段;已匹配支持事件但参数或目标不安全时使用 `Need Manual Review`;内容不可读或抽取失败时使用 `extraction_warnings` 或相应人工复核。 - `extraction_warnings` 只承载解析、OCR、抽取和证据质量问题,不得用来承载未覆盖业务意图。 - `source_message_id` 只保留在根 `source_message`,不得在 item 内重复。 详细覆盖顺序遵循 `03-current-content-completeness.md`。 ## 支持业务事件类型 - `New Booking` - `Update Booking / Amendment` - `Cancel Booking` - `Cancel Allotment` - `Voucher Received` - `Payment Evidence` - `Rooming List` - `Allotment Maintenance` - `AMEND GROUP CODE` - `Invoice Generation` - `Invoice Received` - `Payment Notice` - `Trace` - `Manual RateCode` - `TA RECORDER` - `Note` 普通预订补充信息统一输出 `Trace`。`Note` 暂时仅为旧契约兼容保留,不作为当前普通补充信息的路由结果,除非后续任务卡映射另有明确规则。 ## 业务人工复核结果 - `Need Manual Review` ## 单个事件 ```json { "event_type": "", "event_role": "travel_agent_request", "current_or_history": "current", "source_event_index": "E1", "case_keys": { "group_code": null, "confirmation_number": null, "reservation_number": null, "block_code": null }, "relevant_message_excerpt": "", "attachments": [], "file_references": [], "context_used": {}, "extracted_fields": {}, "manual_review": null } ``` ## Case Key - `case_keys.group_code` 必须是单值或 `null`。 - 多个 group code 必须拆成多个事件。 - `U-` 是价格或 Rate Code marker,绝不能写入 confirmation、reservation、booking reference 或 target key。 - Parent allocation group code 是证据,不是 child event 的 `case_keys.group_code`。 ## Date Evidence 涉及入住、离店或晚数时,`extracted_fields` 应保留: ```json { "arrival_date": "2026-05-14", "departure_date": "2026-05-15", "nights": 1, "date_evidence": { "hotel_date_raw": "14-15", "tour_date_raw": "2026/05/11\n2026/05/16", "action_date_raw": "11/05 AMD", "sheet_month_year": "BOOKING 05-2026", "date_inference_basis": "hotel_date_range_with_tour_date_context" } } ``` 日期识别必须遵循 `54-stay-date-parsing.md`。`action_date_raw` 是动作日期证据,不是入住日期。 ## Trace Contract Trace 用于当前邮件中能绑定具体预订对象、但不属于主任务核心参数的补充业务信息。要求、安排、备注和包含具体预订事实的单纯告知都可以触发 Trace;不要判断发件人是否明确要求酒店记录、执行或转交。 同一封邮件、同一目标对象只输出一个 Trace。多条补充信息按当前证据顺序合并: ```json { "event_type": "Trace", "extracted_fields": { "trace_subtype": "extra_bed | general_request", "trace_text": "<按当前证据顺序合并的完整补充信息原文>", "trace_items": [ { "category": "extra_bed | room_preference | room_setup | meeting | function | meal | transport | payment_information | general_information", "text_raw": "<单条原文>", "service_date": "YYYY-MM-DD | null", "service_period_raw": "<原始时段或 null>", "pax": null, "notify_departments": [] } ], "notify_departments": [] } } ``` - `trace_text` 必须保留完整原文,结构化字段不能替代原文。 - `trace_items` 的顺序必须与 `trace_text` 一致。 - `category` 必须使用固定枚举;banquet 归入 `function`,无法归入更具体类别时使用 `general_information`。 - `service_date`、`service_period_raw`、`pax` 只有在证据明确时填写,否则为 `null`。 - item 的 `notify_departments` 只保留明确或规则唯一支持的部门;事件级 `notify_departments` 是 item 已知部门的去重合集。 - 部门不清时使用空数组,不得仅因此输出人工复核。 - 全部 item 都是 extra bed 时使用 `trace_subtype=extra_bed`;其他情况使用 `general_request`。 - Trace 包含 extra bed item 时,继续在事件级 `extracted_fields` 保留原有 `occupancy_update`、`requires_rate_update` 和 `rate_adjustment_formula`,不得移动到 item 或删除。 - 主事件已完整承接的核心参数不得重复生成 Trace。 - 不同目标对象必须拆成不同 Trace;`case_keys.group_code` 仍为单值。 - `payment_information` 只表示已经确定、需要随预订保留的补充付款安排;付款凭证、到账结果、Payment Notice、Invoice、催款或付款条件审批询问不得改名为 Trace。 `FYI guide will arrive at 20:00` 等具体预订告知可以触发 Trace;只有 `Thanks`、`Noted`、`Received`、裸 `FYI` 等没有具体业务信息的文字不能触发 Trace。需要酒店进行价格、退款、账期、付款政策、合同条件或其他业务审批的询问也不能伪装成 Trace。 不属于 Trace 但具有当前业务意义的内容必须原样保留在 Main Agent 素材包的 `unknowns` 中。只要同邮件存在至少一个支持事件,业务 skill 就按 `03-current-content-completeness.md` 将其一对一输出到顶层 `unhandled_current_intents`;不得静默忽略或改成 Trace。 ## 人工复核 ```json { "manual_review": { "reason_code": "", "visible_reason": "", "review_record_type": "business_event_review", "missing_fields": [], "blocking_points": [], "conflicting_points": [], "suggested_human_actions": [], "evidence_to_check": [], "known_fields": {} } } ``` ## 派生事件 派生或关联事件使用: ```json { "related_source_event_index": "", "related_source_event_indices": [], "related_event_type": "", "relationship_type": "", "requires_downstream_hard_validation": true } ``` `related_source_event_index` 用于单一关联事件;一个派生事件关联多个来源事件时使用 `related_source_event_indices`。 典型场景: - New/Update/Amend Group Code 同事件出现 extra bed、Meeting、meal、arrival notice 或其他预订补充信息,按目标派生一个 linked `Trace`。 - Rooming List 按每个目标 group 派生 `TA RECORDER`。 - Parent-to-child allocation creation 必须派生独立的 parent release/cancel 候选 `message_event`,不得只放进 child `New Booking.extracted_fields`。 ## Parent-To-Child Parent Candidate 当 current evidence 确认 parent group 拆成 child group codes,且 parent group code 清楚时,必须额外输出一个 parent release/cancel candidate: ```json { "event_type": "Cancel Booking", "event_role": "travel_agent_request", "current_or_history": "current", "source_event_index": "E_PARENT_RELEASE", "case_keys": { "group_code": "", "confirmation_number": null, "reservation_number": null, "block_code": null }, "relevant_message_excerpt": "", "attachments": [], "file_references": [], "context_used": { "source": "current parent-to-child allocation evidence" }, "extracted_fields": { "parent_release_or_cancel_candidate": true, "release_reason": "parent_to_child_allocation_split", "parent_group_code": "", "child_group_codes": [""], "allocation_split_from_parent": true, "parent_original_room_summary": null }, "related_source_event_indices": ["E1"], "related_event_type": "New Booking", "relationship_type": "linked_parent_release_after_child_split", "requires_downstream_hard_validation": true, "manual_review": null } ``` 该事件是候选事件,不代表 PMS 已取消成功,不创建真实 TaskCard,不写外部系统。 ## 禁止输出 不得输出真实 TaskCard ID、最终 Case 状态、Payment 确认、Block Status 自动转换、Receipt、Invoice 文件、Opera/PMS 写入结果。 不得生成或返回结果 JSON 文件、结果文件引用、结果下载链接或 artifact。该限制只针对最终处理结果,不禁止下载、读取和解析输入附件,也不禁止在事件中保留输入附件证据。