Browse Source

引入接口文档

WangKang 1 week ago
parent
commit
6ed16782c5

+ 0 - 215
docs/2026-03-26-code-changes-summary.md

@@ -1,215 +0,0 @@
-# 代码变更说明文档
-
-**日期**: 2026-03-26  
-**提交次数**: 5 次  
-**涉及文件**: 200+ 个文件
-
----
-
-## 一、提交概览
-
-| 提交ID | 提交说明 | 时间 |
-|--------|----------|------|
-| 4de959d0 | 新增表结构的增删改查接口 | 20:49 |
-| d2bd9e28 | 基础对话;SSE 流式响应;会话创建+历史查选接口 | 20:16 |
-| 4dc0df0f | ai agent | 18:25 |
-| 9b5a845e | add docs | 17:37 |
-| 93cc7508 | 收敛多余项目,非 admin、openplatform 外的功能收敛进 emoon-extend; 基础设施收敛进 emoon-infra | 16:21 |
-
----
-
-## 二、主要功能变更
-
-### 1. AI Agent 引擎配置管理 (4de959d0)
-
-**新增功能**: AI Agent 引擎配置的完整 CRUD 接口
-
-**涉及模块**: `emoon-infra/emoon-modules-api/emoon-system-api`
-
-**新增文件**:
-- `AiAgentEngineConfigBo.java` - 业务对象
-- `AiAgentEngineConfigVo.java` - 视图对象
-- `AiAgentEngineConfigMapper.java` - 数据访问层
-- `IAiAgentEngineConfigService.java` - 服务接口
-- `AiAgentEngineConfigServiceImpl.java` - 服务实现
-- `AiAgentEngineConfigController.java` - 控制器层
-
-**功能说明**: 提供 AI Agent 引擎配置的增删改查接口,支持配置不同 AI 引擎的参数。
-
----
-
-### 2. 智能对话系统 (d2bd9e28)
-
-**新增功能**: 基础对话能力、SSE 流式响应、会话管理
-
-**涉及模块**: `emoon-openplatform`
-
-**核心功能**:
-
-#### 2.1 Agent 对话控制器
-- 文件: `AgentController.java`
-- 功能: 提供对话接口,支持流式响应
-
-#### 2.2 DTO/VO 对象
-- `AgentChatRequest.java` - 对话请求对象
-- `AgentChatResponse.java` - 对话响应对象
-- `ConversationCreateRequest.java` - 会话创建请求
-- `ConversationVo.java` - 会话视图对象
-
-#### 2.3 服务层
-- `IAgentChatService.java` / `AgentChatServiceImpl.java` - 对话服务
-- `IConversationService.java` / `ConversationServiceImpl.java` - 会话管理服务
-
-#### 2.4 工具类
-- `SignUtil.java` - 签名工具,用于接口安全验证
-
-**技术特点**:
-- 使用 SSE (Server-Sent Events) 实现流式响应
-- 支持会话创建和历史记录查询
-
----
-
-### 3. AI Agent 核心引擎 (4dc0df0f)
-
-**新增功能**: AI Agent 核心引擎架构
-
-**涉及模块**: `emoon-infra/emoon-modules-api/emoon-mcp-api` 和 `emoon-system-api`
-
-#### 3.1 引擎核心 (mcp-api)
-
-| 文件 | 说明 |
-|------|------|
-| `AgentEngine.java` | 引擎接口定义 |
-| `AgentEngineFactory.java` | 引擎工厂,用于创建引擎实例 |
-| `AgentRequest.java` / `AgentResponse.java` | 请求/响应对象 |
-| `MockAgentEngine.java` | 模拟引擎实现(用于测试) |
-
-#### 3.2 领域模型 (新增 8 个实体)
-
-**AI Agent 应用相关**:
-- `AiAgentApp.java` - AI 应用定义
-- `AiAgentEngineConfig.java` - 引擎配置
-
-**AI 卡片相关**:
-- `AiCardInstance.java` - 卡片实例
-- `AiCardDefinition.java` - 卡片定义
-- `AiCardCategory.java` - 卡片分类
-- `AiCardPlugin.java` - 卡片插件
-- `AiCardGrayConfig.java` - 灰度配置
-- `AiCardActionLog.java` - 操作日志
-
-**对话相关**:
-- `AiConversation.java` - 对话记录
-
-**使用统计**:
-- `AiUsageLog.java` - 使用日志
-
-#### 3.3 数据访问层
-每个实体对应一个 Mapper 接口,共 8 个 Mapper。
-
-#### 3.4 服务层
-每个实体对应一个 Service 接口和实现,共 8 个 Service。
-
----
-
-### 4. 项目架构重构 (93cc7508)
-
-**重构内容**: 项目结构收敛整理
-
-#### 4.1 收敛到 emoon-extend 的模块
-
-**emoon-file-downloader** - 文件下载服务
-- 独立的文件下载微服务
-- 包含 Dockerfile 支持容器化部署
-
-**emoon-tongue** - 舌诊服务
-- 舌诊 AI 诊断功能
-- 包含 H5 页面支持
-- 主要组件:
-  - 舌诊控制器 (CreateRecordController, DiagnosisController 等)
-  - 舌诊服务实现 (TongueDiagnosisServiceImpl, TongueAiDiagnosisServiceImpl 等)
-  - MinIO 文件存储集成
-  - JWT 认证工具
-
-#### 4.2 收敛到 emoon-infra 的基础设施
-
-**emoon-common 公共模块**:
-- `emoon-common-core` - 核心工具类、常量、异常定义
-- `emoon-common-doc` - SpringDoc 接口文档配置
-- `emoon-common-encrypt` - 数据加密解密 (AES、RSA、SM2、SM4)
-- `emoon-common-excel` - Excel 导入导出
-- `emoon-common-httpclient` - HTTP 客户端封装
-- `emoon-common-idempotent` - 幂等性控制
-- `emoon-common-job` - 定时任务配置
-- `emoon-common-json` - JSON 处理
-- `emoon-common-log` - 日志注解和切面
-
-**emoon-modules-api 模块**:
-- `emoon-system-api` - 系统服务 API
-- `emoon-mcp-api` - MCP 服务 API
-
----
-
-### 5. 文档更新 (9b5a845e)
-
-**新增文档**: `integrated-medical-agent-dify-card-solution.md`
-
-**内容**: 医疗智能体 Dify 卡片集成方案说明
-
----
-
-## 三、技术栈说明
-
-### 新增依赖
-- SSE 流式响应支持
-- 签名验证工具
-
-### 架构特点
-1. **分层架构**: Controller -> Service -> Mapper 清晰分层
-2. **工厂模式**: AgentEngineFactory 用于引擎实例创建
-3. **模块化设计**: 功能按模块划分,便于维护扩展
-4. **基础设施复用**: 通用功能收敛到 emoon-infra
-
----
-
-## 四、接口清单
-
-### 新增 Controller 接口
-
-| 模块 | Controller | 说明 |
-|------|------------|------|
-| emoon-system | AiAgentEngineConfigController | 引擎配置 CRUD |
-| emoon-openplatform | AgentController | 对话接口、会话管理 |
-
----
-
-## 五、数据库表结构
-
-### 新增表 (8 张)
-
-| 表名 | 说明 |
-|------|------|
-| ai_agent_app | AI 应用表 |
-| ai_agent_engine_config | 引擎配置表 |
-| ai_card_instance | 卡片实例表 |
-| ai_card_definition | 卡片定义表 |
-| ai_card_category | 卡片分类表 |
-| ai_card_plugin | 卡片插件表 |
-| ai_card_gray_config | 灰度配置表 |
-| ai_card_action_log | 操作日志表 |
-| ai_conversation | 对话记录表 |
-| ai_usage_log | 使用日志表 |
-
----
-
-## 六、后续建议
-
-1. **接口文档**: 建议为新增接口补充 Swagger 注解
-2. **单元测试**: 为 Service 层添加单元测试
-3. **权限控制**: 检查 Controller 接口的权限注解配置
-4. **日志规范**: 统一使用 @Log 注解记录操作日志
-
----
-
-**文档生成时间**: 2026-03-27  
-**生成工具**: QoderWork

+ 0 - 462
docs/ai-platform-deep-review.md

@@ -1,462 +0,0 @@
-# 医梦 AI 中台技术方案 —— 深层联动评审(第二轮)
-
-**评审日期**: 2026-05-23
-**评审方法**: 全文档逐章节联动分析 + 代码验证 + 架构一致性校验
-**评审范围**: v5.0 修订稿(1988 行)
-
----
-
-## 0. 修订质量评价
-
-GPT 的修订整体有效,吸收了我第一轮评审的核心意见:
-
-| 吸收的意见 | 新增章节 | 评价 |
-|-----------|---------|------|
-| 两套引擎抽象 | 2.4.1 | 处理策略明确,决策正确 |
-| 巨石控制器迁移 | 2.4.2 | 迁移路径清晰 |
-| AiAgent vs AiAgentApp | 2.4.3 | 设计态/运行态区分合理 |
-| Card Runtime 归属 | 2.4.4 | 服务边界表清晰 |
-| MCP 命名歧义 | 2.4.5 | 分阶段调整策略务实 |
-| Dify 降级 | 2.4.6 | 五项能力覆盖到位 |
-| 能力值套餐示例 | 10.15 | 有具体数字,可作为开发输入 |
-| 定价规则版本化 | 10.16 | 字段设计合理 |
-| 服务契约 | 10.17 | 输入/输出定义清晰 |
-| 审批工作流 | 10.18 | 状态机和审批类型表完备 |
-| 超量消费 | 10.19 | 策略明确 |
-| 计量事件枚举 | 10.20 | 覆盖全面 |
-| Outbox 表设计 | 13.2.5 | 字段完整 + 恢复机制 |
-| 工程交付保障 | 14.1-14.10 | 大幅补齐非功能需求 |
-
-关于 `AbstractEngineAdapter` 文件不存在的指正——经验证该文件确实存在于 `emoon-openplatform/src/main/java/com/emoon/openplatform/engine/adapter/AbstractEngineAdapter.java`。我第一轮评审在此处有误。
-
----
-
-## 1. 架构一致性:需要解决的矛盾
-
-### 1.1 Dify → OpenPlatform 的回调方向不明确
-
-**矛盾点**:第 4 节架构图显示 `Dify --> OpenAPI`(Dify 回调 OpenPlatform),但第 5.4 节时序图是标准的 request-response 模式。这两条线含义不同:
-
-- 如果 Dify 回调 OpenPlatform(如 MCP 工具调用回调):需要 OpenPlatform 暴露额外的回调端点,MCP Server 需要独立网络可达
-- 如果是纯 request-response:Dify 只是被调方,MCP 工具调用由 Dify 直接发起到 MCP Server
-
-当前文档在不同章节给出了相互矛盾的暗示:
-
-| 章节 | 描述 | 隐含的调用方向 |
-|------|------|---------------|
-| 4 节架构图 | `Dify --> OpenAPI` | Dify 回调 OpenPlatform |
-| 4.3 C4 Container | `Rel(dify, mcp, "调用 MCP 工具", "HTTP/MCP")` | Dify 直连 MCP Server |
-| 5.4 时序图 | `Agent->>Dify: 发送消息` `Dify-->>Agent: reply` | 标准 request-response |
-| 5.5 卡片动作时序 | `Card->>MCP: 调用标准工具` | Card Runtime 调 MCP(非 Dify 调 MCP) |
-
-**影响**:这决定了 MCP Server 的部署拓扑(独立进程在 Dify 可达网络中 vs 嵌入 OpenPlatform)、认证模型(Dify 如何认证 MCP Server?谁持有 HIS 凭证?)和审计链路。
-
-**建议**:在文档中明确画出两条独立的调用路径:
-1. **对话路径**:OpenPlatform → Dify(request-response),Dify 内部通过 HTTP/MCP 协议回调 OpenPlatform 托管的 MCP 工具(如果 MCP Server 嵌入 OpenPlatform),或直连独立部署的 MCP Server
-2. **卡片动作路径**:前端 → OpenPlatform(Card Runtime)→ MCP Server → HIS(此路径不经过 Dify)
-
-### 1.2 MCP Server 嵌入 vs 独立的决策矩阵缺失
-
-文档 14.3 节说"可先同进程,后独立"但没有给出决策条件。两种模式影响面差异巨大:
-
-| 维度 | 嵌入模式(同进程) | 独立模式(独立进程) |
-|------|------------------|---------------------|
-| Dify 调用 MCP | Dify → OpenPlatform HTTP 回调 | Dify → MCP Server 直连 |
-| 事务边界 | 卡片动作 + MCP 调用可在同一 DB 事务 | 分布式事务或最终一致 |
-| Outbox | 无需 Outbox(同事务写事件) | 需要 Outbox |
-| 认证 | MCP 工具复用 OpenPlatform 的鉴权上下文 | MCP Server 需要独立认证 |
-| 部署 | 简单,一个 JVM | 需要内网可达、服务发现 |
-| 性能 | 零网络开销 | 一次额外的 HTTP 调用 |
-
-**建议**:增加一个决策表,明确什么条件下从嵌入切换到独立模式(如:HIS 调用延迟 > X ms 影响主链路响应、某医院要求 MCP Server 部署在内网、多院并发超过单进程承载能力)。
-
-### 1.3 卡片动作回传 Dify 的机制缺失
-
-第 5.5 节时序图最后一步:
-```
-Card->>Dify: 可选:把动作结果作为下一轮上下文
-```
-
-这是一个关键但完全未定义的接口。卡片动作结果如何回传给 Dify?
-
-- **方案 A**:Card Runtime 调 Dify API,带上 `conversationId` + 新的 `inputs`(含卡片动作结果)
-- **方案 B**:Card Runtime 只更新 `ai_conversation` 的上下文 JSON,等下一次用户发消息时一起传给 Dify
-
-**方案 A 的问题**:
-- Dify 的 `/chat-messages` API 需要用户消息(`query` 字段必填),如果卡片动作不产生用户消息,怎么触发 Dify 的下一轮?
-- 如果 Dify API 设计上每个请求都是一个新消息,那"注入上下文"可能不是 Dify 的原生能力
-
-**方案 B 的问题**:
-- 如果用户不在卡片动作后立即发消息,上下文会丢失时效性
-- 如果用户在卡片动作后发了新消息,上下文如何合并?
-
-**建议**:明确选择方案并在文档中描述具体机制。建议方案 A + 定义 Dify 的 `inputs` 字段传递卡片动作结果。
-
----
-
-## 2. 数据一致性:深层风险
-
-### 2.1 API 入口缺少幂等键
-
-文档要求在卡片动作、MCP 写操作、计量事件、账本扣减层面做幂等,但 `POST /api/v1/agent/chat` 本身没有幂等键设计。
-
-**风险场景**:客户端发 `POST /api/v1/agent/chat` → 服务端成功处理并返回 → 响应在网络中丢失 → 客户端重试 → 服务端创建第二条重复会话、第二次 Dify 调用、第二个计量事件。
-
-虽然 `ai_usage_log` 不是财务账本,但重复的 Dify 调用会产生实际的 token 成本,且重复的计量事件会在三期造成重复计费。
-
-**建议**:
-1. 客户端在请求头中携带 `X-Emoon-Idempotency-Key`(或复用 `X-Emoon-Request-Id`)
-2. OpenPlatform 在处理前写 `idempotency_key + status(pending/processed)` 到 Redis 或通用幂等表
-3. 重复请求直接返回第一次的处理结果(可从幂等记录中查到 conversationId + messageId)
-
-### 2.2 CreditAccountService 缺少并发控制方案
-
-文档 10.14 节第 2 条说"账本更新必须事务内完成",但未说明如何防止 `available_credits` 的并发扣减。两个请求同时读取 `available_credits = 100`,各自冻结 80,都认为自己有足够余额。
-
-**需要补充**:
-- 账户余额更新使用乐观锁(`version` 字段)还是悲观锁(`SELECT ... FOR UPDATE`)?
-- 如果使用乐观锁,冻结失败时的重试策略?
-- 如果使用悲观锁,锁的粒度和超时?
-
-**建议**:在 `ai_credit_account` 表增加 `version` 字段,使用乐观锁。冻结操作在事务内:`UPDATE ai_credit_account SET available_credits = available_credits - ?, frozen_credits = frozen_credits + ?, version = version + 1 WHERE id = ? AND version = ? AND available_credits >= ?`。更新失败则重试或返回余额不足。
-
-### 2.3 定价规则与合同折扣的优先级未定义
-
-文档 10.1 节定义合同有"价格策略:标准价、折扣、超量价、阶梯价、赠送额度",10.4 节定义定价规则"按智能体配置标准能力值",10.16 节定义定价规则版本化。
-
-当合同折扣价和定价规则标准价冲突时,谁优先?
-
-**示例**:定价规则说"导诊问答 = 5 credits/次",合同说"导诊问答享受 7 折"。最终扣费应该是 3.5 credits 还是 5 credits?
-
-**建议**:明确定价优先级链:
-1. 合同级折扣/加价(最高优先级)
-2. 定价规则(基准价)
-3. 平台默认值(兜底)
-
-并在 `PricingService` 的输入中增加 `contractId`,在输出中记录"应用的折扣规则"。
-
-### 2.4 计量事件的 status 字段与账本一致性
-
-文档 10.3 节计量事件 status 为 `pending / settled / reversed / failed`,10.10 节扣费时序中 MeterEventService 调用 PricingService 后再调用 CreditAccountService。
-
-如果 PricingService 计算成功但 CreditAccountService.settle() 失败(比如乐观锁冲突),计量事件的状态是什么?是 `pending`(等待重试)还是 `failed`(标记为失败但后续人工处理)?
-
-**建议**:计量事件增加中间状态 `pricing_completed`,区分"价格已计算"和"账本已扣减"。账本扣减失败时事件停留在 `pricing_completed`,等待重试;重试上限后进入 `failed` 需要人工介入。
-
----
-
-## 3. Dify 集成的关键空白
-
-### 3.1 Dify 输出中卡片数据的格式契约未定义
-
-这是 `DifyAgentEngine` 实现的核心输入。文档 5.1 节说"Dify 返回文本、结构化卡片数据",但没有定义 Dify 如何返回卡片数据。
-
-Dify Workflow 的 `/chat-messages` API 响应格式为:
-```json
-{
-  "event": "message",
-  "answer": "...",
-  "metadata": {
-    "usage": {...}
-  }
-}
-```
-
-卡片数据可能出现在:
-- `answer` 字段中作为 JSON 嵌入文本
-- `metadata` 中的自定义字段
-- Dify Workflow 的 `outputs` 字段
-
-**建议**:明确约定——Dify Workflow 的最后一个节点返回结构化 JSON,包含 `reply`、`cardKey`、`cardData`、`riskLevel`(可选)字段。`DifyAgentEngine` 解析 Dify 响应中的 JSON 并映射到 `AgentResponse`。
-
-### 3.2 流式响应中卡片数据的传输时机
-
-Dify SSE 流式响应逐个 token 返回 `answer`。如果卡片数据嵌入在 `answer` 中,DifyAgentEngine 必须:
-- 缓存所有流式片段
-- 在流结束后(`event: message_end`)解析完整 JSON
-- 提取 `cardKey/cardData` 并填充到最终的 `AgentResponse`
-
-但文档 5.4 节时序图中,`Dify-->>Agent: reply、usage、cardKey、cardData` 似乎暗示这些是一次性返回的。在 SSE 场景下这不成立。
-
-**建议**:明确流式场景下的卡片处理策略:
-1. DifyAgentEngine 缓存所有 `answer` 片段
-2. 在收到 `message_end` 事件后解析完整输出
-3. 如果输出是有效的卡片 JSON,在最后一个 `AgentResponse` 中设置 `finished=true` + `cardKey/cardData`
-4. 中间流式块只携带增量文本
-
-### 3.3 Dify 多应用路由机制缺失
-
-一个项目可能有多个智能体,每个智能体可能对应不同的 Dify App。`AiAgentEngineConfig.config_json` 存储 Dify 连接信息。但文档没有说明:
-
-- `config_json` 中 Dify 相关字段的标准结构(`apiBaseUrl`、`apiKey`、`appId`?)
-- 同一个智能体是否可以绑定多个 Dify App(如 A/B 测试、灰度)
-- 如果 Dify App 被删除或 API Key 失效,系统如何检测和告警
-
-**建议**:在 5.1 或 6.2 节增加 `config_json` 的 Dify 配置标准 schema:
-```json
-{
-  "dify": {
-    "baseUrl": "https://dify.example.com",
-    "apiKey": "app-xxxxx",
-    "appId": "xxx",
-    "timeout": {"connect": 30, "read": 60, "sse": 300}
-  }
-}
-```
-
-### 3.4 DirectLLMAgentEngine 完全缺失
-
-文档多次提到 `DirectLLMAgentEngine`(架构图、路由层、实现拆解表),但从未描述它的设计:
-- 直连模型如何生成卡片?直连 LLM 没有 Workflow 能力,如何返回结构化 cardKey/cardData?
-- 直连模型是否需要 Function Calling 来替代 MCP 工具调用?
-- 直连模型的 system prompt 来自哪里?是 `AiAgentApp.systemPrompt` 还是知识模块的模板?
-
-**建议**:至少用一页描述 DirectLLMAgentEngine 的核心设计:
-1. 使用 `AiAgentApp.systemPrompt` 作为 system message
-2. 支持 OpenAI-compatible Function Calling 来调用 MCP 工具(通过 OpenPlatform 代理,不直连)
-3. 卡片生成通过 prompt engineering(要求模型输出特定 JSON 格式)
-4. 适用场景:简单 FAQ、不需要多步 Workflow 的问答
-
----
-
-## 4. 跨模块耦合问题
-
-### 4.1 OpenPlatform 的架构角色过重
-
-文档从 v4 的"Dify 做总控,OpenPlatform 只渲染"改为了 v5 的"OpenPlatform 掌握合同、额度、计量、账单、审计、风控、卡片实例状态、租户权限和系统可观测性"。
-
-但审视实际调用链:
-```
-Client → OpenPlatform(auth) → AgentEngine(route) → DifyAPI(proxy) → Dify(business logic) → MCP(data access) → HIS
-```
-
-OpenPlatform 除了 auth + proxy + 事后记账,实际的业务决策都在 Dify 中。OpenPlatform 承担了大量职责但实际介入程度有限——它更多是"在旁边看着"而不是"主动参与"。
-
-**潜在问题**:
-- OpenPlatform 变成了一个承担 10+ 职责的"上帝模块"
-- 初级工程师可能把所有逻辑都写进 openplatform,导致模块膨胀
-- 测试复杂度高——要测 openplatform 的一个功能需要 mock Dify、MCP、Card、Metering、Account 全部依赖
-
-**建议**:在工程实现中严格遵守第 10.9 节的服务分层原则。OpenPlatform 中的 `AgentChatServiceImpl` 只做编排(调 A → 调 B → 调 C),具体逻辑在独立 Service 中。
-
-### 4.2 计量耦合到每个组件
-
-文档要求 OpenPlatform、DifyAdapter、MCP Server、Card Runtime 都产生计量事件。这意味着:
-- 每个组件都要依赖 `MeterEventService`(或 OutboxEventService)
-- 每个组件都要理解计量上下文
-- 计费规则的变更可能影响所有组件
-
-**更解耦的方案**(供参考):
-- 各组件只产生原始业务事件(`ConversationCompleted`、`ToolCalled`、`CardActionExecuted`)
-- 统一的计量处理器监听业务事件,按规则转换为计量事件
-- 计费规则变更只影响计量处理器,不影响业务组件
-
-但要注意——这引入了额外的事件处理延迟和复杂度。文档当前方案(组件直接产生计量事件)在 MVP 阶段更简单,但长期维护成本高。建议在文档中承认这个 tradeoff。
-
-### 4.3 知识模块与 Dify 的关系未定义
-
-C4 Container 图(4.3)中包含 `emoon-knowledge-api`(知识库、提示词、质控),但全文没有描述:
-- Dify 是否直接使用 emoon 的 Weaviate 向量库,还是 Dify 自带知识库?
-- `PromptTemplate` 是给 DirectLLMAgentEngine 用还是传给 Dify?
-- 质控规则(`QualityControlRule`)由谁执行?Dify 输出后 OpenPlatform 后置检查?
-
-**建议**:在 4.4 或新增一节明确:
-1. Dify 使用自己的知识库能力(RAG 节点内置),不需要调用 emoon 的 Weaviate
-2. PromptTemplate 用于 DirectLLMAgentEngine 和传给 Dify 的 `inputs` 字段
-3. 质控规则在 OpenPlatform 中后置执行——Dify 返回后,OpenPlatform 调 `QualityControlService.check()` 校验输出
-
----
-
-## 5. 工程实施层面未解决的问题
-
-### 5.1 SSE 连接生命周期管理
-
-文档提到了 SSE(第 5.1 节、7.1 节),但未涉及:
-- SSE 连接的最大存活时间(当前 Mock 引擎无超时,Dify SSE 默认 read timeout 300s)
-- 客户端断开后如何清理(当前 `SseEmitter.completeWithError()` 只处理异常)
-- 同一个用户是否可以同时有多个 SSE 连接(多 tab、多设备)
-- 长时间无活动的 SSE 连接如何处理(心跳?超时断开?)
-
-**建议**:在方案中定义:
-1. SSE 连接最大空闲时间 5 分钟,超时自动关闭
-2. 服务端每 30 秒发一个空注释 (`: heartbeat`) 维持连接
-3. 同一用户同时最多 5 个 SSE 连接
-4. 客户端断开后,通过 `SseEmitter.onCompletion()` 清理资源
-
-### 5.2 缓存策略完全缺失
-
-文档提到 Redis 用于"限流、幂等、热点配置、短锁",但对于以下高频读取场景没有缓存设计:
-- 卡片定义(每次对话都可能查询)
-- 智能体配置(每次对话都查询)
-- 引擎配置(每次对话都查询)
-- 医院科室字典(MCP 工具查询频率高)
-
-**建议**:增加缓存策略:
-1. 卡片定义、智能体配置、引擎配置:本地缓存(Caffeine)+ Redis 通知失效
-2. 医院科室、医生字典:Redis 缓存,TTL 按医院配置(通常 5-30 分钟)
-3. 定价规则:本地缓存,仅在规则版本变更时失效
-
-### 5.3 多模态文件上传路径
-
-文档 10.3 节提到语音、图像、文书服务产生计量事件,但未描述文件如何在系统中流转:
-- 患者在小程序录音 → 音频文件上传到哪里(OSS?临时目录?)
-- OpenPlatform 把文件 URL 传给 Dify,还是 Dify 直接接受文件上传?
-- Dify 的文件上传 API 需要 `multipart/form-data`,OpenPlatform 是中转还是直传?
-
-**建议**:明确文件流程:
-1. 客户端上传文件到 OSS(`emoon-common-oss`),获取 URL
-2. 如果是 Dify 处理的文件,OpenPlatform 调 Dify `/files/upload` API 中转(Dify 需要自己的文件引用)
-3. 如果是 DirectLLM 处理的文件(如图像分析),URL 直接放在请求中传给模型
-4. 文件访问 URL 带有时效性签名,过期后不可访问
-
-### 5.4 并发锁的 Redis 故障降级
-
-文档 14.7 节定义了并发的 Redis 锁粒度,但未说明 Redis 不可用时的降级:
-- 锁号时 Redis 连接超时——是拒绝请求还是降级到数据库锁?
-- 卡片动作的幂等键依赖 Redis 还是数据库唯一约束?
-
-**建议**:明确降级策略——所有 Redis 锁都有数据库唯一约束或幂等记录兜底。Redis 不可用时,数据库层面仍能保证不重复操作(性能下降但不丢数据)。
-
----
-
-## 6. 三期计费深层问题
-
-### 6.1 冻结-结算的超时处理
-
-文档 10.5 节定义了 `freeze → settle` 流程,10.11 节账本状态机有 `frozen → released` 路径,但未定义:
-- 冻结后多久未结算自动释放?(冻结金额长期占用可用余额)
-- 谁负责检测超时冻结并释放?(定时任务?ScheduledExecutorService?)
-- 释放前是否需要确认源任务确实已失败?
-
-**建议**:
-1. `ai_credit_ledger` 冻结记录增加 `expire_at` 字段
-2. SnailJob 定时任务每分钟扫描过期的冻结记录
-3. 释放前通过 `source_id` 查询源任务状态,确认已失败或未完成
-4. 如果源任务状态不确定(如 HIS pending),不自动释放,转人工处理
-
-### 6.2 部分结算的缺失
-
-文档 10.5 节语音电话的"分段冻结,按实际分钟结算"暗示了部分结算需求——冻结 10 分钟额度,实际只用了 6 分钟。但没有部分结算的账本操作类型。
-
-当前的 `settle` 是全量结算。部分结算需要:
-- `settle_partial(freezeId, actualCredits)` → 扣除已用部分
-- `release(freezeId, remainingCredits)` → 释放剩余部分
-
-**建议**:明确 `settle` 操作支持部分结算,在账本流水中同时记录 `settle(实际消耗)` 和 `release(冻结-实际)` 两条流水。
-
-### 6.3 套餐包的过期未用完额度处理
-
-文档 10.2 节账户有 `valid_from/valid_to` 有效期,10.1 节合同有生命周期,但未定义:
-- 合同到期时,账户中剩余的 `available_credits` 如何处理?
-- 是清零?延期?还是按比例退款?
-- 账本中如何体现"过期"——新操作类型 `expire`?
-
-**建议**:增加 `expire` 账本操作类型。合同到期时:`available_credits → 0`,同时写 `expire` 流水记录失效的额度。
-
-### 6.4 能力值账户的子账户层次过深
-
-文档 10.2 节定义了"区域总账户 → 医院子账户"的二级层次。但实际可能需要更多层级(区域 → 医院 → 院区 → 科室)。
-
-**潜在问题**:层级越深,额度分配和汇总越复杂。`ai_credit_account.parent_account_id` 支持无限层级,但业务逻辑需要限制。
-
-**建议**:限制子账户最多 2 层(主账户 → 科室预算账户),区域能力池作为独立账户类型不参与父子层级。
-
----
-
-## 7. 遗漏的关键跨场景联动
-
-### 7.1 用户取消操作的全链路回滚
-
-用户在卡片上点击"挂号"后关闭了小程序——系统需要:
-1. 卡片状态从 `submitted`/`processing` 变为 `cancelled`
-2. MCP 工具调用需要取消或补偿(如果 HIS 已经锁号,需要释放)
-3. 已冻结的能力值需要释放
-4. 计量事件可能需要标记为 `reversed`
-
-文档 5.6 节卡片状态机有 `cancelled` 状态,12.6 节有异常处理表,但没有**用户主动取消**这条路径的端到端流程。
-
-**建议**:增加用户取消的全链路流程图。
-
-### 7.2 Dify Workflow 中途失败的恢复
-
-Dify Workflow 执行到第 3 步(已调 MCP 锁号)后第 4 步(创建挂号记录)失败:
-- Dify 返回什么?Workflow 的失败信息还是部分结果?
-- OpenPlatform 如何知道"锁号成功但挂号失败"?
-- 已锁的号源谁来释放?MCP 补偿操作还是人工?
-
-**建议**:在 Dify Workflow 设计规范中约定——失败时在 outputs 中返回 `{"status": "failed", "completedSteps": ["lock_schedule"], "failedStep": "create_appointment", "error": "..."}`。OpenPlatform 根据 `completedSteps` 决定是否需要调补偿操作。
-
-### 7.3 计量事件的 idempotency_key 跨组件一致性
-
-文档要求所有计量事件都有 `idempotency_key`,但同一笔业务操作可能产生多个组件的事件。例如一次挂号操作:
-- Card Runtime 产生一个 `CARD_ACTION_CONFIRMED` 事件
-- MCP Server 产生一个 `MCP_TOOL_WRITE` 事件
-- 这两个事件应该是同一个计费事件(只扣一次能力值),还是分开计费?
-
-如果是同一个事件,需要用同一个 `idempotency_key` 串联,但 `source_type` 不同。文档的计量事件枚举没有区分"组合事件"和"原子事件"。
-
-**建议**:定义计量事件的合并规则——同一 `traceId` 下的卡片动作和 MCP 写操作,MCP 事件携带卡片动作的 `source_id`,计量处理器按业务规则合并(卡片动作为主事件,MCP 为明细)。
-
----
-
-## 8. 对文档结构的建议
-
-### 8.1 缺少术语定义表
-
-文档中大量使用术语但没有集中定义。初级工程师会在以下术语间混淆:
-- Agent / 智能体 / AiAgentApp / AiAgent
-- MCP / Model Context Protocol / Legacy MCP Proxy / Agent Core
-- 能力值 / credits / token
-- 项目 / projectId / 租户 / tenantId
-- 会话 / conversationId / externalConversationId
-
-**建议**:在文档开头增加术语表(附录形式)。
-
-### 8.2 缺少决策记录(ADR)
-
-文档中有多处关键设计决策(如"为什么能力值用 Decimal"、"为什么账本从账本汇总不从日志汇总"),这些决策的 WHY 分散在各处。
-
-**建议**:在单独的 `docs/adr/` 目录下维护 ADR,方案文档中引用即可。
-
----
-
-## 9. 最终评价与优先级
-
-### 文档整体质量
-
-v5.0 修订稿是一份**高质量的生产级方案**,结构清晰,覆盖面广,工程可操作性强。GPT 吸收一审意见后,基本补齐了缺失的非功能需求(测试、部署、安全、灾备、监控)和业务细节(套餐示例、事件枚举、审批流、服务契约)。
-
-### 需在编码前解决的阻塞级问题(P0)
-
-| # | 问题 | 影响 |
-|---|------|------|
-| 1 | Dify 输出中卡片数据的格式契约未定义 | DifyAgentEngine 无法实现 |
-| 2 | 流式响应中卡片数据的传输时机未明确 | DifyAgentEngine SSE 处理无法实现 |
-| 3 | Dify 调用 MCP 的网络路径不明确(直连 vs 回调 OpenPlatform) | 部署拓扑和认证模型无法确定 |
-| 4 | 卡片动作回传 Dify 的机制缺失 | 多轮对话的卡片-对话闭环无法实现 |
-| 5 | API 入口缺少幂等键 | 网络重试导致重复调用、重复计费 |
-
-### 需在编码前解决的高优先级问题(P1)
-
-| # | 问题 |
-|---|------|
-| 6 | CreditAccountService 缺少并发控制方案(乐观锁/悲观锁选择) |
-| 7 | 定价规则与合同折扣的优先级未定义 |
-| 8 | 冻结超时自动释放机制未定义 |
-| 9 | 部分结算(冻结 > 实际消耗)的账本操作未定义 |
-| 10 | Dify 配置的标准 JSON schema 未定义 |
-| 11 | SSE 连接生命周期管理未定义 |
-| 12 | 缓存策略缺失 |
-| 13 | 多模态文件上传路径未定义 |
-
-### 需在编码前澄清的设计选择(P2)
-
-| # | 问题 |
-|---|------|
-| 14 | DirectLLMAgentEngine 设计缺失 |
-| 15 | 知识模块与 Dify 的关系未定义 |
-| 16 | MCP Server 嵌入 vs 独立的切换条件 |
-| 17 | 合同到期后剩余额度的处理 |
-| 18 | 账户子账户层级深度限制 |
-| 19 | 用户主动取消的全链路回滚流程 |
-| 20 | Dify Workflow 中途失败的补偿机制 |

+ 0 - 707
docs/ai-platform-implementation-guide.md

@@ -1,707 +0,0 @@
-# AI 开放平台实现记录文档
-
-> 基于 `integrated-medical-agent-dify-card-solution.md` 第五章(数据库设计)和第十八章(工程模块设计)的实现成果。
-
----
-
-## 一、实现范围总览
-
-本次实现分三个阶段完成,共涉及 **43 个文件**(新建 39 + 修改 4)。
-
-| 阶段 | 内容 | 模块 | 文件数 |
-|------|------|------|--------|
-| Phase 1 | AgentEngine 引擎框架 + DO + Mapper + Service 接口 | mcp-api / system-api | 33 |
-| Phase 2 | OpenPlatform 对话 + 会话管理 API | openplatform | 10(新建) + 2(修改) |
-| Phase 3 | Admin 端引擎配置 CRUD 接口 | system-api / emoon-system | 4(新建) + 2(修改) |
-
----
-
-## 二、Phase 1 - AgentEngine 引擎框架与基础设施
-
-### 2.1 AgentEngine 可插拔引擎框架
-
-位于 `emoon-infra/emoon-modules-api/emoon-mcp-api/src/main/java/com/emoon/mcp/engine/`
-
-| 文件 | 说明 |
-|------|------|
-| `AgentEngine.java` | 引擎核心接口,定义 `chat()`(同步)、`chatStream()`(流式)、`getEngineType()`(路由标识) |
-| `AgentRequest.java` | 统一请求对象,包含 agentId、query、conversationId、engineConfig、inputs、files |
-| `AgentResponse.java` | 统一响应对象,包含 reply、cardKey、cardData、conversationId、messageId、usage、finished |
-| `AgentEngineFactory.java` | 引擎工厂,Spring 启动时自动收集所有 AgentEngine Bean,按 `getEngineType()` 路由 |
-
-**路由机制**:
-```
-AgentEngineFactory.getEngine("dify")  -> DifyAgentEngine(待实现)
-AgentEngineFactory.getEngine("mock")  -> MockAgentEngine(已有骨架)
-AgentEngineFactory.getEngine("direct") -> DirectLLMAgentEngine(待实现)
-```
-
-### 2.2 DO 对象(8 个 AI 相关表)
-
-#### system-api 模块(6 个)
-
-位于 `emoon-infra/emoon-modules-api/emoon-system-api/src/main/java/com/emoon/system/domain/`
-
-| DO 类 | 对应表 | 关键字段 | 说明 |
-|-------|--------|----------|------|
-| `AiAgentEngineConfig` | ai_agent_engine_config | id, configName, engineType, configJson, status | 引擎连接配置(baseUrl/apiKey 等存在 configJson 中) |
-| `AiAgentApp` | ai_agent_app | id, agentId, agentName, agentType, engineConfigId | 智能体元数据 |
-| `AiUsageLog` | ai_usage_log | agentId, engineType, promptTokens, totalTokens | 调用日志 |
-| `AiCardCategory` | ai_card_category | categoryKey, name, parentId, sortOrder | 卡片分类(树形) |
-| `AiCardDefinition` | ai_card_definition | cardKey, version, schemaJson, uiConfigJson | 卡片 UI 模板 |
-| `AiCardPlugin` | ai_card_plugin | pluginId, name, version, manifestJson, auditStatus | 第三方卡片插件 |
-
-#### mcp-api 模块(2 个)
-
-位于 `emoon-infra/emoon-modules-api/emoon-mcp-api/src/main/java/com/emoon/mcp/domain/`
-
-| DO 类 | 对应表 | 关键字段 | 说明 |
-|-------|--------|----------|------|
-| `AiConversation` | ai_conversation | conversationId, agentId(Long), engineType, status | 会话记录 |
-| `AiCardInstance` | ai_card_instance | instanceId, cardKey, cardVersion, stateJson | 卡片实例 |
-
-**所有 DO 的共同特征**:
-- 继承 `TenantEntity`(含 tenantId)-> `BaseEntity`(含 createBy/updateBy/createTime/updateTime)
-- `delFlag`、`status` 字段类型为 `String`("0"/"1"),不是 int
-- 日期字段使用 `java.util.Date`
-- `agentId` 在 `AiConversation` 中是 `Long` 类型(FK 到 ai_agent_app.id),不是 String 业务 ID
-
-### 2.3 Mapper 接口(10 个)
-
-| Mapper | 泛型 | 模块 |
-|--------|------|------|
-| `AiAgentEngineConfigMapper` | `BaseMapperPlus<AiAgentEngineConfig, AiAgentEngineConfigVo>` | system-api |
-| `AiAgentAppMapper` | `BaseMapperPlus<AiAgentApp, AiAgentApp>` | system-api |
-| `AiUsageLogMapper` | `BaseMapperPlus<AiUsageLog, AiUsageLog>` | system-api |
-| `AiCardCategoryMapper` | `BaseMapperPlus<AiCardCategory, AiCardCategory>` | system-api |
-| `AiCardDefinitionMapper` | `BaseMapperPlus<AiCardDefinition, AiCardDefinition>` | system-api |
-| `AiCardActionLogMapper` | `BaseMapperPlus<AiCardActionLog, AiCardActionLog>` | system-api |
-| `AiCardPluginMapper` | `BaseMapperPlus<AiCardPlugin, AiCardPlugin>` | system-api |
-| `AiCardGrayConfigMapper` | `BaseMapperPlus<AiCardGrayConfig, AiCardGrayConfig>` | system-api |
-| `AiConversationMapper` | `BaseMapperPlus<AiConversation, AiConversation>` | mcp-api |
-| `AiCardInstanceMapper` | `BaseMapperPlus<AiCardInstance, AiCardInstance>` | mcp-api |
-
-### 2.4 Service 接口(9 个)
-
-位于 `emoon-infra/emoon-modules-api/emoon-system-api/src/main/java/com/emoon/system/service/`
-
-每个 Service 接口提供标准的 `queryById`、`queryList`、`insert`、`update`、`deleteWithValidByIds` 方法。
-
----
-
-## 三、Phase 2 - OpenPlatform 对话与会话管理 API
-
-### 3.1 文件清单
-
-位于 `emoon-openplatform/src/main/java/com/emoon/openplatform/`
-
-| 路径 | 说明 |
-|------|------|
-| `domain/dto/request/AgentChatRequest.java` | 对话请求 DTO |
-| `domain/dto/request/ConversationCreateRequest.java` | 创建会话请求 DTO |
-| `domain/dto/resp/AgentChatResponse.java` | 对话响应 DTO(同步 + SSE 共用) |
-| `domain/dto/resp/ConversationVo.java` | 会话视图对象 |
-| `util/SignUtil.java` | 签名验证工具(MD5,5 分钟有效期) |
-| `service/IConversationService.java` | 会话管理 Service 接口 |
-| `service/IAgentChatService.java` | 对话 Service 接口 |
-| `service/impl/ConversationServiceImpl.java` | 会话管理实现 |
-| `service/impl/AgentChatServiceImpl.java` | 对话核心编排实现 |
-| `controller/v1/AgentController.java` | REST 控制器(4 个接口) |
-
-**修改的文件**:
-| 路径 | 变更 |
-|------|------|
-| `pom.xml` | 新增 emoon-mcp-api、emoon-system-api 依赖 |
-| `enums/SystemEnum.java` | 新增 4 个错误码(AGENT_NOT_FOUND/AGENT_DISABLED/ENGINE_CONFIG_NOT_FOUND/CONVERSATION_NOT_FOUND) |
-
-### 3.2 认证机制
-
-所有 OpenPlatform API 使用请求头签名认证:
-
-| Header | 说明 |
-|--------|------|
-| `X-Emoon-Timestamp` | 毫秒时间戳 |
-| `X-Emoon-Request-Id` | 请求唯一标识(UUID) |
-| `X-Emoon-Public-Key` | 项目公钥(查 sys_project 表) |
-| `X-Emoon-Sign` | 签名值 |
-
-**签名算法**:
-```
-sign = MD5(requestId + "&" + timestamp + "&" + payload + "&" + privateKey).toLowerCase()
-```
-- `payload`:POST 请求为 JSON body 字符串,GET 请求为空字符串
-- 时间戳有效期 5 分钟
-
-### 3.3 API 接口定义
-
-#### 3.3.1 POST /api/v1/agent/chat - 智能体对话
-
-支持同步和 SSE 流式两种模式,由 `stream` 字段控制。
-
-**请求体**:
-```json
-{
-  "agentId": "agent_123456",
-  "query": "帮我挂个内科的号",
-  "conversationId": null,
-  "stream": false,
-  "userId": 10001,
-  "inputs": {},
-  "files": []
-}
-```
-
-**同步响应**(`stream=false`):
-```json
-{
-  "code": 200,
-  "msg": "操作成功",
-  "data": {
-    "reply": "好的,我来帮您挂内科的号。请问您想挂哪个医生?",
-    "cardKey": null,
-    "cardData": null,
-    "conversationId": "conv_a1b2c3d4-xxxx",
-    "messageId": "msg_x1y2z3",
-    "usage": {
-      "promptTokens": 120,
-      "completionTokens": 45,
-      "totalTokens": 165
-    },
-    "finished": true
-  }
-}
-```
-
-**SSE 流式响应**(`stream=true`):
-
-返回 `text/event-stream`,每个事件的 data 字段是一个 `AgentChatResponse` JSON:
-
-```
-data: {"reply":"好的","conversationId":"conv_xxx","messageId":"msg_xxx","finished":false}
-
-data: {"reply":",我来帮您","conversationId":"conv_xxx","messageId":"msg_xxx","finished":false}
-
-data: {"reply":"挂内科的号。","conversationId":"conv_xxx","messageId":"msg_xxx","usage":{"promptTokens":120,"completionTokens":45,"totalTokens":165},"finished":true}
-```
-
-#### 3.3.2 POST /api/v1/conversation/create - 创建会话
-
-**请求体**:
-```json
-{
-  "agentId": "agent_123456",
-  "userId": 10001,
-  "conversationName": "挂号咨询"
-}
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功",
-  "data": {
-    "conversationId": "conv_a1b2c3d4-xxxx",
-    "agentId": 1,
-    "conversationName": "挂号咨询",
-    "status": "active",
-    "messageCount": 0,
-    "totalTokens": 0,
-    "lastMessageTime": null,
-    "createTime": "2026-03-26T12:00:00.000+00:00"
-  }
-}
-```
-
-#### 3.3.3 GET /api/v1/conversation/list - 查询会话列表
-
-**请求参数**(Query String):
-| 参数 | 类型 | 必填 | 说明 |
-|------|------|------|------|
-| agentId | String | 否 | 按智能体筛选 |
-| userId | Long | 否 | 按用户筛选 |
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功",
-  "data": [
-    {
-      "conversationId": "conv_a1b2c3d4-xxxx",
-      "agentId": 1,
-      "conversationName": "挂号咨询",
-      "status": "active",
-      "messageCount": 5,
-      "totalTokens": 820,
-      "lastMessageTime": "2026-03-26T12:05:00.000+00:00",
-      "createTime": "2026-03-26T12:00:00.000+00:00"
-    }
-  ]
-}
-```
-
-#### 3.3.4 GET /api/v1/conversation/{conversationId} - 查询会话详情
-
-**路径参数**:
-| 参数 | 说明 |
-|------|------|
-| conversationId | 会话 UUID |
-
-**响应**:与列表中单个元素结构相同。
-
----
-
-## 四、Phase 3 - Admin 端引擎配置 CRUD
-
-### 4.1 文件清单
-
-**新建文件**:
-
-| 路径 | 说明 |
-|------|------|
-| `system-api/.../domain/bo/AiAgentEngineConfigBo.java` | 业务对象,`@AutoMapper` + 入参校验 |
-| `system-api/.../domain/vo/AiAgentEngineConfigVo.java` | 视图对象,`@AutoMapper` 自动转换 |
-| `system-api/.../service/impl/AiAgentEngineConfigServiceImpl.java` | Service 实现(分页/增/改/删/唯一校验) |
-| `emoon-system/.../controller/system/AiAgentEngineConfigController.java` | REST 控制器 |
-
-**修改文件**:
-
-| 路径 | 变更 |
-|------|------|
-| `system-api/.../service/IAiAgentEngineConfigService.java` | 重构为 Bo/Vo 模式,增加分页查询 |
-| `system-api/.../mapper/AiAgentEngineConfigMapper.java` | 第二泛型参数改为 `AiAgentEngineConfigVo` |
-
-### 4.2 API 接口定义
-
-基础路径:`/system/ai/engineConfig`
-
-所有接口需要 Sa-Token 权限认证(管理后台登录态)。
-
-#### 4.2.1 GET /system/ai/engineConfig/list - 分页查询
-
-**请求参数**(Query String):
-
-| 参数 | 类型 | 必填 | 说明 |
-|------|------|------|------|
-| configName | String | 否 | 模糊匹配 |
-| engineType | String | 否 | 精确匹配:dify / direct / spring_ai / mock |
-| status | String | 否 | 精确匹配:0=启用 1=停用 |
-| projectId | Integer | 否 | 精确匹配 |
-| pageNum | Integer | 是 | 页码 |
-| pageSize | Integer | 是 | 每页大小 |
-
-**权限**:`ai:engineConfig:list`
-
-**cURL 示例**:
-```bash
-curl -X GET 'http://localhost:8080/system/ai/engineConfig/list?pageNum=1&pageSize=10&engineType=dify' \
-  -H 'Authorization: Bearer <token>'
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功",
-  "rows": [
-    {
-      "id": 1,
-      "projectId": 1,
-      "configName": "导诊助手-Dify配置",
-      "engineType": "dify",
-      "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}",
-      "status": "0",
-      "createTime": "2026-03-26T12:00:00.000+00:00",
-      "updateTime": "2026-03-26T12:00:00.000+00:00"
-    }
-  ],
-  "total": 1
-}
-```
-
-#### 4.2.2 GET /system/ai/engineConfig/listAll - 不分页查询
-
-用于下拉选择框。参数同 list 但不需要 pageNum/pageSize。
-
-**权限**:`ai:engineConfig:list`
-
-**cURL 示例**:
-```bash
-curl -X GET 'http://localhost:8080/system/ai/engineConfig/listAll?engineType=dify' \
-  -H 'Authorization: Bearer <token>'
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功",
-  "data": [
-    {
-      "id": 1,
-      "projectId": 1,
-      "configName": "导诊助手-Dify配置",
-      "engineType": "dify",
-      "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}",
-      "status": "0",
-      "createTime": "2026-03-26T12:00:00.000+00:00",
-      "updateTime": "2026-03-26T12:00:00.000+00:00"
-    }
-  ]
-}
-```
-
-#### 4.2.3 GET /system/ai/engineConfig/{id} - 查询详情
-
-**权限**:`ai:engineConfig:query`
-
-**cURL 示例**:
-```bash
-curl -X GET 'http://localhost:8080/system/ai/engineConfig/1' \
-  -H 'Authorization: Bearer <token>'
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功",
-  "data": {
-    "id": 1,
-    "projectId": 1,
-    "configName": "导诊助手-Dify配置",
-    "engineType": "dify",
-    "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}",
-    "status": "0",
-    "createTime": "2026-03-26T12:00:00.000+00:00",
-    "updateTime": "2026-03-26T12:00:00.000+00:00"
-  }
-}
-```
-
-#### 4.2.4 POST /system/ai/engineConfig - 新增配置
-
-**权限**:`ai:engineConfig:add`
-
-**cURL 示例(Dify 类型)**:
-```bash
-curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
-  -H 'Authorization: Bearer <token>' \
-  -H 'Content-Type: application/json' \
-  -d '{
-    "projectId": 1,
-    "configName": "导诊助手-Dify配置",
-    "engineType": "dify",
-    "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}",
-    "status": "0"
-  }'
-```
-
-**cURL 示例(Direct 直连大模型类型)**:
-```bash
-curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
-  -H 'Authorization: Bearer <token>' \
-  -H 'Content-Type: application/json' \
-  -d '{
-    "projectId": 1,
-    "configName": "GPT-4o直连配置",
-    "engineType": "direct",
-    "configJson": "{\"baseUrl\":\"https://api.openai.com/v1\",\"apiKey\":\"sk-xxx\",\"model\":\"gpt-4o\"}",
-    "status": "0"
-  }'
-```
-
-**cURL 示例(SpringAI 类型)**:
-```bash
-curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
-  -H 'Authorization: Bearer <token>' \
-  -H 'Content-Type: application/json' \
-  -d '{
-    "projectId": 1,
-    "configName": "SpringAI配置",
-    "engineType": "spring_ai",
-    "configJson": "{\"baseUrl\":\"https://api.openai.com/v1\",\"apiKey\":\"sk-xxx\",\"model\":\"gpt-4o\",\"temperature\":0.7,\"maxTokens\":2000}",
-    "status": "0"
-  }'
-```
-
-**cURL 示例(Mock 测试类型)**:
-```bash
-curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
-  -H 'Authorization: Bearer <token>' \
-  -H 'Content-Type: application/json' \
-  -d '{
-    "projectId": 1,
-    "configName": "本地Mock配置",
-    "engineType": "mock",
-    "configJson": "{\"mockResponse\":\"我是模拟回复,用于开发测试\"}",
-    "status": "0"
-  }'
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功"
-}
-```
-
-#### 4.2.5 PUT /system/ai/engineConfig - 修改配置
-
-**权限**:`ai:engineConfig:edit`
-
-**cURL 示例**:
-```bash
-curl -X PUT 'http://localhost:8080/system/ai/engineConfig' \
-  -H 'Authorization: Bearer <token>' \
-  -H 'Content-Type: application/json' \
-  -d '{
-    "id": 1,
-    "configName": "导诊助手-Dify配置(更新)",
-    "engineType": "dify",
-    "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-new-key-456\"}"
-  }'
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功"
-}
-```
-
-#### 4.2.6 DELETE /system/ai/engineConfig/{ids} - 批量删除
-
-**权限**:`ai:engineConfig:remove`
-
-**cURL 示例(单个删除)**:
-```bash
-curl -X DELETE 'http://localhost:8080/system/ai/engineConfig/1' \
-  -H 'Authorization: Bearer <token>'
-```
-
-**cURL 示例(批量删除)**:
-```bash
-curl -X DELETE 'http://localhost:8080/system/ai/engineConfig/1,2,3' \
-  -H 'Authorization: Bearer <token>'
-```
-
-**响应**:
-```json
-{
-  "code": 200,
-  "msg": "操作成功"
-}
-```
-
----
-
-## 五、configJson 结构规范
-
-`ai_agent_engine_config.config_json` 字段按引擎类型存储不同的 JSON 结构:
-
-### Dify 类型(`engine_type = "dify"`)
-
-```json
-{
-  "baseUrl": "http://8.136.61.90/v1",
-  "secretKey": "app-abc123xyz"
-}
-```
-
-| 字段 | 说明 |
-|------|------|
-| baseUrl | Dify 实例调用地址(同实例内所有 agent 相同) |
-| secretKey | 该 agent 的专属 API 密钥(每个 agent 不同,是路由的唯一标识) |
-
-### Direct 类型(`engine_type = "direct"`)
-
-```json
-{
-  "baseUrl": "https://api.openai.com/v1",
-  "apiKey": "sk-xxx",
-  "model": "gpt-4o"
-}
-```
-
-| 字段 | 说明 |
-|------|------|
-| baseUrl | 大模型 API 地址 |
-| apiKey | API 密钥 |
-| model | 指定模型 |
-
-### SpringAI 类型(`engine_type = "spring_ai"`)
-
-```json
-{
-  "baseUrl": "https://api.openai.com/v1",
-  "apiKey": "sk-xxx",
-  "model": "gpt-4o",
-  "temperature": 0.7,
-  "maxTokens": 2000
-}
-```
-
-### Mock 类型(`engine_type = "mock"`)
-
-```json
-{
-  "mockResponse": "我是模拟回复,用于开发测试"
-}
-```
-
----
-
-## 六、OpenPlatform API 测试请求示例
-
-以下示例使用 cURL 演示完整的调用流程。
-
-### 6.1 准备签名
-
-签名计算公式:`MD5(requestId + "&" + timestamp + "&" + payload + "&" + privateKey)`
-
-```bash
-# 变量准备
-TIMESTAMP=$(date +%s000)
-REQUEST_ID=$(uuidgen)
-PUBLIC_KEY="your-public-key"
-PRIVATE_KEY="your-private-key"
-
-# 计算签名(以创建会话为例)
-PAYLOAD='{"agentId":"agent_123456","userId":10001,"conversationName":"测试会话"}'
-SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&${PAYLOAD}&${PRIVATE_KEY}" | md5)
-```
-
-### 6.2 创建会话
-
-```bash
-curl -X POST 'http://localhost:8080/api/v1/conversation/create' \
-  -H 'Content-Type: application/json' \
-  -H "X-Emoon-Timestamp: ${TIMESTAMP}" \
-  -H "X-Emoon-Request-Id: ${REQUEST_ID}" \
-  -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \
-  -H "X-Emoon-Sign: ${SIGN}" \
-  -d '{
-    "agentId": "agent_123456",
-    "userId": 10001,
-    "conversationName": "测试会话"
-  }'
-```
-
-### 6.3 同步对话
-
-```bash
-PAYLOAD='{"agentId":"agent_123456","query":"帮我挂个内科的号","conversationId":"conv_xxx","stream":false,"userId":10001}'
-SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&${PAYLOAD}&${PRIVATE_KEY}" | md5)
-
-curl -X POST 'http://localhost:8080/api/v1/agent/chat' \
-  -H 'Content-Type: application/json' \
-  -H "X-Emoon-Timestamp: ${TIMESTAMP}" \
-  -H "X-Emoon-Request-Id: ${REQUEST_ID}" \
-  -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \
-  -H "X-Emoon-Sign: ${SIGN}" \
-  -d '{
-    "agentId": "agent_123456",
-    "query": "帮我挂个内科的号",
-    "conversationId": "conv_xxx",
-    "stream": false,
-    "userId": 10001
-  }'
-```
-
-### 6.4 SSE 流式对话
-
-```bash
-PAYLOAD='{"agentId":"agent_123456","query":"帮我挂个内科的号","stream":true,"userId":10001}'
-SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&${PAYLOAD}&${PRIVATE_KEY}" | md5)
-
-curl -N -X POST 'http://localhost:8080/api/v1/agent/chat' \
-  -H 'Content-Type: application/json' \
-  -H 'Accept: text/event-stream' \
-  -H "X-Emoon-Timestamp: ${TIMESTAMP}" \
-  -H "X-Emoon-Request-Id: ${REQUEST_ID}" \
-  -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \
-  -H "X-Emoon-Sign: ${SIGN}" \
-  -d '{
-    "agentId": "agent_123456",
-    "query": "帮我挂个内科的号",
-    "stream": true,
-    "userId": 10001
-  }'
-```
-
-> `-N` 参数禁用 cURL 缓冲,用于实时接收 SSE 事件流。
-
-### 6.5 查询会话列表
-
-```bash
-SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&&${PRIVATE_KEY}" | md5)
-
-curl -X GET 'http://localhost:8080/api/v1/conversation/list?agentId=agent_123456&userId=10001' \
-  -H "X-Emoon-Timestamp: ${TIMESTAMP}" \
-  -H "X-Emoon-Request-Id: ${REQUEST_ID}" \
-  -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \
-  -H "X-Emoon-Sign: ${SIGN}"
-```
-
-> 注意:GET 请求的 payload 为空字符串,签名中对应 `&&` 部分。
-
-### 6.6 查询会话详情
-
-```bash
-SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&&${PRIVATE_KEY}" | md5)
-
-curl -X GET 'http://localhost:8080/api/v1/conversation/conv_a1b2c3d4-xxxx' \
-  -H "X-Emoon-Timestamp: ${TIMESTAMP}" \
-  -H "X-Emoon-Request-Id: ${REQUEST_ID}" \
-  -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \
-  -H "X-Emoon-Sign: ${SIGN}"
-```
-
----
-
-## 七、错误码
-
-### OpenPlatform 错误码(SystemEnum)
-
-| 错误码 | 枚举值 | 说明 |
-|--------|--------|------|
-| 3 | INVALID_PUBLIC_KEY | 公钥不正确 |
-| 6 | ILLEGAL_REQUEST | 签名验证失败 |
-| 8 | PROJECT_NOT_EXISTS | 项目不存在 |
-| 11 | PROJECT_NO_AGENT_PERMISSION | 项目无该智能体权限 |
-| 12 | AGENT_NOT_FOUND | 智能体不存在 |
-| 13 | AGENT_DISABLED | 智能体已停用 |
-| 14 | ENGINE_CONFIG_NOT_FOUND | 引擎配置不存在 |
-| 15 | CONVERSATION_NOT_FOUND | 会话不存在 |
-
----
-
-## 八、关键设计决策记录
-
-| 决策 | 说明 |
-|------|------|
-| SysProjectDo 无 tenantId | 使用默认值 `"000000"` |
-| agentId 在 AiConversation 中是 Long | FK 到 ai_agent_app.id(主键),API 层用 String 业务 ID 后在 Service 中转换 |
-| engineType 不在 AiAgentApp 上 | 通过 engineConfigId 关联 AiAgentEngineConfig 获取,避免冗余 |
-| configJson 用 String 存储 | 不做 JSON 类型映射,保持灵活性,不同引擎结构不同 |
-| delFlag/status 是 String 类型 | 与框架保持一致,比较用 `"0".equals(...)` |
-| SSE 超时设为 0 | `new SseEmitter(0L)` 表示无超时,由引擎自行控制结束 |
-| 签名算法使用 MD5 | 沿用现有项目签名方案(SignUtil) |
-| ServiceImpl 放在 system-api | 遵循项目现有架构,不单独分离 |
-
----
-
-## 九、待实现功能
-
-| 功能 | 说明 |
-|------|------|
-| DifyAgentEngine | 对接 Dify REST API 的引擎实现 |
-| DirectLLMAgentEngine | 直连大模型引擎实现(基于 SpringAI ChatClient) |
-| 消息历史存储 | 存储对话消息记录并提供查询接口 |
-| Admin 端智能体 CRUD | AiAgentApp 的增删改查管理接口 |
-| Admin 端卡片管理 | 卡片定义、分类、插件的管理接口 |
-| 前端菜单/权限配置 | 在 sys_menu 中添加 AI 管理相关菜单和权限 |

+ 0 - 203
docs/ai-platform-module-architecture-map.md

@@ -1,203 +0,0 @@
-# 医梦 AI 中台工程模块架构一张图
-
-> 定位:现有 Maven 工程 + 规划领域模块 + 依赖/调用关系总览。  
-> 风格:大厂技术培训 / 高密度 / 强层级 / 甲方快乐图。
-
-```mermaid
-flowchart TB
-    %% ========== Style ==========
-    classDef entry fill:#0b5cff,color:#fff,stroke:#073ea8,stroke-width:2px
-    classDef existing fill:#eaf3ff,color:#071d49,stroke:#6aa5ff,stroke-width:1.5px
-    classDef planned fill:#eefbf3,color:#083b1f,stroke:#45b36b,stroke-width:1.5px
-    classDef boundary fill:#fff7e6,color:#4a2a00,stroke:#f0a020,stroke-width:1.5px
-    classDef infra fill:#f3f4f6,color:#111827,stroke:#9ca3af,stroke-width:1.5px
-    classDef external fill:#f7efff,color:#2e1065,stroke:#9b5de5,stroke-width:1.5px
-    classDef data fill:#fff1f2,color:#4a0614,stroke:#fb7185,stroke-width:1.5px
-    classDef future fill:#ecfeff,color:#083344,stroke:#06b6d4,stroke-width:1.5px,stroke-dasharray: 5 4
-
-    %% ========== External Channels ==========
-    subgraph L0["L0 外部触点 / 客户侧入口"]
-        Patient["患者端<br/>小程序 / APP / 公众号"]:::external
-        Doctor["医护端<br/>医生站 / 护士站 / PDA"]:::external
-        Robot["机器人 / 自助机 / 床旁屏"]:::external
-        OpsUser["医院管理员 / 医梦运营 / 财务"]:::external
-        Device["IoMT 设备<br/>床垫 / 雷达 / 输液泵 / 体征"]:::external
-        VoiceGw["语音网关<br/>外呼 / 通话回调"]:::external
-    end
-
-    %% ========== Application Entrypoints ==========
-    subgraph L1["L1 应用入口层:当前已存在,建议保留少量启动应用"]
-        AdminApp["emoon-admin<br/>管理后台启动应用<br/>系统配置 / 合同账单 / 运营审计"]:::entry
-        OpenApp["emoon-openplatform<br/>对外开放平台<br/>签名鉴权 / Agent API / SSE / Card Action"]:::entry
-        SnailJob["emoon-extend/emoon-snailjob-server<br/>任务调度基础设施"]:::existing
-        Monitor["emoon-extend/emoon-monitor-admin<br/>监控后台"]:::existing
-    end
-
-    Patient --> OpenApp
-    Doctor --> OpenApp
-    Robot --> OpenApp
-    OpsUser --> AdminApp
-    Device -.设备事件.-> IoMTHub
-    VoiceGw -.通话回调.-> FollowupSvc
-
-    %% ========== Current Maven Foundation ==========
-    subgraph L2["L2 当前 Maven 基座:已有工程资产"]
-        Common["emoon-infra/emoon-common<br/>core / redis / mybatis / satoken / security / oss / job / idempotent"]:::infra
-        SystemApi0["emoon-modules-api/emoon-system-api<br/>系统 DO / Service API / Mapper"]:::existing
-        McpApi0["emoon-modules-api/emoon-mcp-api<br/>现有 AgentEngine / 会话 / 卡片 / MCP 混合 API"]:::existing
-        KnowledgeApi0["emoon-modules-api/emoon-knowledge-api<br/>知识库 / Agent 设计态 / 质控资产"]:::existing
-        SystemImpl0["emoon-modules/emoon-system<br/>系统管理 / 引擎配置 CRUD"]:::existing
-        McpImpl0["emoon-modules/emoon-mcp<br/>现有 MCP 雏形 / CRUD 模板"]:::existing
-        Tongue0["emoon-extend/emoon-tongue<br/>舌诊/面诊专精能力"]:::existing
-        Migration["emoon-extend/emoon-migration<br/>迁移工具"]:::existing
-        FileDownloader["emoon-extend/emoon-file-downloader<br/>文件下载工具"]:::existing
-    end
-
-    AdminApp --> Common
-    OpenApp --> Common
-    AdminApp --> SystemImpl0
-    SystemImpl0 --> SystemApi0
-    OpenApp --> SystemApi0
-    OpenApp --> McpApi0
-    OpenApp --> KnowledgeApi0
-    McpImpl0 --> McpApi0
-    Tongue0 --> Common
-
-    %% ========== Planned Domain Modules ==========
-    subgraph L3["L3 规划领域模块:模块化单体优先,按领域隔离"]
-        AiAgentApi["emoon-ai-agent-api<br/>AgentRequest / AgentResponse / Engine SPI"]:::planned
-        AiAgent["emoon-ai-agent<br/>DifyAgentEngine / DirectLLM / Mock<br/>会话编排 / Dify 输出归一"]:::planned
-
-        AiCardApi["emoon-ai-card-api<br/>CardDefinition / CardInstance / CardAction API"]:::planned
-        AiCard["emoon-ai-card<br/>卡片定义 / 实例 / 动作状态机 / 快照"]:::planned
-
-        AiMcpApi["emoon-ai-mcp-api<br/>ToolContract / ToolResult / HospitalAdapter SPI"]:::planned
-        AiMcp["emoon-ai-mcp<br/>MCP Tool Service / HIS Adapter / 工具审计"]:::planned
-
-        AiMeterApi["emoon-ai-meter-api<br/>MeterEvent / BillingEpisode / Outbox Contract"]:::planned
-        AiMeter["emoon-ai-meter<br/>Outbox / MeterEvent / BillingEpisode / 事件归并"]:::planned
-
-        AiBillingApi["emoon-ai-billing-api<br/>Contract / Pricing / Credit / Billing API"]:::planned
-        AiBilling["emoon-ai-billing<br/>合同 / 能力值账户 / 定价 / 账本 / 账单"]:::planned
-
-        AiKnowledgeApi["emoon-ai-knowledge-api<br/>Knowledge / Prompt / RAG / QC API"]:::planned
-        AiKnowledge["emoon-ai-knowledge<br/>知识资产 / Prompt 模板 / RAG / 质控规则"]:::planned
-    end
-
-    %% planned replacement/migration hints
-    McpApi0 -.拆分迁移.-> AiAgentApi
-    McpApi0 -.拆分迁移.-> AiCardApi
-    McpApi0 -.拆分迁移.-> AiMcpApi
-    KnowledgeApi0 -.收敛迁移.-> AiKnowledgeApi
-    SystemApi0 -.保留系统域.-> AiBillingApi
-
-    OpenApp --> AiAgentApi
-    OpenApp --> AiCardApi
-    OpenApp --> AiMeterApi
-    OpenApp --> AiBillingApi
-    OpenApp --> AiKnowledgeApi
-    AdminApp --> AiBillingApi
-    AdminApp --> AiCardApi
-    AdminApp --> AiMcpApi
-    AdminApp --> AiKnowledgeApi
-
-    AiAgent --> AiAgentApi
-    AiCard --> AiCardApi
-    AiMcp --> AiMcpApi
-    AiMeter --> AiMeterApi
-    AiBilling --> AiBillingApi
-    AiKnowledge --> AiKnowledgeApi
-
-    AiAgent --> AiKnowledgeApi
-    AiAgent --> AiCardApi
-    AiAgent --> AiMeterApi
-    AiCard --> AiMcpApi
-    AiCard --> AiMeterApi
-    AiMcp --> AiMeterApi
-    AiMeter --> AiBillingApi
-    AiBilling --> AiMeterApi
-
-    %% ========== Future Boundary Services ==========
-    subgraph L4["L4 可独立部署边界:先模块化实现,满足条件后拆服务"]
-        McpServer["MCP Tool Server<br/>独立院内部署候选<br/>HIS/EMR/LIS/PACS 唯一工具出口"]:::future
-        AudioGw["AudioStreamGateway<br/>WebSocket 音频流 / ASR 分钟计量"]:::future
-        IoMTHub["IoMTEventIngestion<br/>设备认证 / 护理预警事件接入"]:::future
-        FollowupSvc["OutboundFollowupService<br/>随访计划 / 语音外呼 / 风险升级"]:::future
-        FileWorker["File/OCR/ASR Worker<br/>报告解析 / 图片处理 / 长任务"]:::future
-    end
-
-    AiMcp -.后续独立.-> McpServer
-    AiAgent -.音频文本进入 Agent.-> AudioGw
-    AudioGw --> AiMeterApi
-    IoMTHub --> AiMeterApi
-    FollowupSvc --> AiMeterApi
-    FileWorker --> AiMeterApi
-    OpenApp --> AudioGw
-    SnailJob --> FollowupSvc
-    SnailJob --> AiMeter
-    SnailJob --> AiBilling
-
-    %% ========== External AI / Hospital Systems ==========
-    subgraph L5["L5 外部系统 / 院内系统 / AI 能力"]
-        Dify["Dify Platform<br/>Workflow / LLM 节点 / RAG / 工具调用"]:::external
-        DirectLLM["Direct LLM / Vision Model<br/>L1 主引擎 / 降级 / 专精模型"]:::external
-        HIS["HIS / EMR / LIS / PACS<br/>排班 / 号源 / 建档 / 住院 / 随访"]:::external
-        ASR["ASR / TTS Provider<br/>语音识别 / 合成"]:::external
-        OSS["Object Storage<br/>报告 / 图片 / 音频 / 脱敏附件"]:::external
-    end
-
-    AiAgent --> Dify
-    AiAgent --> DirectLLM
-    Dify --> AiMcp
-    AiMcp --> HIS
-    McpServer --> HIS
-    AudioGw --> ASR
-    FollowupSvc --> ASR
-    OpenApp --> OSS
-    FileWorker --> OSS
-    Tongue0 --> DirectLLM
-
-    %% ========== Data Layer ==========
-    subgraph L6["L6 数据与可靠性底座:先单库强一致,Outbox 异步扩展"]
-        MySQL["MySQL<br/>系统 / Agent / Card / Meter / Billing / Audit"]:::data
-        Redis["Redis<br/>缓存 / 限流 / 短锁 / SSE 状态"]:::data
-        Outbox["Outbox Table<br/>业务事实同事务落库"]:::data
-        Ledger["Credit Ledger<br/>grant / freeze / settle / release / reverse"]:::data
-        Audit["Audit / Trace<br/>traceId / 医疗确认 / 敏感访问"]:::data
-    end
-
-    SystemImpl0 --> MySQL
-    AiAgent --> MySQL
-    AiCard --> MySQL
-    AiMcp --> MySQL
-    AiMeter --> MySQL
-    AiBilling --> MySQL
-    AiKnowledge --> MySQL
-    AiMeter --> Outbox
-    AiBilling --> Ledger
-    OpenApp --> Redis
-    AiCard --> Redis
-    AiMcp --> Redis
-    AiMeter --> Audit
-    AiBilling --> Audit
-    AiMcp --> Audit
-    AiCard --> Audit
-
-    %% ========== Legend ==========
-    subgraph Legend["图例 / 拆分原则"]
-        LG1["蓝色:启动应用 / 入口"]:::entry
-        LG2["浅蓝:当前已有模块"]:::existing
-        LG3["绿色:建议规划领域模块"]:::planned
-        LG4["青色虚线:未来可拆服务"]:::future
-        LG5["红色:数据与账本事实"]:::data
-        LG6["紫色:外部系统"]:::external
-    end
-```
-
-## 读图口径
-
-1. **短期不做全量微服务**:主路径保持模块化单体,优先跑通业务闭环。
-2. **当前最需要拆清楚的是 `emoon-mcp-api`**:它现在混了 Agent、Card、MCP 概念,建议拆成 `emoon-ai-agent-api`、`emoon-ai-card-api`、`emoon-ai-mcp-api`。
-3. **Billing/Meter 不建议先拆服务**:合同、账本、事件归并需要强一致语义,先同库同事务 + Outbox。
-4. **MCP、Audio、IoMT、Followup 是未来服务化边界**:它们有独立部署、长连接、设备接入或外部网关诉求,但第一阶段可先模块化实现。
-5. **L1 可走 DirectLLM 主引擎**:不必强制上 Dify;L2/L3 复杂场景再以 Dify Workflow 作为主编排。

+ 0 - 400
docs/ai-platform-review-report.md

@@ -1,400 +0,0 @@
-# 医梦 AI 中台技术方案评审报告
-
-**评审日期**: 2026-05-23
-**评审范围**: 《医梦 AI 中台二期技术方案与三期商业闭环设计》v5.0 及当前工程代码
-**评审方法**: 逐章审阅方案文档 + 全面代码库探索(模块结构、数据模型、服务链路、基础设施)
-
----
-
-## 1. 总体评价
-
-方案文档质量较高,是一份专业的生产级技术方案。架构分层清晰,组件职责边界明确,C4 图和时序图完整,实施路线务实。**核心方向合理,可以作为开发蓝图。**
-
-评审确认了 16 项合理设计、7 个结构性问题、11 个二期实现缺口、5 个三期设计缺陷、15 个需要补充的领域。
-
----
-
-## 2. 二期方案评审
-
-### 2.1 合理设计(确认)
-
-| # | 设计 | 评价 |
-|---|------|------|
-| 1 | AgentEngine 抽象 + Factory 路由模式 | 合理,已部分实现 |
-| 2 | Dify Workflow 承载流程编排 | 合理,适合频繁调整的医疗问诊/导诊流程 |
-| 3 | MCP Server 统一对接 HIS | 合理,降低多厂商 HIS 的适配复杂度 |
-| 4 | 卡片承载确定性业务操作 | 合理且必要,医疗流程不能完全依赖自然语言 |
-| 5 | 卡片实例快照机制 | 必须保留,避免卡片定义升级影响历史会话 |
-| 6 | 初级工程师实施顺序(Mock→Dify文本→Dify卡片→卡片动作→MCP写操作→全链路审计) | 非常务实 |
-| 7 | 接口设计 6 条原则(稳定对外、幂等写操作、项目隔离、统一错误码、traceId、JSON 解析) | 正确且必要 |
-| 8 | 合同/项目为计费主粒度,科室/智能体为分摊维度 | 正确的商业模型映射 |
-| 9 | 账本只追加不物理删除,冲正产生反向流水 | 财务系统基本要求 |
-| 10 | 账单从账本汇总,不从 ai_usage_log 汇总 | 关键的正确决策 |
-| 11 | 金额用 Decimal 不用 double | 基本财务规范 |
-| 12 | 合理不限量也写内部计量事件 | 没有遗漏成本核算 |
-| 13 | 计量事件先幂等再处理 | 防止重复扣费的核心保证 |
-| 14 | 账本更新事务内完成 | 一致性基本要求 |
-| 15 | 拒绝二期承担完整计费系统、第三方卡片生态、大而全 HIS 数据中台 | 范围控制的正确决策 |
-| 16 | 每步实现保留可回放样例 | 务实的工程质量要求 |
-
-### 2.2 文档未覆盖的工程现实问题
-
-#### 问题 1:两套引擎抽象并存且冲突
-
-代码库存在两套并行的引擎接口体系:
-
-**新体系**(`emoon-infra/emoon-modules-api/emoon-mcp-api/src/main/java/com/emoon/mcp/engine/`):
-- `AgentEngine` 接口:`chat()`、`chatStream()`、`getEngineType()`
-- `AgentEngineFactory`:Spring 容器收集所有 `AgentEngine` bean
-- 被 `AgentChatServiceImpl`(主链路)使用
-
-**旧体系**(`emoon-openplatform/src/main/java/com/emoon/openplatform/engine/`):
-- `AIEngine` → `EngineAdapter`:含 `ChatFactory`、`ConversationFactory`、`AgentFactory`、`DatasetFactory`、`EngineCapability` 枚举
-- `DifyAdapterClient` implements `ChatFactory` + `ConversationFactory`
-- **引用 `AbstractEngineAdapter` 但该文件不存在于代码库中**
-- 不被 `AgentChatServiceImpl` 使用
-
-Dify 客户端连接的是旧体系,但主链路用的是新体系。**Dify 能力已经通过 `DifyApiClient` 实现,但未接入主链路**——这正是文档 2.3 节指出的"DifyAgentEngine 未进入统一 AgentEngine 主链路"。
-
-**文档对此没有任何讨论**——旧体系是废弃还是合并?`AbstractEngineAdapter` 缺失文件如何处置?
-
-**建议**:
-1. 废弃旧体系(`openplatform.engine` 包下的 `AIEngine`/`EngineAdapter`/`ChatFactory` 等)
-2. 在新 `AgentEngine` 接口下实现 `DifyAgentEngine`
-3. `DifyAgentEngine` 复用现有 `DifyApiClient`(OkHttp SSE 客户端已验证可用)
-4. 删除缺失引用的 `DifyAdapterClient` 和 `AbstractEngineAdapter`
-
-#### 问题 2:ApiSseController —— 2413 行巨石遗留控制器
-
-`emoon-openplatform/src/main/java/com/emoon/openplatform/controller/ApiSseController.java` 是一个先于 v1 API 存在的遗留控制器,将模型路由、RAG 检索、MCP 调用(内部术语,非 Model Context Protocol)、SSE 流处理、音频处理、提示词占位符替换等逻辑全部内联在一个文件中。
-
-它通过 `/chat/apisse` 路径对外服务,完全旁路 `AgentEngine` 抽象。如果线上同时存在这个旧路径和 `/api/v1/agent/chat` 新路径,会导致:
-- 签名鉴权逻辑重复(SignUtil vs 内联 MD5 实现)
-- 权限校验路径不一致(CheckController vs AgentController.authenticate)
-- 使用日志格式不一致
-- 卡片系统无法复用
-
-**建议**:在二期实施计划中增加一个任务——将 ApiSseController 的各路能力拆入 AgentEngine 实现:
-- 模型直连逻辑 → `DirectLLMAgentEngine`
-- RAG 匹配逻辑 → 独立的 RAG 服务或 Dify 知识库节点
-- 音频处理 → 独立的 AudioService
-- 提示词处理 → PromptTemplateService
-
-#### 问题 3:两套 AiAgent 实体并存
-
-| 实体 | 位置 | 用途 |
-|------|------|------|
-| `AiAgentApp` | `emoon-system-api` | 简单模型:app 级别,映射到 `engine_config_id`,是二期主链路使用的智能体定义 |
-| `AiAgent` | `emoon-knowledge-api` | 复杂模型:含 `clusterId`、`subType`、`goal`、`scenario`、`steps`、`inputs`、`outputs`、`integrations`、`runtimeModel`、`securityPii` 等丰富字段 |
-
-文档只提到 `ai_agent_app`。建议明确两个实体的关系:`AiAgent` 是知识模块的内部智能体定义(包含详细的 step/workflow 定义),`AiAgentApp` 是开放平台的对外智能体视图(轻量映射到引擎配置)。二者需要通过某种方式关联。
-
-### 2.3 二期实现缺口
-
-按严重程度从高到低排列:
-
-| # | 缺口 | 严重程度 | 当前状态 | 文档是否识别 |
-|---|------|----------|----------|-------------|
-| 1 | **DifyAgentEngine 未实现** — `AgentEngineFactory.getEngine("dify")` 会抛异常 | **阻塞** | 仅有 MockAgentEngine | 是(2.3 节) |
-| 2 | **卡片实例创建未实现** — `AgentChatServiceImpl` 只透传 cardKey/cardData 到 DTO,不写 `ai_card_instance` 表 | **阻塞** | 数据模型已就绪,逻辑缺失 | 否 |
-| 3 | **卡片动作接口完全缺失** — 无 `POST /api/v1/card/{instanceId}/actions/{action}` Controller | **阻塞** | 无 Controller、无 Service、无状态机 | 否 |
-| 4 | **MCP 工具服务未实现** — `emoon-mcp` 模块只有脚手架 CRUD | **阻塞** | `HospitalActivityController` 含占位符注释 `【请填写功能名称】` | 是(2.3 节) |
-| 5 | **HIS Adapter 接口和实现不存在** — 无 HL7/FHIR/厂商接口适配层 | **高** | 无相关代码 | 是(13.6 节有设计) |
-| 6 | **traceId 未生成和传播** — 全链路无法串联 | **高** | `AgentController` 和 `AgentChatServiceImpl` 均未生成 | 是(7.3 节有要求) |
-| 7 | **tenantId 硬编码 "000000"** — `SysProjectDo` 缺少 tenantId 字段 | **高** | `AgentController.authenticate()` 中 `new AuthContext(projectId, "000000")` | 是(2.3 节) |
-| 8 | **卡片实例状态查询接口缺失** — 无 `GET /api/v1/card/{instanceId}` | **高** | 前端无法恢复卡片状态 | 否 |
-| 9 | **ai_usage_log 大量字段未填充** — projectId、userId、latencyMs、status、query、answer、modelName、pricing 字段、workflowRunId 均有字段但 `asyncLogUsage()` 未写入 | **中** | 实体定义了 20+ 字段,实际只写 5 个 | 否 |
-| 10 | **无 idempotencyKey** — 写操作无防重机制 | **中** | 仅有 `emoon-common-idempotent` 做表单级去重 | 否 |
-| 11 | **无 scenarioCode/deptId 上下文** — 请求上下文中缺少计量维度 | **中** | 请求 DTO 和上下文对象均无这些字段 | 否 |
-| 12 | **无消息历史查询 API** — 聊天 UI 需要拉取历史消息 | **中** | `ai_usage_log` 有 query/answer 字段但未填充 | 否 |
-| 13 | **会话上下文缺失** — `AiConversation` 只有计数,没有 `contextJson` 存储业务上下文(如当前已选科室) | **低** | Dify 多轮对话需要业务上下文 | 否 |
-| 14 | **签名算法使用 MD5** — MD5 存在碰撞攻击 | **低** | `SignUtil` 和 `ApiSseController` 均使用 MD5 | 否 |
-
-### 2.4 二期架构设计问题
-
-#### 问题 4:Card Runtime 的代码归属不清晰
-
-文档 4.1 节将 Card Runtime 列为独立组件。但实际代码中卡片相关类分散在三个位置:
-
-| 类 | 所在模块 |
-|----|---------|
-| `AiCardDefinition` | `emoon-system-api` |
-| `AiCardInstance` | `emoon-mcp-api` |
-| `AiCardActionLog` | `emoon-system-api` |
-| `AiCardGrayConfig` | `emoon-system-api` |
-
-卡片实例创建逻辑应由 `AgentChatServiceImpl` 触发(解析 Dify 返回的 cardKey → 查定义 → 创建实例 → 写快照)。卡片动作执行应是一个独立服务 `CardActionService`(校验实例状态 → 幂等 → 调 MCP → 更新状态 → 写动作日志)。
-
-但文档没有明确这个服务放在哪个模块。建议:
-- `CardActionService` 接口放在 `emoon-mcp-api`
-- 实现放在 `emoon-openplatform` 或 `emoon-mcp`
-
-#### 问题 5:MCP 命名歧义
-
-代码库中 "MCP" 有双重含义:
-1. `emoon-mcp` 模块 = AI 引擎核心模块(`AgentEngine` 接口所在)
-2. `ApiSseController.processMcpStream()` = 调外部 LLM 代理
-
-文档使用 "MCP" 指 Anthropic 的 **Model Context Protocol**(工具协议标准)。三者完全不同,开发人员会严重混淆。
-
-**建议**:
-- `emoon-mcp` 重命名为 `emoon-agent-core`(承载 AgentEngine 抽象)
-- 新增 `emoon-mcp-server` 模块承载真正的 MCP 协议实现
-- 旧 `processMcpStream()` 随着 ApiSseController 重构而移除
-
-#### 问题 6:Dify 健康检查与降级策略缺失
-
-文档将 Dify 定位为流程编排核心,但未讨论:
-- Dify 平台不可用时的降级策略(回退 DirectLLM?返回预设回复?)
-- Dify 响应超时对 SSE 连接的影响
-- 管理后台是否需要展示 Dify 连接状态
-
-建议在二期设计中增加:
-1. Dify 健康检查端点(管理后台展示连接状态)
-2. 降级策略配置(按智能体配置是否允许降级、降级到哪个引擎)
-3. 超时熔断(已在 13.6 节 MCP 工具层面提到,但 Dify 调用本身也需要)
-
----
-
-## 3. 三期方案评审
-
-### 3.1 合理设计(确认)
-
-三期整体设计质量高。能力值账户体系、计量中心、定价折算、账单结算、风控审批的分层清晰,账本状态机(grant → freeze → settle → release → reverse → adjust)完整且符合财务系统设计原则。核心决策均正确:
-
-- 项目/医院为合同和额度主对象(不是 tenantId)
-- 科室/智能体/用户用于预算和分摊
-- 账本只追加不物理删除
-- 冲正产生反向流水,不覆盖原流水
-- 账单以账本为准,`ai_usage_log` 仅作排障辅助
-- 金额用 Decimal
-- 合理不限量也写内部计量事件
-- 合同规则版本化,历史账单按当时规则回放
-- 计量事件先幂等再处理
-- MVP 阶段先做同步账本,后续再引入异步优化
-
-### 3.2 三期设计缺陷
-
-#### 问题 7:能力值(credits)的定义不够精确
-
-文档定义能力值为"虚拟货币",但缺少关键信息:
-
-1. **基准汇率**:1 能力值 ≈ 多少人民币?
-2. **折算系数示例**:导诊 1 次 = X credits,报告解读 1 份 = Y credits,语音随访 1 分钟 = Z credits,之间的比例关系是什么?
-3. **成本核算公式**:credits 与底层模型 token 成本的换算关系?
-
-**影响**:这些是表结构设计和定价规则引擎的关键输入。没有具体示例,开发人员无法确定 `ai_pricing_rule` 表的字段设计是否正确。
-
-**建议**:至少在方案中给出一个具体套餐的完整示例:
-```
-标准包:200,000 能力值 / 年,价格 ¥80,000
-- 导诊问答:10 credits/次(含 2000 token)
-- 智能分诊:50 credits/次
-- 预问诊:30 credits/完整问诊单
-- 报告解读:100 credits/份
-- 病历生成:200 credits/份
-- 语音随访:15 credits/分钟
-```
-
-#### 问题 8:定价规则版本化缺乏存储设计
-
-文档说"支持版本化,历史账单按当时规则回放",但没有说明:
-1. 版本如何存储——是 `version` 字段还是规则快照?
-2. 计量事件发生时如何确定用哪个版本?
-3. 账单重算时(合同变更、规则修正)如何处理?
-
-**建议**:
-1. `ai_pricing_rule` 增加 `version` 和 `effective_from`/`effective_to` 字段
-2. 计量事件写入时记录 `pricing_rule_version` 或规则的 JSON 快照
-3. 账单汇总时使用事件快照中的规则,而不是实时查询规则表
-
-#### 问题 9:三期与二期的接口契约未定义
-
-文档 12 节描述了集成点但未定义数据结构:
-
-| 接口 | 缺失的定义 |
-|------|-----------|
-| `MeterEventService` | 方法签名、输入/输出 |
-| `QuotaPolicyService` | 返回什么?放行/需冻结/需审批/阻断 的枚举? |
-| `CreditAccountService` | freeze/settle/release 的方法签名和参数 |
-| `PricingService` | 输入(事件)→ 输出(credits)的契约 |
-| `BillingService` | 账单生成的触发方式和输入 |
-
-**建议**:在三期实施前补充各服务的 Java 接口定义(interface),至少包含方法签名和关键 Javadoc。
-
-#### 问题 10:Outbox 模式缺乏利用现有基础设施的具体方案
-
-文档 13.2 节描述了 Outbox 模式,但:
-1. 项目中已有 **SnailJob** 分布式任务调度(`emoon-infra/emoon-common/emoon-common-job`),但未被提及
-2. 现有 `emoon-common-idempotent` 只做表单级去重(Redis 5 秒窗口),不够用
-3. 未定义 Outbox 事件表结构和事件类型枚举
-4. 未讨论"事务提交成功但投递处理器挂了"的恢复机制
-
-**建议**:
-1. 复用 SnailJob 做事件投递的定时扫描
-2. 新增 `ai_outbox_event` 表:`event_id, event_type, payload_json, idempotency_key, status(pending/processing/processed/dead), retry_count, next_retry_at, created_at`
-3. 所有计量、审计、报表事件先写 Outbox,同事务落库
-
-#### 问题 11:三期数据模型缺少关键实体
-
-文档 10.12 节 ER 图和 11 节表清单缺少以下实体:
-
-| 缺失 | 说明 |
-|------|------|
-| **审批记录表** | 10.8 节风控策略需要审批,但无审批实体。至少需要:审批 ID、关联对象类型和 ID、审批类型、申请人、审批人、审批结果、原因、有效时间、创建时间 |
-| **合同变更历史** | 合同规则版本化应有变更历史表,记录每次合同变更的 before/after、操作人和时间 |
-| **定价规则快照** | 在计量事件中关联定价规则版本,但规则快照本身建议单独存储,支持历史回放 |
-| **运营报告快照** | `ai_operation_report` 已列出,但缺少明细行(报告包含多个指标) |
-
-#### 问题 12:审批工作流未设计
-
-文档 10.8 节风控策略依赖审批(余额为 0 时审批、超出合同场景时审批),但方案中没有审批工作流的设计。缺失:
-
-- 审批节点定义(谁审批?项目经理?医梦运营?医院管理员?)
-- 审批超时策略(多久未审批自动拒绝/通过?)
-- 审批结果生效和失效机制(审批通过后额度放行多久有效?)
-- 审批与风控策略的关联(每种策略对应什么审批流程?)
-
-#### 问题 13:超量消费的结算流程不完整
-
-文档 10.6 节提到超量账单,但以下问题未明确:
-- 超量后的能力值单价是按超量价还是标准价?(文档 10.1 提到"超量价"但未展开)
-- 超量消费是事后结算还是现场补购?
-- 超量消耗是否影响正在进行的调用?(软拦截之后用户能否继续用?)
-
----
-
-## 4. 需要补充和优化的功能
-
-### 4.1 二期必须补充
-
-| # | 补充项 | 说明 |
-|---|--------|------|
-| 1 | **消息历史查询 API** | `GET /api/v1/conversation/{id}/messages`,前端聊天 UI 必须拉取历史消息。可复用 `ai_usage_log.query/answer` 字段(当前未填充),或新增 `ai_message` 表 |
-| 2 | **Dify 输入裁剪与脱敏层** | 在 `DifyAgentEngine` 中增加数据裁剪层,定义传给 Dify 的字段白名单,避免完整病历无控制进入 Dify |
-| 3 | **会话业务上下文存储** | `AiConversation` 增加 `context_json` 字段,存储多轮对话中的业务上下文(已选科室、已选医生、已选时间等),支撑 Dify Workflow 分支决策 |
-| 4 | **Mock HIS Server** | 文档 13.6.2 提到但无设计。二期联调需要可配置返回数据的 Mock 层。建议实现一个简单的 HTTP Mock Server(Spring Boot Profile 激活),按配置文件返回预设 JSON |
-| 5 | **Dify 版本探测** | 文档 13.3 节提到 WorkflowProbeService——发布前探测。二期即使不做完整版本治理,也应至少实现一个基础探测:`POST /api/v1/admin/workflow/{version}/probe`,校验 Dify 应用能否调通、输出是否包含预期字段 |
-| 6 | **卡片定义管理后台** | `AiCardDefinition` 实体已存在,但管理后台的 CRUD 接口未实现。建议补齐基本的卡片定义管理(列表、新增、编辑、版本管理) |
-
-### 4.2 三期需要深化
-
-| # | 深化项 | 说明 |
-|---|--------|------|
-| 7 | **能力值套餐模板定义** | 至少给出"标准包"的完整示例(能力值总量、价格、覆盖场景、各项能力 credits、有效期、超量价),作为数据库设计和定价引擎的输入 |
-| 8 | **计费事件的完整枚举** | 方案 10.3 节列出了计量事件来源,但需要完整的 `MeterEventType` 枚举定义(`CHAT_COMPLETED, CHAT_STREAM_PARTIAL, CHAT_FAILED, CARD_DISPLAYED, CARD_ACTION_CONFIRMED, MCP_TOOL_READ, MCP_TOOL_WRITE, ASR_MINUTE, TTS_MINUTE, IMAGE_ANALYSIS, DOCUMENT_GENERATED` 等),以及每种事件的默认计量策略 |
-| 9 | **运营报告的字段定义** | 至少定义"项目月度运营报告"的完整字段清单和报表结构 |
-| 10 | **合同状态机的完整定义** | 文档 10.1 节提到"草稿、生效、暂停、过期、终止、续签",但没有画出状态转换图。建议补充:draft → active → suspended → active / expired / terminated → renewed |
-| 11 | **账单生成触发机制** | 月度账单是定时任务(每月 1 日自动生成上月账单)还是手动触发?超量账单是实时生成还是月末汇总?需要明确触发方式 |
-
-### 4.3 文档完全未涉及的领域
-
-| # | 缺失领域 | 说明 |
-|---|----------|------|
-| 12 | **测试策略** | 多组件系统(OpenPlatform + Dify + MCP + Card + Metering)的关键测试场景:单元测试覆盖哪些层?Dify 和 HIS 如何 Mock?端到端链路如何验证?计量/账本的测试如何保证扣费正确? |
-| 13 | **数据库迁移策略** | 二三期新增 20+ 张表,需要规范的 Schema 迁移管理。建议采用 Flyway 或 Liquibase,替代手动 SQL 脚本 |
-| 14 | **部署架构** | `emoon-mcp` 是独立服务还是嵌入模块?如果是独立 MCP Server,需要独立进程和网络通信;如果嵌入,与 Dify 的集成协议需要调整。需要明确部署拓扑 |
-| 15 | **API 版本化策略** | 当前有 `/chat/apisse`(旧)和 `/api/v1/agent/chat`(新),后续 API 如何演进?URL 路径版本 vs Header 版本? |
-| 16 | **向后兼容性** | 现有 `/chat/apisse` 的客户端如何迁移到 `/api/v1`?需要兼容期多长? |
-| 17 | **安全算法升级** | 当前签名使用 MD5(碰撞攻击可行),建议列入 HMAC-SHA256 升级计划。同时文档 6.2 节提到敏感配置加密,但未指定加密算法(AES-256-GCM?) |
-| 18 | **数据保留与归档** | 医疗数据有法定保留期限。建议定义:会话数据保留周期、日志归档策略、患者数据删除机制(满足《个人信息保护法》)、计费数据法定保留期 |
-| 19 | **国际化机制** | 卡片定义的 `name`/`description` 等字段当前无 i18n 支持。建议在 `AiCardDefinition` 中考虑 JSON 多语言字段(`{"zh": "科室选择", "en": "Department Select"}`) |
-| 20 | **Dify 平台故障降级** | Dify 不可用时的完整降级策略:超时阈值、重试策略、降级到 DirectLLM 还是返回预设回复、用户提示语、管理后台告警 |
-| 21 | **并发控制** | 挂号锁号场景的高并发处理:Redis 分布式锁的粒度(按号源?按医生时间段?)、锁超时、自动释放、并发冲突的用户提示 |
-| 22 | **数据合规审计** | 谁在什么时间查看了哪些敏感数据——除了操作日志外,需要专门的敏感数据访问审计表,满足《个人信息保护法》和等保要求 |
-| 23 | **性能基准** | 预期的 TPS/QPS?SSE 长连接的并发数?数据库连接池规划?卡片查询的缓存策略? |
-| 24 | **灾备方案** | 计费数据的备份策略?数据库主从/集群方案?Redis 持久化策略? |
-| 25 | **灰度发布** | 方案 13.3 节提到 Dify Workflow 的灰度策略(按项目、科室、用户比例),但 OpenPlatform 自身的代码灰度(新版本上线)策略未提 |
-| 26 | **监控指标定义** | 文档 13.7 节提到可观测性但没有具体的监控指标。建议定义:API 请求量/QPS/错误率/P99 延迟、Dify 调用成功率/延迟、MCP 工具调用成功率/延迟、卡片动作成功率、SSE 连接数/断连率、计费事件的 processing lag |
-
----
-
-## 5. 实施优先级调整建议
-
-基于以上分析,建议的优先级分层:
-
-### P0 — 阻塞项(二期必须先完成)
-
-| 任务 | 原因 |
-|------|------|
-| DifyAgentEngine 实现(复用 DifyApiClient) | 主链路无法使用 Dify |
-| 卡片实例创建(AgentChatServiceImpl 中增加 CardInstance 落库) | 卡片数据无法持久化 |
-| 卡片动作接口 + 状态机 | 用户无法与卡片交互 |
-| traceId 生成与全链路传播 | 排障不可行 |
-| tenantId 修复(SysProjectDo 增加 tenantId,AuthContext 动态获取) | 多租户无隔离 |
-| 消息历史查询 API | 前端聊天 UI 基本功能 |
-
-### P1 — 高优先级(二期应完成)
-
-| 任务 | 原因 |
-|------|------|
-| MCP 查询类工具实现(科室、医生、排班) | 卡片展示需要真实数据 |
-| HIS Adapter 接口定义 + Mock 实现 | 联调依赖 |
-| 旧引擎体系清理(废弃 openplatform.engine 包) | 代码健康 |
-| 计量上下文(scenarioCode/deptId)在主链路中传递 | 后续计费不需要重构 |
-| ai_usage_log 填充完整字段 | 排障可用 |
-| Dify 健康检查 + 降级策略 | 生产稳定性 |
-
-### P2 — 三期 MVP
-
-| 任务 | 原因 |
-|------|------|
-| Outbox 事件表 + 投递(复用 SnailJob) | 事件可靠性基础 |
-| ai_meter_event + ai_credit_account + ai_credit_ledger 表 | 计量基础设施 |
-| 简单定价规则(按智能体固定能力值 + 按 token 折算) | MVP 可用 |
-| MeterEventService + CreditAccountService(同步版本) | 核心计费链路 |
-| 管理后台查询余额和扣费流水 | 运营可见 |
-
-### P3 — 三期完善
-
-| 任务 | 原因 |
-|------|------|
-| 定价规则引擎(版本化) + 合同中心 | 完整计费 |
-| 风控审批工作流 | 生产风控 |
-| 月账单 + 科室分摊 | 客户对账 |
-| 运营看板 | 运营交付 |
-| 合理不限量内部计量 | 成本核算 |
-
----
-
-## 6. 关键风险清单
-
-| 风险 | 概率 | 影响 | 缓解措施 |
-|------|------|------|----------|
-| Dify usage 不稳定或缺失 | 中 | 高 — 计量基准不可靠 | 以平台计量事件为主,Dify usage 为补充 |
-| SSE 流式响应中断导致状态不一致 | 高 | 中 — 会话标记不完整、计费争议 | 按 partial/中断策略决定是否计量;卡片实例设置合理超时 |
-| MCP 写操作超时后重复执行 | 中 | 高 — 重复挂号、重复建档 | 先查 HIS 最终状态再决定重试;所有写操作有幂等键 |
-| HIS 厂商接口差异导致交付延迟 | 高 | 高 — L2/L3 交付周期不可控 | Adapter 模式 + Mock Server 先行联调 |
-| 合理不限量成本失控 | 中 | 高 — 利润为负 | 内部计量 + 峰值预警 + 合同边界 + 运营定期复核 |
-| 能力值定价规则频繁变化 | 中 | 中 — 历史账单不可信 | 规则版本化 + 计量事件记录规则快照 |
-| Dify 平台故障导致大面积服务中断 | 低 | 高 — 所有智能体对话不可用 | 降级策略 + 健康检查 + 告警 |
-| 医疗关键链路被计费系统硬阻断 | 低 | 极高 — 医疗安全事故 | 软拦截优先、审批放行、事后结算,不做简单余额不足即停 |
-| 会话/卡片数据泄露 | 低 | 极高 — 合规事故 | 日志脱敏、Dify 输入裁剪、传输加密、敏感字段加密存储 |
-
----
-
-## 7. 最终建议汇总
-
-### 对二期
-
-1. **立即实现 DifyAgentEngine**——这是打通主链路的第一要务,现有 DifyApiClient 已验证可用,接入 AgentEngine 接口即可
-2. **补齐卡片全链路**——实例创建、动作接口、状态机、查询接口,缺一不可
-3. **清理技术债务**——废弃旧引擎体系、明确 MCP 命名、ApiSseController 迁移路线
-4. **尽早接入计量上下文**——traceId、scenarioCode、deptId、idempotencyKey 进入主链路,避免三期重构
-5. **ai_usage_log 不再是裸日志**——填充完整字段(projectId、userId、latencyMs、status、modelName 等),成为有价值的排障数据源
-
-### 对三期
-
-6. **三期 MVP 优先交付"能扣、能查、能防重复、能出账"**——不做复杂规则引擎和运营报表
-7. **能力值给出具体示例**——一个标准包套餐的完整定义,作为实施输入
-8. **接口契约先行**——各计费服务的 Java 接口在编码前定义清楚
-9. **Outbox 复用 SnailJob**——不要从零造轮子
-10. **所有 HIS 写操作必须经过 MCP Server**——这是生产安全底线
-
-### 对方案文档
-
-11. **增加测试策略章节**——多组件系统的测试方案必不可少
-12. **补充部署拓扑图**——明确哪些是独立服务、哪些是嵌入模块
-13. **补充安全章节**——签名算法升级、敏感字段加密方案、数据脱敏具体规则
-14. **补充数据生命周期章节**——保留周期、归档策略、删除机制
-15. **补充 Dify 降级方案**——Dify 不可用时的具体处置

+ 0 - 216
docs/ai-platform-review-round3.md

@@ -1,216 +0,0 @@
-# 医梦 AI 中台技术方案 —— 第三轮深度评审
-
-**评审日期**: 2026-05-23
-**评审方法**: 全文档联动验证 + 实现可行性推演 + 边界场景压力测试
-**评审范围**: v5.0 修订稿(2445 行,含 19 个章节 + 术语表 + ADR 索引)
-
----
-
-## 0. 修订验证
-
-GPT 对本轮 20 个问题全部做了实质性回应。核心新增章节逐一验证:
-
-| 二审问题 | 新增章节 | 验证结果 |
-|---------|---------|---------|
-| Dify 输出契约 | 5.8.2 | 定义了 answer/cards/riskLevel/scenarioCode 输出字段,卡片输出有 schema 校验要求 |
-| 流式卡片时机 | 5.8.3 | 定义了 5 种 SSE 事件类型,明确了卡片只在落库后发 `card_created` |
-| 调用方向矛盾 | 4.2 | 新画了统一调用方向图,定义了 5 段调用的信任边界 |
-| MCP 部署决策 | 4.3 | 三种部署形态对比表 + 推荐路线,明确指出同进程也要 Outbox |
-| 卡片回传 Dify | 5.8.4 | 默认方案(下轮 inputs 携带)+ 立即续跑方案(synthetic message),两种模式都有 |
-| Dify 失败补偿 | 5.8.5 | 5 种失败场景的处理策略表 |
-| DirectLLM 缺失 | 5.9 | 三种引擎定位表,明确 DirectLLM 必须走同一治理链路 |
-| API 幂等 | 7.4 | 5 种接口的幂等键设计 + 幂等记录字段 + 5 条合并规则 |
-| 定价优先级 | 10.17 | 5 级优先级表 + 实现要求(pricing_source 输出) |
-| 计量事件状态 | 10.18 | 完整的 8 状态机 + 5 条一致性规则 |
-| 账户并发 | 10.19 | 乐观锁 + 短退避重试 + 两层账户限制 |
-| 特殊账务 | 10.26 | 8 种场景的处理策略表,含能力值到期 |
-| 知识模块边界 | 13.9 | 职能对比表 + 架构图,明确 "emoon-knowledge 是权威来源" |
-| SSE 治理 | 13.10 | 6 阶段生命周期表 + 6 条实现规则 |
-| 缓存文件多模态 | 13.11 | 6 类缓存 TTL 策略 + Redis 降级规则 + 独立文件链路图 |
-| 术语表+ADR | 18, 19 | 18 条术语 + 10 条 ADR |
-
-**修订有效**。文档已从 v4 的 "Dify 做总控,OpenPlatform 只渲染" 完全转型为 v5 的 "OpenPlatform 掌握入口和事实,Dify 掌握编排"。
-
----
-
-## 1. 残余实现级问题
-
-### 1.1 Synthetic Message 的无界递归风险
-
-第 5.8.4 节"立即续跑"方案:Card Runtime 完成动作后 → Agent 编排服务向 Dify 发 synthetic user message → Dify 返回新卡片 → 用户点击新卡片 → 又触发 synthetic message → ...
-
-如果不加深度限制,一个挂号流程可能自动跑完 5-6 轮 Dify 调用(选科室 → 选医生 → 选时间 → 确认 → 创建),全程无用户参与。这在部分场景是期望行为,但在以下情况出问题:
-- Dify Workflow 某一步输出异常卡片,自动循环
-- 能力值消耗远超预期(用户只点了一次,系统跑了 6 次 Dify 调用)
-- 并发 synthetic message 导致同一个 conversation 内消息乱序
-
-**建议**:增加 synthetic message 深度限制(默认最多 3 轮),超限后返回卡片让用户主动发起下一轮。每轮 synthetic message 都要产生计量事件和额度校验。
-
-### 1.2 Outbox Payload 的多态解析未定义
-
-第 13.2.5 节 Outbox 表有 `payload_json` 字段,但不同 `source_type` 的 payload 结构完全不同:
-
-| source_type | payload 内容 |
-|-------------|-------------|
-| openplatform | conversationId, messageId, agentId, query/answer 摘要 |
-| dify | workflowRunId, usage, modelName, conversationId |
-| mcp | toolName, inputParams 摘要, outputResult 摘要, hospitalId, latencyMs |
-| card | instanceId, actionName, cardKey, actionResult |
-
-`MeterEventHandler` 需要知道如何解析每种 payload。如果用 Jackson polymorphic deserialization,每种类型需要一个 `@JsonTypeInfo` 标记。如果 payload 是自由格式 JSON,则解析逻辑分散且脆弱。
-
-**建议**:定义 `OutboxPayload` 基类 + 每种 source_type 的子类,`event_type` 字段驱动反序列化。这个设计应该在编码前确定。
-
-### 1.3 SSE 心跳的实现机制
-
-第 13.10 节说"平台定期发送 heartbeat",但 Spring `SseEmitter` 没有内置心跳。实际实现需要:
-- 维护一个 `Map<String, SseEmitter>` (conversationId → emitter)
-- 后台线程每 30 秒遍历所有活跃 emitter 发送 `SseEmitter.event().comment("")`
-- emitter 的 `onCompletion()` / `onTimeout()` 回调中清理 map
-- 需要考虑:SseEmitter 的 `send()` 在客户端已断开时会抛异常,需要 catch
-
-这些是纯实现细节,但会在二期早期就遇到。
-
-**建议**:不需要在方案文档中展开,但应在 implementation guide 中给出心跳实现代码骨架。
-
-### 1.4 乐观锁重试风暴
-
-第 10.19 节说"并发更新失败时按短退避重试"。但 10 个并发冻结请求同时打同一个账户时,乐观锁会全部失败,然后全部重试,再次全部失败……形成重试风暴。
-
-**建议**:明确重试策略——指数退避 + 随机抖动(如 `100ms * 2^retryCount + random(0, 50ms)`),最大重试 3 次。
-
----
-
-## 2. 跨章节交互矛盾
-
-### 2.1 卡片动作用 MCP 的调用路径不一致
-
-第 5.5 节卡片动作时序:`Card->>MCP: 调用标准工具`
-第 4.5 节 C4 Container:Card 没有独立容器,属于 OpenPlatform 或 mcpApi
-
-**问题**:Card Runtime 调 MCP 是模块内调用还是跨服务调用?如果 MCP 独立部署了,Card Runtime 能否直接调?还是必须通过 OpenPlatform 中转折?
-
-**建议**:增加说明——卡片动作需要 MCP 工具时,走 `CardActionService → MCP Tool Service`(同进程内模块调用),MCP 独立部署后改为 HTTP/内部 RPC。卡片永远不绕过 OpenPlatform 直接调 MCP。
-
-### 2.2 计量事件去重键跨 source_type 的语义
-
-第 7.4 节 API 幂等:对话请求幂等键 = `projectId + agentId + clientMessageId`
-第 10.3 节计量事件:`idempotency_key` 是计量事件的幂等键
-
-同一笔业务操作的两个层面:
-- API 幂等:防止同一个 HTTP 请求被重复处理
-- 计量幂等:防止同一个业务事实被重复计费
-
-**问题**:如果 API 幂等返回了第一次结果(没产生新业务事实),计量事件是否还生成?答案应该是不生成——因为幂等返回意味着"这个请求已经处理过了,没有新的事实发生"。但文档没有明确这个关联。
-
-**建议**:在 7.4 和 10.3 之间增加交叉引用——API 幂等命中时不产生新计量事件。API 幂等记录中的 `trace_id` 可用于找到第一次的计量事件。
-
-### 2.3 合同套餐和定价规则的双重版本化
-
-第 10.16 节:定价规则有 `version` + `effective_from/to`
-第 10.25 节:合同有状态机(draft → active → suspended → expired)
-
-**问题**:一个计量事件在时间 T 发生。当时的合同是"标准包 8 折",当时的定价规则是"导诊 5 credits/次"。合同续签后变成了"深度包 7 折",定价规则也升级到 v2"导诊 3 credits/次"。历史账单重算时,应该用哪个合同、哪个规则?
-
-当前设计:事件携带 `pricing_rule_id + pricing_rule_version + rule_snapshot`。但没有携带合同快照。
-
-**建议**:计量事件也保存 `contract_id + contract_snapshot`(至少保存 applicable discounts、scope 等关键字段)。
-
----
-
-## 3. 规模化压力点
-
-### 3.1 对话上下文无限增长
-
-第 5.8.4 节的"默认方案":每轮卡片动作结果写入会话上下文 → 下一轮 Dify 调用时通过 `inputs` 传入。
-
-10 轮卡片交互后,`inputs` 可能包含 10 个历史卡片结果。Dify 的 `inputs` 有大小限制(通常几 KB 到几十 KB)。超限后 Dify 调用会失败。
-
-**建议**:增加上下文裁剪策略——`inputs` 只携带最近 3 轮卡片动作结果 + 当前业务关键字段摘要。历史卡片结果可通过 `conversationId` 查询。
-
-### 3.2 ai_meter_event_detail 表的膨胀
-
-第 10.3 节:一次对话有 1 个 event + N 个 detail(token、分钟、图片等)。日均 10 万次调用 × 平均 3 条 detail = 日均 30 万行。
-
-**建议**:detail 表按时间分区(如按月),或 detail 不作为独立表而作为 event 的 JSON 字段存储(如果查询模式不需要独立索引 detail)。
-
-### 3.3 SSE 线程池的隔离
-
-第 13.10 节规则:"使用专用线程池或响应式转发,不与普通 API 线程池混用"。
-
-当前 Spring Boot 默认的 Tomcat 线程池(200 线程)如果被 200 个 SSE 长连接占满,所有普通 API 都无法响应。
-
-**建议**:量化这个风险——每个 SSE 连接占用 1 个 Tomcat 线程(同步转发模式)。如果试点项目预期 50 并发 SSE + 50 普通 API,Tomcat 默认 200 线程足够。但超过 200 SSE 连接时需要异步转发(Servlet 3.1 async + 独立线程池或 WebFlux)。
-
----
-
-## 4. 运营级未覆盖场景
-
-### 4.1 批量账务修正(误扣费回滚)
-
-第 10.14 节有 `reverse` 和 `adjust` 操作。但当"定价规则配错导致 500 笔导诊多扣了 3 credits/笔"时,需要:
-1. 识别受影响的事件范围(时间窗口 + 场景)
-2. 批量生成 `reverse` + `re-settle` 流水
-3. 验证修正后账单一致性
-4. 通知受影响客户
-
-当前的 `adjust` 操作是单笔的,没有批处理能力。
-
-**建议**:在运营中心增加"批量账务修正"功能——选择事件范围 → 预览受影响流水 → 审批 → 批量生成 correction 流水 → 触发账单重算。
-
-### 4.2 区域能力池的跨账户资金流动
-
-第 10.2 节提到"区域总账户"和"医院子账户",但没有具体说明资金流动规则:
-- 医院子账户用完后能否从总账户自动划拨?
-- 总账户余额不足时,医院子账户是否受影响?
-- 区域多院的月度账单是汇总还是分账?
-
-**建议**:至少明确初版规则——初版不做自动划拨,子账户额度由运营手动分配。自动划拨留到后续版本。
-
-### 4.3 合理不限量的"边界"定义
-
-文档多次提到"合理不限量超出边界"、"合理不限量异常峰值"、"合理不限量成本失控",但始终没有定义"合理"的具体判断标准。
-
-这本质上是业务规则,但技术上需要参数化:
-- 日均调用量超过合同约定日均的 X%?
-- 单次调用成本超过 Y 元?
-- 某场景用量占比超过总用量的 Z%?
-
-**建议**:在 `ai_quota_policy` 中增加"合理不限量"的监控参数——默认阈值(如日均 2x 合同隐含日均量即标记异常、单场景占比 > 60% 标记异常)。
-
----
-
-## 5. 方案文档本身的优化建议
-
-### 5.1 缺少"最小可行路径"的独立章节
-
-文档内容丰富(2445 行、19 章),但一个新人要理解"从零到上线要做什么",需要跳读第 2、3、4、5、7、13、14、15 章。建议增加一个"二期 MVP 最小可行路径"章节,用一页纸说清楚:
-- 需要新增/修改哪几个文件
-- 数据库要建哪几张表
-- 要调通几个接口
-- 验收标准是什么
-
-### 5.2 顺序依赖未显式标注
-
-各 task 之间有明显依赖(如 MeterEventService 依赖 Outbox 表先建好),但 15.5 节的开发任务表是扁平列表,没有依赖关系。
-
-**建议**:改为一句话标注依赖。如"卡片实例创建(依赖:Dify 引擎适配器已完成并返回 cardKey/cardData)"。
-
-### 5.3 ADR 索引缺少 ADR 文件的具体路径
-
-第 19 节 ADR 索引很好,但建议每条 ADR 指向具体文件路径(如 `docs/adr/ADR-001-agent-engine-unification.md`),便于直接定位。
-
----
-
-## 6. 总体评价
-
-v5.0 修订稿是一份**生产级别的高质量技术方案**。三次迭代从 v4 的"Dify 总控+OpenPlatform 只渲染"演化为 v5 的"OpenPlatform 掌握入口和事实,Dify 掌握编排,MCP 掌握院内工具"的清晰架构。
-
-文档覆盖了架构设计、数据模型、接口契约、链路时序、计量账本、运营报告、部署运维、安全合规、灾备监控——各方面均已达到可交付给开发团队执行的标准。
-
-剩余问题分三类:
-- **P0(编码前澄清)**: synthetic message 递归控制、Outbox payload 多态解析、卡片调用 MCP 的路径 (3 项)
-- **P1(二期实现中处理)**: SSE 心跳实现、乐观锁重试策略、计量幂等与 API 幂等的关联 (3 项)  
-- **P2(三期前明确)**: 批量账务修正、区域能力池划拨规则、合理不限量边界参数 (3 项)
-
-**建议:方案文档在此版本冻结为设计基线,剩余 P0 问题在 implementation guide 或具体模块的接口定义阶段解决。不再做全文档级的大修。**

+ 0 - 353
docs/ai-platform-review-round4.md

@@ -1,353 +0,0 @@
-# 医梦 AI 中台技术方案 —— 第四轮场景穿透评审
-
-**评审日期**: 2026-05-23
-**评审方法**: 具体医疗场景流程穿透 + 计费闭环追踪 + 模块间契约一致性校验
-**评审范围**: v5.0 修订稿(2538 行)
-
----
-
-## 0. 第三轮修订验证
-
-GPT 对本轮 9 个问题全部做出实质性回应。关键新增验证:
-
-| 三审问题 | 新增内容 | 位置 | 验证结果 |
-|---------|---------|------|---------|
-| synthetic 递归 | 5 项边界规则(深度3/并发/计量/风险/裁剪) | §5.8.4 L577-585 | 有效 |
-| 乐观锁重试风暴 | 指数退避+随机抖动,最多3次 | §10.19 L1266 | 有效 |
-| 卡片调用MCP路径 | §4.2 统一调用方向图+信任边界表 | §4.2 L212-242 | 有效 |
-| API幂等↔计量幂等关联 | 见§7.4 (API幂等) + §10.18 (计量事件状态) 交叉引用自然成立 | — | 弱关联,建议显式交叉引用 |
-| Outbox payload多态 | 5种payload schema定义 + 4条解析规则 | §13.2.5 L1664-1679 | 有效 |
-| 区域能力池 | 5条初版规则(手工分配,不做自动划拨) | §10.25 L1396-1402 | 有效 |
-| 批量账务修正 | 审批→预览→批量correction→重算账单 | §10.26 L1419 | 有效 |
-| 合理不限量参数化 | 5个监控参数+默认值 | §13.11.1 L2111-2119 | 有效 |
-| MVP最小路径 | 7项最小要求+验收链路 | §17 L2481-2491 | 有效 |
-| ADR路径 | 10条ADR各有具体文件路径 | §19 L2517-2524 | 有效 |
-| meter_event_detail膨胀 | 初版用JSON字段,高频查询时独立按月分区 | §11.1 L1451 | 有效 |
-
-**第四轮不再追踪前三轮已解决的问题。**
-
----
-
-## 1. 场景穿透分析
-
-以下用四个真实医疗场景,逐步骤穿透文档的每条链路,检查设计是否能闭环。
-
-### 1.1 场景A:智能分诊后挂号(完整链路)
-
-#### 1.1.1 端到端步骤
-
-```
-步骤1: 用户输入 "我头疼发烧两天了"
-   └─ POST /api/v1/agent/chat
-   ├─ 鉴权: X-Emoon-* → 项目签名验证 → projectId, tenantId
-   ├─ 幂等: X-Emoon-Idempotency-Key → 查幂等记录
-   ├─ 查 ai_agent_app → agentId="triage-agent" → engineConfigId → engineType="dify"
-   ├─ AgentEngineFactory → DifyAgentEngine
-   ├─ QuotaPolicyService: 查合同范围 → allow (triage 场景在合同内)
-   ├─ 生成 traceId, 构建 AgentRequest
-   └─ DifyAgentEngine.chat() → Dify API /chat-messages
-       ├─ inputs: {query: "我头疼发烧两天了", patientSummary: {...}}
-       └─ conversationId: null (首次) 或 externalConversationId
-
-步骤2: Dify Workflow 处理
-   ├─ LLM节点: 意图识别 → intent=triage, riskLevel=L2
-   ├─ 知识检索: 搜索头痛/发热相关临床指南
-   ├─ LLM节点: 综合症状+指南 → 生成分诊建议
-   ├─ 工具调用: his_get_departments() → MCP Server → HIS Adapter → HIS
-   │   └─ MCP返回: [{id:1, name:"内科"}, {id:2, name:"神经内科"}]
-   └─ 结束节点: 输出结构化JSON
-       └─ {answer: "您可能是...建议挂内科", 
-           cards: [{cardKey: "department-select", cardData: [...], businessContext: {...}}],
-           riskLevel: "L2", scenarioCode: "triage", workflowRunId: "wf-001",
-           usage: {promptTokens: 500, completionTokens: 300}}
-
-步骤3: OpenPlatform 处理 Dify 响应
-   ├─ 解析 answer + cards[]
-   ├─ 校验 cardKey="department-select" 存在,cardData 通过 schema 校验 ✓
-   ├─ CardInstanceService.create("department-select", cardData, conversationId)
-   │   └─ INSERT ai_card_instance (status=active, state_json, ui_config_snapshot)
-   ├─ 更新 ai_conversation (messageCount++, totalTokens+=800, externalConversationId)
-   ├─ asyncLogUsage (ai_usage_log: agentId, engineType, promptTokens, completionTokens)
-   ├─ OutboxEventService.write (event_type=CHAT_COMPLETED, source_type=openplatform)
-   └─ 返回 AgentChatResponse {reply, cardKey, cardData, conversationId, traceId}
-
-步骤4: 前端渲染科室选择卡片,用户点击"内科"
-   └─ POST /api/v1/card/{instanceId}/actions/select
-   ├─ X-Emoon-Idempotency-Key: projectId + instanceId + "select" + clientActionId
-   ├─ CardActionService:
-   │   ├─ 校验 instance.status=active ✓
-   │   ├─ 校验 action "select" 在卡片定义的 actions_json 中 ✓
-   │   ├─ 写 ai_card_action_log (status=pending)
-   │   ├─ 更新 ai_card_instance.state_json (记录 selectedDept=内科)
-   │   └─ 写 ai_card_action_log (status=completed)
-   └─ 返回 actionResult {selectedDept: "内科"}
-
-步骤5: 卡片动作完成→自动续跑(synthetic message 第1轮)
-   └─ Agent编排服务 → Dify
-   ├─ synthetic=true, cardActionResult={selectedDept:"内科"}
-   ├─ Dify Workflow: 分析已选科室 → 工具调用 his_get_doctors(deptId=1)
-   │   └─ MCP → HIS → 返回 [{id:101, name:"张医生", title:"主任医师"}]
-   └─ Dify 返回 doctor-select 卡片
-       └─ cards: [{cardKey: "doctor-select", cardData: [...]}]
-
-步骤6: 用户点击"张医生"→卡片动作→synthetic第2轮
-   └─ Dify 返回 time-select 卡片
-
-步骤7: 用户点击"周二上午"→卡片动作→synthetic第3轮
-   └─ Dify 返回 confirm-appointment 卡片(L3风险,需要用户确认)
-
-步骤8: 用户点击"确认挂号"→卡片动作
-   ├─ riskLevel=L3 → 二次确认卡片 ✓
-   ├─ CardActionService → MCP Tool Service → his_lock_schedule(slotId) 
-   │   ├─ Redis 短锁 (hospitalId + scheduleId + slotId)
-   │   ├─ HIS Adapter → HIS 锁号接口
-   │   └─ 返回锁号成功
-   ├─ → his_create_appointment(patientId, doctorId, slotId)
-   │   └─ HIS Adapter → HIS 创建预约
-   └─ 更新卡片实例 (status=completed, result_json)
-       └─ synthetic 深度已达3轮上限,不再自动续跑,返回成功卡片给用户
-
-步骤9: 计量闭环
-   ├─ 步骤3 Outbox → SnailJob → MeterEventHandler → MeterEvent (CHAT_COMPLETED, triage)
-   │   └─ PricingService → 5 credits → CreditAccountService.settle → Ledger (settle, -5)
-   ├─ 步骤5 Outbox → MeterEvent (CHAT_COMPLETED, triage? doctor_select?)
-   │   └─ PricingService → ? credits → settle
-   ├─ 步骤6 Outbox → MeterEvent (CHAT_COMPLETED, ?)
-   │   └─ PricingService → ? credits → settle
-   ├─ 步骤7 Outbox → MeterEvent (CHAT_COMPLETED, ?)
-   │   └─ PricingService → ? credits → settle
-   └─ 步骤8 Outbox → MeterEvent (CARD_ACTION_CONFIRMED + MCP_TOOL_WRITE)
-       └─ PricingService → appointment_service credits → settle
-```
-
-#### 1.1.2 发现的三个问题
-
-**问题 1(计费粒度不一致)**: 第 10.15 节定价定义为"智能分诊 3-8 credits/次",暗示按"一次完整的用户交互"计费。但第 5.8.4 节规则"每轮 synthetic message 都必须产出独立计量事件和额度校验",导致一次分诊挂号的 4 轮 Dify 调用各自独立计费(步骤3+5+6+7 = 4 次 × 3-8 credits = 12-32 credits),远超定价表中的"3-8 credits/次"。
-
-**这是文档目前最严重的计费设计不一致**——定价模型是"按次"(per complete interaction),但实现模型是"按调用"(per Dify call)。
-
-**问题 2(synthetic message 的 scenario_code 来源不明)**: 第 5.8.2 节说 scenarioCode 来自"智能体配置或 Dify 输出",但 synthetic message 是平台自动生成的,其 scenario_code 应该:
-- 继承原始对话的 scenario_code?
-- 根据卡片动作类型动态判断?
-- 由 CardActionService 指定?
-
-文档没有定义。如果 Dify 在 synthetic message 响应中不返回 scenarioCode,PricingService 无法确定用什么定价规则。
-
-**问题 3(MCP_TOOL_WRITE + CARD_ACTION_CONFIRMED 事件合并规则缺失)**: 步骤 8 同时产出了 CARD_ACTION_CONFIRMED 和 MCP_TOOL_WRITE 两个事件。第 10.23 节说 MCP_TOOL_WRITE "并入业务能力",第 12.4 节说"以卡片动作 ID 作为幂等源"。但计量处理器如何知道这两个事件属于同一业务操作,应该合并为一笔扣费?
-
-如果两个事件分别经过 PricingService → CreditAccountService.settle(),客户被重复扣费。
-
-#### 1.1.3 建议
-
-1. 定义计费粒度的"次" = 一次用户主动发起(非 synthetic)的对话 + 该对话触发的所有 synthetic 链路上的调用。在 `ai_conversation` 或 `ai_meter_event` 中增加 `parent_event_id` 字段,将 synthetic 调用的事件挂到原始用户发起的事件下,定价时按父事件统一计费。
-
-2. 明确 synthetic message 的 scenario_code 继承原始请求的 scenario_code,除非 Dify 在响应中显式覆盖。
-
-3. 定义事件合并规则:同一 traceId 内的 CARD_ACTION_CONFIRMED 和 MCP_TOOL_WRITE,MCP 事件的 `source_id` 携带卡片动作 ID,计量处理器识别到关联后只对 CARD_ACTION_CONFIRMED 执行定价和扣费,MCP_TOOL_WRITE 仅作为内部成本计入 `ai_meter_event_detail`。
-
----
-
-### 1.2 场景B:患者建档
-
-```
-步骤1: 用户 "我要建档"
-   └─ POST /api/v1/agent/chat → Dify
-   └─ Dify 返回 patient-registration 卡片
-       └─ cardData: {fields: [{name: "姓名", type: "text"}, {name: "身份证号", type: "id_card"}, ...]}
-
-步骤2: 前端渲染建档表单,用户填写并提交
-   └─ POST /api/v1/card/{instanceId}/actions/submit
-   └─ cardData包含完整PII (姓名、身份证号、手机号、住址)
-
-步骤3: CardActionService → MCP → HIS his_create_patient(patientData)
-   └─ HIS 返回 patientId
-
-步骤4: 计量: CARD_ACTION_CONFIRMED → PricingService → register_patient credits
-```
-
-**问题 4(PII 经 Dify 流转)**: 步骤 1 中 Dify 返回了包含完整字段定义的卡片。步骤 2 用户提交的数据经过 CardActionService 直接到 MCP,不经过 Dify——这是正确的(PII 走了后端 MCP 路径)。
-
-但卡片定义中的 `fields` 数组(姓名、身份证号等字段 label)会出现在 Dify 的输出中。如果 Dify 在步骤 1 的 cardData 中预填了患者的部分信息(如从之前的对话中提取),PII 就进入了 Dify。
-
-**这取决于 Dify 的 cardData 是否包含预填数据**。文档第 5.8.2 节要求"businessContext 只保存必要上下文,不把完整病历、身份证号等敏感信息塞进卡片",但没有区分"卡片定义"和"预填数据"的安全边界。
-
-**建议**:在 5.8.2 节增加一条——卡片 cardData 中的预填字段白名单:允许预填 deptId、doctorId、appointmentTime 等业务标识,禁止预填姓名、身份证号、手机号等 PII。PII 只通过卡片动作提交时的加密通道传输(前端→CardActionService→MCP),不进入 Dify 的输入或输出。
-
----
-
-### 1.3 场景C:舌诊
-
-```
-步骤1: 用户上传舌象照片
-   └─ POST /api/v1/file/upload → FileAPI
-   ├─ 校验: 项目权限、用户授权、文件类型(image/*)、大小(<10MB)
-   ├─ 安全扫描: 病毒/恶意内容检测
-   ├─ 存储: OSS (加密), 返回 fileId + signedUrl
-   └─ 文件访问审计记录
-
-步骤2: 用户 "帮我看看舌象" + fileId
-   └─ POST /api/v1/agent/chat {query: "帮我看看舌象", files: ["file-001"]}
-   └─ AgentEngine 路由:
-       ├─ 如果智能体配置为 DirectLLM → DirectLLMAgentEngine
-       │   └─ 构建 vision model 请求 (imageUrl=file-001的签名URL)
-       └─ 如果智能体配置为 Dify → DifyAgentEngine
-           └─ 先调 Dify /files/upload 中转文件
-           └─ 再调 Dify /chat-messages (files: [{difyFileId}])
-
-步骤3: 模型返回舌象分析
-   └─ 输出: {answer: "舌质淡红,苔薄白...", cards: [{cardKey: "tongue-result", cardData: {...}}]}
-
-步骤4: 计量: IMAGE_ANALYSIS event
-   └─ 谁产生这个事件?OpenPlatform? DifyAgentEngine? DirectLLMAgentEngine?
-```
-
-**问题 5(IMAGE_ANALYSIS 事件的产生方不明确)**: 第 10.3 节计量事件来源表中有"图像服务",第 10.23 节事件枚举中有 IMAGE_ANALYSIS。但具体是谁负责产出这个事件?
-
-- 如果是 DifyAgentEngine 产出:依赖 Dify 返回的 usage,但 Dify 的 usage 只含 token,不含"分析了几张图片"
-- 如果是 DirectLLMAgentEngine 产出:引擎自己知道请求中包含图片,可以产出
-- 如果是 OpenPlatform 产出:需要在请求中识别 fileRef 是图片类型
-
-三种路径的行为不一致会导致某些路径漏计费。
-
-**建议**:明确 IMAGE_ANALYSIS 事件统一由 OpenPlatform 在调用完成后产出。OpenPlatform 在 `buildAgentRequest` 时识别 `files` 参数中哪些是图片/文件,在 `handleResponse` 时根据文件类型产出对应的计量事件(IMAGE_ANALYSIS、DOCUMENT_GENERATED 等)。引擎层不负责区分计量类型。
-
----
-
-### 1.4 场景D:面诊
-
-与舌诊相同的问题。此外面诊涉及人脸图像,第 13.8.4 节的数据合规要求更为严格——人脸属于 P2 个人敏感数据,需要加密存储、脱敏展示。
-
-**问题 6(面诊图像合规路径不完整)**: 第 13.8.2 节要求"卡片展示按权限脱敏",面诊结果卡片可能包含面部特征分析。如果卡片快照(`ai_card_instance.ui_config_snapshot`)包含了面部分析结果,实际上保存了从面部图像中提取的敏感特征。
-
-**建议**:在 13.8.2 节卡片合规要求中增加——面诊/舌诊等医学图像分析结果卡片,快照只保存分析结论(如"面色萎黄,提示脾虚"),不保存原始图像引用或面部特征向量。
-
----
-
-## 2. AI 中台完整计费闭环追踪
-
-### 2.1 从调用到出账的全链路推演
-
-以下追踪一个完整的"签约→入账→调用→计量→扣费→账单"路径:
-
-```
-T0: 合同签约
-   ContractService.create(projectId=100, package=标准包, credits=200000, price=200000元, validFrom=2026-01-01, validTo=2026-12-31)
-   → ai_contract (status=active)
-   → ai_contract_scope (覆盖 导诊、分诊、预问诊、报告解读、病历生成、挂号服务)
-   → ai_credit_account (projectId=100, available_credits=200000, frozen_credits=0, used_credits=0, version=0)
-   → ai_credit_ledger (ledger_type=grant, credits=+200000, direction=credit)
-
-T1: 用户发起分诊请求 (traceId=t-001)
-   QuotaPolicyService.check(projectId=100, agentId=triage, scenarioCode=triage)
-   → 合同有效 ✓, 场景在合同范围内 ✓, 余额充足 ✓
-   → 返回: allow (无需冻结)
-   
-   Dify 调用 → CHAT_COMPLETED (800 tokens)
-   
-   Outbox: {event_type: CHAT_COMPLETED, traceId: t-001, idempotency_key: "100-triage-msg001"}
-   
-   SnailJob 投递 → MeterEventHandler
-   → ai_meter_event (event_id=evt-001, scenario_code=triage, meter_item=token, 
-                     raw_quantity=800, credits=?, status=received)
-
-   PricingService.calculate(evt-001, contractId=1, scenario=triage)
-   → 定价优先级: 查合同明细价(无) → 合同折扣(标准包无折扣) → 项目专属价(无) → 平台默认价(5 credits/次)
-   → 输出: credits=5, pricing_source=platform_default, rule_version=v1, rule_snapshot={...}
-   → ai_meter_event (status=pricing_completed, credits=5)
-
-   CreditAccountService.settle(accountId=1, credits=5, idempotencyKey="evt-001")
-   → UPDATE ai_credit_account SET available_credits=199995, used_credits=5, version=1 
-     WHERE id=1 AND version=0 AND available_credits >= 5
-   → ai_credit_ledger (ledger_type=settle, credits=-5, direction=debit, event_id=evt-001)
-   → ai_meter_event (status=settled)
-
-T2-T4: synthetic消息触发3轮Dify调用 (traceId=t-001.1, t-001.2, t-001.3)
-   每轮各自产出 CHAT_COMPLETED 事件 → 各自 settle 5 credits
-   累计扣费: 5 + 5 + 5 + 5 = 20 credits (但用户只感知到1次"分诊"操作)
-
-T5: 用户确认挂号 (traceId=t-001.4)
-   CardActionService → MCP → HIS → his_lock_schedule + his_create_appointment
-   
-   Outbox: {event_type: CARD_ACTION_CONFIRMED, source_type: card, traceId: t-001.4}
-   Outbox: {event_type: MCP_TOOL_WRITE, source_type: mcp, traceId: t-001.4}
-   
-   问题: 两个事件各自进入 PricingService → settle?
-   如果是: 扣费 = 挂号服务(20 credits) + MCP写操作(? credits)
-   可能: 扣费 = 20 credits (如果MCP_TOOL_WRITE正确并入) 或 20+5=25 credits (如果各自计费)
-
-T6: 月底 (2026-01-31)
-   SnailJob → BillingService.generateMonthly(projectId=100, period=2026-01)
-   → 汇总所有 settled 且未出账的 ai_credit_ledger 记录
-   → ai_billing_statement (total=本月累计settle总和, status=draft)
-   → ai_billing_statement_item (按科室、智能体、场景分摊)
-   → 状态: draft → confirmed → invoiced
-```
-
-### 2.2 闭环缺口
-
-**缺口 1(计费粒度与定价模型不匹配)**: 如上所述,synthetic message 的"每次调用独立计费"与定价表的"每次业务操作"计费存在本质冲突。要么改定价模型(按 token / 按调用次数),要么改计费实现(synthetic 链路中的事件合并到一个父事件)。
-
-**缺口 2(事件合并规则的实现位置)**: 即使定义了合并规则(CARD_ACTION_CONFIRMED + MCP_TOOL_WRITE 合并),文档没有明确这个规则在哪个组件实现。选项:
-- A: CardActionService 在写 Outbox 时就不为 MCP 写操作单独写事件(源头合并)
-- B: MeterEventHandler 在处理时识别关联并合并(事后合并)
-- A 更简单,但要求 CardActionService 知道"这个 MCP 调用的计费已经包含在卡片动作中"
-
-**缺口 3(合同快照在计量事件中缺失)**: 第 10.17 节要求事件保存 `pricing_rule_id + pricing_rule_version + rule_snapshot`,第 10.18 节计量事件状态机也包含这些字段。但合同快照没有保存。如果合同在 T5(1月15日)被修改(如新增折扣),T1(1月1日)的事件在重算时会用新合同还是旧合同?
-
-第 10.17 节第3条说"合同价变更后只影响新事件,不重算历史事件",这解决了向前兼容。但审计时无法从事件本身确定"当时用的合同条款是什么"。
-
-**缺口 4(计量事件状态 billed 后,账单被作废的处理)**: 第 10.18 节事件状态机有 `settled → billed`,但没有 `billed → 回退` 的路径。如果账单生成后发现有误需要重算(如批量账务修正),已 billed 的事件如何处理?
-
----
-
-## 3. 文档覆盖度矩阵
-
-以下用四个场景检验文档是否回答了流程中的每个关键问题:
-
-| 关键问题 | 挂号 | 建档 | 舌诊 | 面诊 | 文档覆盖 |
-|---------|------|------|------|------|---------|
-| 用户如何发起? | chat | chat | chat+file upload | chat+file upload | ✓ §7.1, §13.11.2 |
-| AI引擎如何路由? | AgentEngineFactory | 同 | 同 | 同 | ✓ §5.1 |
-| 卡片如何生成? | Dify输出cardKey+cardData | 同 | 同/DirectLLM | 同 | ✓ §5.8.2 |
-| 卡片动作如何执行? | CardActionService+MCP | 同 | N/A | N/A | ✓ §5.5, §7.4 |
-| PII如何保护? | 仅传deptId/doctorId | **需传姓名/身份证** | 图片本身含PII | 面部图片含PII | ⚠ 不完全 |
-| 文件如何流转? | N/A | N/A | **FileAPI→OSS→模型** | 同 | ⚠ 路径不完整 |
-| 计费如何触发? | CHAT_COMPLETED每轮 | CHAT_COMPLETED | **IMAGE_ANALYSIS** | **IMAGE_ANALYSIS** | ✗ 多轮重复计费 |
-| 事件如何合并? | **CARD+MCP双事件** | CARD+MCP | N/A | N/A | ✗ 合并规则缺失 |
-| 异常如何回滚? | §12.6异常表 | 同 | 图片上传失败 | 面部识别失败 | ⚠ 部分覆盖 |
-| 审计如何追溯? | traceId全链路 | traceId | **图片+分析结论** | **图片+分析结论** | ✓ §13.7 |
-
-图例:✓ 覆盖 ⚠ 部分覆盖 ✗ 未覆盖
-
----
-
-## 4. 文档本身的结构问题
-
-### 4.1 计费闭环的"调用→计量→扣费→出账"分散在7个章节
-
-理解完整计费链路需要跳读:§5.8.4 → §10.3 → §10.10 → §10.11 → §10.13 → §10.18 → §12.1。
-
-**建议**:在三期章节开头(§10之前)增加一个一页纸的"计费全景图",用最简化的方式展示"用户一次对话 → 哪些计量事件 → 什么规则折算 → 怎么扣余额 → 何时出账单"。
-
-### 4.2 二分架构(二期/三期)导致部分内容重复
-
-§5.1 对话链路和 §12.1 OpenPlatform 调用入口描述的是同一件事(调用入口的处理),但一个是"二期设计"视角,一个是"三期集成"视角。合并或交叉引用可减少维护负担。
-
----
-
-## 5. 综合评价
-
-文档经过四轮迭代,已从 v4 的"Dify 做总控,OpenPlatform 只渲染"彻底转型为 v5 的生产级方案。2538 行覆盖了架构设计、数据模型、接口契约、链路时序、计量账本、运营报告、部署运维、安全合规、灾备监控、术语表和 ADR 索引。
-
-**本轮发现的 6 个新问题**:
-
-| # | 严重程度 | 问题 | 影响 |
-|---|---------|------|------|
-| 1 | **阻塞** | 计费粒度不一致——synthetic每轮独立计费 vs 定价表按"次"计费 | 四轮链条扣12-32 credits vs 预期3-8 credits |
-| 2 | **阻塞** | CARD_ACTION_CONFIRMED + MCP_TOOL_WRITE 双事件合并规则缺失 | 一次挂号可能被重复扣费 |
-| 3 | **高** | IMAGE_ANALYSIS 事件产出方不明确(Dify/DirectLLM/OpenPlatform) | 舌诊/面诊可能漏计费 |
-| 4 | **高** | 建档场景 PII 经 Dify 流转的边界不清晰 | 数据合规风险 |
-| 5 | **中** | synthetic message 的 scenario_code 来源未定义 | 定价匹配可能错误 |
-| 6 | **中** | 合同快照未在计量事件中保存(仅保存了定价规则快照) | 历史审计不完整 |
-
-**建议:P0(阻塞项)在编码前解决。P1-P2(高/中优先)在对应模块实现时解决。方案文档在此版本冻结。**

+ 0 - 359
docs/ai-platform-review-round5.md

@@ -1,359 +0,0 @@
-# 医梦 AI 中台技术方案 —— 第五轮全流程穿透评审
-
-**评审日期**: 2026-05-23
-**评审方法**: 逐流程逐一穿透 + 架构约束校验 + 跨流程一致性对比
-**评审范围**: v5.0 修订稿(2903 行,含 10 个业务场景 + billingEpisode 闭环 + L1-L4 映射)
-
----
-
-## 0. 本轮新增内容验证
-
-GPT 本轮新增了四大块内容。逐一验证:
-
-| 新增内容 | 位置 | 核心价值 | 验证结果 |
-|---------|------|---------|---------|
-| billingEpisode 计费闭环 | §10.0 | 解决了第四轮发现的计费粒度不一致(阻塞级)问题 | **有效**——synthetic/子调用归入episode,客户侧只在业务完成点扣费 |
-| 四个新医疗场景 | §5.11.5-5.11.8 | 预问诊、诊间病历、住院评估、出院随访 | 流程完整,但存在架构路径问题(见下文) |
-| L1-L4 工程能力映射 | §9.2 | 防止所有客户上一套最重方案 | 有效,但 DirectLLM 在 L1 的角色需补 |
-| 收费项与系统模块关系 | §9.3 | 实施费不进账本,商业语义清晰 | 有效 |
-| 运营交付闭环 | §10.7.1-10.7.2 | 综合运营服务费的交付物可量化 | 有效,但部分指标依赖系统尚未采集的数据 |
-| 业务交付阶段 | §15.0 | 工程路线对齐项目交付里程碑 | 有效 |
----
-
-## 1. 八个场景逐一穿透 + 架构约束校验
-
-以下对文档中每个场景逐步骤执行架构约束校验。约束来自文档自定规则:
-- C1: 外部入口必须经 OpenPlatform 鉴权(§4.2)
-- C2: 流程编排必须经 AgentEngine → Dify(§5.1)
-- C3: HIS 访问必须经 MCP Server(§5.3)
-- C4: 高风险写操作必须有人工确认(§13.5)
-- C5: 所有写操作必须有幂等键(§7.4)
-- C6: 客户侧计费以 billingEpisode 为单位,子调用不独立形成"次"(§10.0)
-- C7: PII 不进入 Dify 输入/输出(§13.8)
-- C8: 医疗文书 AI 只生成草案,必须医生确认(§13.5)
-
-### 1.1 导诊问答
-
-```
-用户: "高血压挂什么科" → OpenPlatform(C1✓) → AgentEngine → Dify(C2✓)
-  → LLM+RAG 生成答复 → 返回文本 → 无卡片
-→ 计量: CHAT_COMPLETED → billingEpisode → settle(N credits)
-```
-
-| 约束 | 判定 |
-|------|------|
-| C1 入口鉴权 | ✓ |
-| C2 编排路径 | ✓ |
-| C4 风险确认 | N/A (L1风险) |
-| C6 billingEpisode | ✓ 单次调用=单次episode |
-
-**结论**: 闭环。无问题。
-
-### 1.2 智能分诊挂号
-
-```
-用户: "我头疼发烧" → OpenPlatform(C1✓) → AgentEngine → Dify(C2✓)
-  → LLM意图→分诊→his_get_departments(C3✓)→返回科室卡片
-→ 卡片实例创建 → 前端渲染
-
-用户点击"内科" → CardActionService → 
-  → synthetic msg round1 → Dify(C2✓) → his_get_doctors(C3✓)→医生卡片
-→ 用户点击"张医生" → CardActionService →
-  → synthetic msg round2 → Dify → his_get_schedule(C3✓)→时间卡片
-→ 用户点击"周二上午" → CardActionService →
-  → synthetic msg round3 → Dify → 确认卡片
-→ 用户确认 → CardActionService(C5✓ 幂等) →
-  → his_lock_schedule(C3✓, C4✓ L3确认) → his_create_appointment(C3✓)
-
-→ 计量: 分诊episode(settle N credits) + 挂号确认episode(settle M credits)
-   4轮synthetic事件的CHAT_COMPLETED作为子事件归入分诊episode(C6✓)
-   MCP_TOOL_WRITE作为挂号episode的detail,不独立计费(C6✓)
-```
-
-| 约束 | 判定 |
-|------|------|
-| C1-C6 全部 | ✓ |
-| synthetic深度=3 | ✓ 在限制内 |
-| billingEpisode归并 | ✓ §10.0规则解决了第4轮的阻塞问题 |
-
-**结论**: 闭环。第四轮发现的阻塞问题(计费粒度不一致、双事件重复扣费)已被 §10.0 billingEpisode 概念解决。
-
-### 1.3 患者建档
-
-```
-用户: "我要建档" → OpenPlatform(C1✓) → Dify(C2✓) → 建档卡片
-  → 卡片定义含字段列表: 姓名/身份证号/手机号/住址
-  → 用户在卡片填写 → CardActionService →
-  → his_create_patient(C3✓) → HIS返回patientId
-
-问题: 卡片字段label(姓名/身份证号)出现在Dify输出中(C7⚠)
-     但字段值通过CardActionService→MCP,不经过Dify ✓
-
-→ 计量: 建档episode(settle N credits)(C6✓)
-```
-
-| 约束 | 判定 | 说明 |
-|------|------|------|
-| C7 PII不进入Dify | ⚠ | 卡片定义中的字段label进入了Dify输出。label是"姓名"不是实际姓名,属于UI元数据非PII。但如有预填数据则违规。 |
-| 其他 | ✓ | |
-
-**残留问题**: §5.8.2 的 cardData 和 businessContext 规则需要区分"卡片元数据(字段label、类型)"和"预填业务数据"。**建议**: 卡片元数据由 OpenPlatform 根据 cardKey 查 ai_card_definition 自行填充,不依赖 Dify 返回。Dify 只返回 cardKey + 业务选择上下文(如已选科室ID),前端再根据 cardKey 获取完整的表单 schema。
-
-### 1.4 舌诊
-
-```
-用户上传舌象照片 → FileAPI → OSS(加密) → 安全扫描 → fileId(C7✓ 上传路径不经过Dify)
-
-用户: "看舌象" + fileId → OpenPlatform(C1✓)
-  → AgentEngine路由: 如果是Dify引擎 → DifyAgentEngine → 
-    1. Dify /files/upload 中转文件
-    2. Dify /chat-messages (files: [{difyFileId}])
-  → 如果是DirectLLM引擎 → DirectLLMAgentEngine → vision model API
-
-  → 模型返回分析 → 卡片展示结果
-
-→ 计量: IMAGE_ANALYSIS → billingEpisode → settle(C6✓)
-→ 合规: 卡片快照只保存分析结论,不保存原图引用或面部特征向量(C7✓)
-```
-
-| 约束 | 判定 | 说明 |
-|------|------|------|
-| C1 入口鉴权 | ✓ | |
-| C2 编排路径 | ⚠ | 路由分支: Dify vs DirectLLM。两路径的计量、审计、文件处理逻辑不一致 |
-| C7 文件合规 | ✓ | §5.11.4 增加了面诊/舌诊合规规则 |
-| C6 billingEpisode | ✓ | |
-
-**残留问题(第四轮未解决)**: IMAGE_ANALYSIS 事件由谁产出?DifyAgentEngine 和 DirectLLMAgentEngine 是两个不同的代码路径:
-- Dify 路径: OpenPlatform 无法直接知道图片被分析了(Dify内部完成),只能靠 Dify 返回的 usage
-- DirectLLM 路径: OpenPlatform 发起 vision API 调用,可以自己产出事件
-
-**建议**: 统一在 OpenPlatform 产出 IMAGE_ANALYSIS 事件。规则: AgentEngine 返回后,OpenPlatform 检查原始请求中是否包含 `files` 参数且文件类型为 image → 产出 IMAGE_ANALYSIS 子事件,挂到当前 billingEpisode。
-
-### 1.5 面诊
-
-与舌诊架构路径完全相同,但有额外合规要求。§5.11.4 规则"不保存原图引用、面部特征向量或可复原的结构化人脸特征"覆盖了这一点。
-
-**结论**: 与舌诊相同的问题和相同的建议。
-
-### 1.6 诊前预问诊与病历草案(新增)
-
-```
-患者 → OpenPlatform(C1✓) → Dify(C2✓) → 知识模板(C7⚠ 路径)
-  → 多轮追问(synthetic) → 结构化预问诊报告
-
-预问诊报告 → 医生确认(C4✓, C8✓) → EMR写入
-
-→ 计量: previsit_episode(C6✓)
-  "多轮追问和synthetic调用归并到同一episode" — 明确
-```
-
-| 约束 | 判定 | 说明 |
-|------|------|------|
-| C1-C6, C8 | ✓ | |
-| **Dify→Knowledge 路径** | ⚠ | 流程图箭头 Dify→Knowledge。§13.9 定义了知识模块边界,但这个箭头暗示 Dify 直接调用 emoon-knowledge。应该通过什么协议?MCP工具?内部API?流程图未标明协议。 |
-
-**残留问题**: 流程图中 Dify → Knowledge 的箭头没有标注协议。§13.9 说"对审计要求高的场景,Dify 不直接检索散乱文档,而是调用平台 RAG/MCP 工具"。预问诊属于审计要求高的场景(生成医疗文书草案)。此处应明确 Dify 通过 MCP 工具调用平台知识库,而非直接连接。
-
-### 1.7 诊间语音病历与质控(新增)
-
-```
-诊间音频 → ASR(说话人分离/实时转写) → NLP结构化抽取 → SOAP/病历草案
-  → 质控服务(完整性/逻辑/时限)
-  → CDSS(鉴别诊断/用药风险)
-  → 医生确认(C4✓, C8✓) → EMR正式保存
-
-→ 计量: ASR_MINUTE + EMR_DRAFT + QC → 同一诊中episode(C6✓)
-```
-
-| 约束 | 判定 | 说明 |
-|------|------|------|
-| **C1 入口鉴权** | ✗ **缺失** | 整个 ASR→NLP→质控管线没有经过 OpenPlatform |
-| **C2 编排路径** | ✗ **缺失** | 音频处理不走 AgentEngine/Dify |
-| **架构兼容性** | ✗ | 实时音频管线是独立的流式处理管道,不在文档的 request-response 架构中 |
-
-**这是本轮最严重的问题**。诊间语音病历是一个**流式管道**(streaming pipeline),而文档整个二期架构是**请求-响应模型**。音频流不能简单地通过 `POST /api/v1/agent/chat` 处理。
-
-**需要补充的设计**:
-1. 音频流的入口:WebSocket 还是持续上传音频片段?
-2. ASR 服务的触发方式:是 OpenPlatform 调第三方 ASR API,还是独立的 ASR 服务直接处理?
-3. ASR 结果如何进入 Dify?作为 inputs 的一段文本?还是 Dify 等外部触发?
-4. 计量: ASR 分钟数如何采集?ASR 服务返回?还是平台计时?
-
-**建议**: 在文档中增加一节"流式音频处理架构",明确:
-- 诊间音频通过 WebSocket 接入 OpenPlatform
-- OpenPlatform 将音频流转发到 ASR 服务(第三方 API 或院内 ASR 引擎)
-- ASR 返回文本后,NLP 结构化抽取可作为 MCP 工具或独立 Service
-- 抽取结果进入 Dify Workflow 的 inputs(作为文本),后续病历生成、质控、CDSS 走标准 AgentEngine→Dify→MCP 路径
-- 音频分钟计量由 OpenPlatform 根据音频流时长或 ASR 服务返回的时长产出
-
-### 1.8 住院入院评估、床位与护理闭环(新增)
-
-```
-患者/医护发起 → OpenPlatform(C1✓, 创建住院episode) → Dify(C2✓)
-  → 预评估/护理等级 → MCP床位查询(C3✓) → HIS
-
-医护确认(C4✓) → HIS写入
-
-IoMT → 预警 → 病区护士站
-  (IoMT路径: 不经过OpenPlatform, C1✗)
-```
-
-| 约束 | 判定 | 说明 |
-|------|------|------|
-| C1-C4 主流程 | ✓ | |
-| **IoMT 路径** | ✗ | IoMT 是独立的物联网数据管道,不经过 OpenPlatform。预警生成、任务派发、护理闭环都不在 AgentEngine 架构中 |
-| **C6 计量** | ⚠ | "IoMT 预警默认按事件或服务包计量",但事件从哪里来? |
-
-**问题**: IoMT 是一个独立的 event-driven 系统,与文档的对话式架构不同。文档没有说明 IoMT 事件如何进入计量体系。
-
-**建议**: IoMT 事件通过 Outbox 或独立事件通道上报到计量中心。IoMT 设备认证和事件鉴权可以独立于 OpenPlatform 的项目鉴权(设备→网关→IoMT Hub→Outbox→Metering)。
-
-### 1.9 出院小结与随访闭环(新增)
-
-```
-医生触发 → Data采集 → Agent → 出院小结草案 → 医生审核(C4✓, C8✓)
-  → 随访计划 → AI随访(电话/微信/问卷) → 风险分层 → 高危→人工
-
-→ 计量: 文书生成 + 随访分钟/次数(C6✓)
-```
-
-| 约束 | 判定 | 说明 |
-|------|------|------|
-| C1-C8 主流程 | ✓ | |
-| **AI随访(outbound call)** | ✗ | 主动外呼电话能力在文档中完全没有设计。语音随访需要 IVR/电话网关/PSTN |
-
-**问题**: AI 随访(主动外呼)是一个 outbound 场景,与 inbound 的 "用户发起对话" 不同。文档中的 SSE/SSE 连接管理都是为 inbound 设计的。Outbound call 需要:
-- 呼叫任务调度(谁、什么时间、什么号码)
-- 电话线路管理
-- 通话状态跟踪
-- 通话录音和 ASR
-- 通话结束后的事件触发
-
-**建议**: 在文档中明确 AI 随访的架构——随访任务由 SnailJob 调度,外呼通过语音网关 API,通话事件通过 Outbox 进入计量,随访结果通过 Dify 或 DirectLLM 进行风险分层。
-
----
-
-## 2. billingEpisode 闭环验证
-
-§10.0 是 GPT 本轮最关键的补充,直接解决了第四轮的阻塞问题。现在验证其设计是否能闭合所有场景。
-
-### 2.1 billingEpisode 的生命周期
-
-文档定义的规则:
-1. 用户主动发起 → 新 billingEpisodeId
-2. synthetic/Dify run/MCP 查询/写操作 → 子事件或成本明细
-3. 客户价值完成点 → settle
-4. 内部成本 → detail
-
-**但缺少 billingEpisode 的状态机**。§10.18 有 ai_meter_event 的状态机(received→pricing_completed→settled→billed),但没有 billingEpisode 的状态机。billingEpisode 是否需要自己的状态?
-
-一个 episode 可能包含多个事件(如分诊episode 含 4 个 CHAT_COMPLETED 子事件 + 1 个 CARD_ACTION 确认事件)。episode 何时算"完成"?
-- 当用户开始了新的主动操作?
-- 当某个业务完成点被触发?
-- 当 conversation 结束?
-
-**建议**: billingEpisode 至少需要以下状态:
-- `open`: 正在收集子事件,尚未到达业务完成点
-- `completed`: 到达业务完成点,父事件已 settle
-- `expired`: 超时未完成(如用户中途关闭),按 partial 策略处理
-- `closed`: 所有子事件已处理完毕
-
-### 2.2 跨 episode 的边界识别
-
-什么情况触发新 episode?
-
-| 场景 | 新episode? | 判断依据 |
-|------|-----------|---------|
-| 用户发起新对话 | ✓ | 用户主动 chat |
-| synthetic 自动续跑 | ✗ | 归入当前 episode |
-| 用户点击卡片 | ⚠ | 是主动操作,但属于当前对话流。应继续当前 episode 还是新开? |
-| 用户在卡片确认后发新消息 | ✓ | 新的主动消息 → 新 episode |
-| Dify 返回新卡片 | ✗ | synthetic 链路内的自动流转 |
-| 医生在医生站触发质控 | ✓ | 不同入口、不同用户 → 新 episode |
-
-文档没有定义卡片动作是否触发新 episode。根据 §10.0 第 1 条"用户主动发起的...卡片动作...创建新的 billingEpisodeId,除非它明确属于已有业务链路"——挂号流程中的卡片动作属于"已有业务链路",所以不创建新 episode。这个规则合理,但"明确属于"的判断标准没有定义。
-
-**建议**: 卡片动作通过 `conversationId + 时间窗口` 判断归属——同一 conversation 内、15 分钟内的卡片动作默认属于同一 billingEpisode,除非该动作完成了"业务完成点"(如挂号确认),此时当前 episode 关闭,下一轮操作(如 confirm 后的新问询)开新 episode。
-
----
-
-## 3. L1-L4 映射中的架构路径缺口
-
-§9.2 的 L1-L4 表非常清晰。但从架构实现角度有一个缺口:
-
-| L级别 | 需要 AgentEngine | 需要 DifyAgentEngine | 需要 DirectLLMAgentEngine | 需要 MCP Server |
-|-------|-----------------|---------------------|--------------------------|-----------------|
-| L1 | ✓ | ✗ (L1只需标准能力接入) | ✓ (直连模型即可) | ✗ |
-| L2 | ✓ | ✓ | ✓ (作为降级) | ✓ |
-| L3 | ✓ | ✓ | ✓ | ✓ |
-| L4 | ✓ | ✓ | ✓ | ✓ |
-
-但文档的 §5.9 DirectLLM 定位说"DirectLLM...承载简单问答、提示词模板、Dify 降级、旧 /chat/apisse 能力迁移"。在 L1 场景下,DirectLLM 是主引擎而非降级引擎——L1 客户可能只用 DirectLLM 完成简单问答,不部署 Dify。
-
-**建议**: §5.9 增加一行:L1 客户可使用 DirectLLMAgentEngine 作为主引擎,不需要 Dify。DirectLLM 在 L1 场景下承担主流程角色,而非仅降级。
-
----
-
-## 4. 场景到架构的路径矩阵
-
-以下用矩阵验证每个场景是否有一条清晰的架构路径:
-
-| 场景 | 入口 | 编排 | HIS访问 | 计量单元 | 架构路径完整? |
-|------|------|------|---------|---------|--------------|
-| 导诊问答 | OpenPlatform | Dify/DirectLLM | N/A | CHAT episode | ✓ |
-| 分诊挂号 | OpenPlatform | Dify | MCP | 分诊episode+挂号episode | ✓ |
-| 患者建档 | OpenPlatform | Dify | MCP | 建档episode | ✓ (C7需补) |
-| 舌诊 | OpenPlatform | Dify/DirectLLM | N/A | IMAGE_ANALYSIS episode | ⚠ (计量产出方) |
-| 面诊 | OpenPlatform | Dify/DirectLLM | N/A | IMAGE_ANALYSIS episode | ⚠ (同上) |
-| 预问诊 | OpenPlatform | Dify | MCP(知识) | previsit_episode | ⚠ (Dify→Knowledge协议) |
-| 诊间语音病历 | **?** | **?** | MCP? | ASR+EMR+QC episode | ✗ (无编排路径) |
-| 住院评估 | OpenPlatform | Dify | MCP | inpatient episode | ✓ |
-| IoMT护理预警 | **IoMT Hub** | **N/A** | N/A | IoMT事件 | ✗ (独立管道) |
-| 出院小结 | OpenPlatform | Dify/DirectLLM | MCP(EMR) | 文书episode | ✓ |
-| AI随访 | **SnailJob** | Dify/DirectLLM | MCP? | 随访episode | ✗ (outbound未设计) |
-
-**3 个场景架构路径不完整**: 诊间语音病历、IoMT护理预警、AI随访。它们需要的不是对话式架构,而是流式处理/事件驱动/任务调度架构。
-
----
-
-## 5. 文档内部一致性检查
-
-### 5.1 已验证的一致性
-
-| 章节对 | 一致性 | 说明 |
-|--------|--------|------|
-| §4.2 调用方向图 ↔ §5.4 对话时序 | ✓ | 入口→Dify→MCP→HIS 方向一致 |
-| §5.8.2 Dify输出格式 ↔ §10.0 billingEpisode | ✓ | cards[] 中的 scenarioCode 可用于 episode 归类 |
-| §7.4 幂等 ↔ §10.18 事件状态机 | ✓ | 幂等键在事件级别防止重复处理 |
-| §9.3 收费项 ↔ §10.0 计费闭环 | ✓ | 实施费/服务费不进能力值账本 |
-| §10.0 billingEpisode ↔ §15.5 开发任务 | ✓ | 任务依赖列标注了 Outbox→计量→账户的顺序 |
-
-### 5.2 发现的不一致
-
-**不一致 1**: §5.11.5 预问诊流程图 Dify→Knowledge 箭头 vs §13.9 规定 Dify 通过 MCP/API 调用平台知识。流程图应标注协议。
-
-**不一致 2**: §10.0 事件归并规则说 "synthetic message 默认作为 episode 下的子事件或成本明细",但 §5.8.4 计量控制规则仍写"每轮 synthetic message 都是一次平台调用,必须产生独立调用日志、计量事件和额度校验"。两条规则不矛盾(可以产生独立事件但不独立 settle),但措辞可能让初级工程师误解为"每轮都独立扣费"。建议 §5.8.4"计量控制"增加一句"但这些事件的客户侧计费统一归入 billingEpisode,不独立扣减客户余额"。
-
-**不一致 3**: §15.5 开发任务表中的"计量事件表"依赖"Outbox事件表",但 §10.0 的 billingEpisode 概念比"写计量事件"更高一层。开发任务表中缺失"billingEpisode 逻辑"任务——事件归并和 episode 管理需要独立的 Service。
-
----
-
-## 6. 综合评价
-
-文档经过五轮迭代,从 12,663 行 v4 重构为 2,903 行 v5,覆盖了 10 个医疗场景、L1-L4 商业模式、完整计费闭环和运营交付体系。
-
-**本轮核心发现**:
-
-| # | 严重程度 | 问题 | 影响场景 |
-|---|---------|------|---------|
-| 1 | **阻塞** | 诊间语音病历的流式音频管道不在现有架构中 | §5.11.6 诊间病历 |
-| 2 | **阻塞** | AI 外呼随访的 outbound 能力未设计 | §5.11.8 出院随访 |
-| 3 | **高** | IoMT 预警管道独立于 OpenPlatform 架构 | §5.11.7 住院护理 |
-| 4 | **高** | billingEpisode 缺少状态机和跨 episode 边界规则 | §10.0 + 所有计费场景 |
-| 5 | **中** | Dify→Knowledge 流程图箭头未标注协议 | §5.11.5 预问诊 |
-| 6 | **中** | IMAGE_ANALYSIS 产出方在Dify/DirectLLM双路径下不一致 | §5.11.3 舌诊 §5.11.4 面诊 |
-| 7 | **低** | §5.8.4 计量控制措辞与 §10.0 billingEpisode 存在理解歧义 | 全场景 |
-| 8 | **低** | DirectLLM 在 L1 中的主引擎角色未在 §5.9 体现 | L1 交付 |
-| 9 | **低** | 开发任务表缺失 billingEpisode 相关任务 | §15.5 |
-
-**最终判断**: 文档的对话式场景(导诊、分诊挂号、建档、舌诊、面诊、预问诊、出院小结)架构路径完整且经五轮打磨质量很高。需要补充的是三个**非对话式管道**的设计:流式音频处理、IoMT 事件处理、outbound 随访呼叫。这三者不是对现有架构的修补,而是需要新增架构模式。建议在文档中增加一节"非对话式 AI 管道"来覆盖。

+ 3804 - 0
docs/接口文档/emoon-ai-openplatform-api-v1.2.openapi.yaml

@@ -0,0 +1,3804 @@
+openapi: 3.0.3
+info:
+  title: EMOON AI OpenPlatform External API
+  version: 1.2.0
+  description: 医梦 AI 中台对外接口契约 v1.2,正式联调基准版。P0/P1 为厂商联调主基线;P2 为受控规划接口。FHIR 映射从主 YAML 移出,避免误解为当前交付承诺。
+servers:
+- url: https://api.{hospital}.emoon.local/api/v1
+  variables:
+    hospital:
+      default: demo
+- url: https://sandbox-api.emoon-ai.com/api/v1
+security:
+- BearerAuth: []
+- HmacAuth: []
+tags:
+- name: Auth
+- name: Platform
+- name: Agent
+- name: Conversation
+- name: File
+- name: Device
+- name: Card
+- name: MCP Tool
+- name: Webhook
+- name: Event
+- name: Usage
+- name: License
+- name: Partner
+- name: Contract
+- name: Credit
+- name: Billing
+- name: Operation
+- name: Audit
+paths:
+  /auth/token:
+    post:
+      summary: 获取短期访问 Token
+      tags:
+      - Auth
+      x-emoon-priority: P0
+      description: client_credentials 模式。
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/TokenRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseToken'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /health:
+    get:
+      summary: 平台健康检查
+      tags:
+      - Platform
+      x-emoon-priority: P0
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseHealth'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /capabilities:
+    get:
+      summary: 查询项目已开通能力
+      tags:
+      - Platform
+      x-emoon-priority: P0
+      parameters: &id005
+      - &id001
+        $ref: '#/components/parameters/ProjectId'
+      - &id002
+        $ref: '#/components/parameters/PartnerId'
+      - &id003
+        $ref: '#/components/parameters/TraceId'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseCapabilities'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /agent/chat:
+    post:
+      summary: 智能体同步对话
+      tags:
+      - Agent
+      x-emoon-priority: P0
+      description: 首轮 conversationId 可为空,平台自动创建会话并返回。支持 Bearer Token;HMAC 模式需携带 X-Emoon-Timestamp、X-Emoon-Nonce、X-Emoon-Signature。
+      parameters: &id004
+      - *id001
+      - *id002
+      - *id003
+      - &id006
+        $ref: '#/components/parameters/IdempotencyKey'
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/AgentChatRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseAgentChat'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /agent/chat/stream:
+    post:
+      summary: 智能体流式对话 SSE
+      tags:
+      - Agent
+      x-emoon-priority: P0
+      description: POST + text/event-stream。断线后使用 conversationId 查询最终消息。
+      parameters: *id004
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/AgentChatRequest'
+      responses:
+        '200':
+          description: SSE stream
+          content:
+            text/event-stream:
+              schema:
+                type: string
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /conversations:
+    post:
+      summary: 创建会话
+      tags:
+      - Conversation
+      x-emoon-priority: P0
+      parameters: *id005
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/ConversationCreateRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseConversation'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    get:
+      summary: 查询会话列表
+      tags:
+      - Conversation
+      x-emoon-priority: P1
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseConversationList'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /conversations/{conversationId}:
+    get:
+      summary: 查询会话详情
+      tags:
+      - Conversation
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/ConversationIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseConversation'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /conversations/{conversationId}/messages:
+    get:
+      summary: 查询会话消息
+      tags:
+      - Conversation
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/ConversationIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseMessageList'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /files/upload:
+    post:
+      summary: 上传图片/音频/文档
+      tags:
+      - File
+      x-emoon-priority: P0
+      description: multipart/form-data 上传,返回 fileId;后续 Agent 调用只传 fileId。
+      parameters: *id004
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseFileUpload'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+      requestBody:
+        required: true
+        content:
+          multipart/form-data:
+            schema:
+              $ref: '#/components/schemas/FileUploadRequest'
+  /files/{fileId}:
+    get:
+      summary: 查询文件元数据
+      tags:
+      - File
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/FileIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseFileMeta'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    delete:
+      summary: 删除或作废文件
+      tags:
+      - File
+      x-emoon-priority: P1
+      description: 删除或作废授权范围内未被锁定的文件。DELETE 本身应保持幂等;正式厂商联调要求传 X-Emoon-Idempotency-Key。同一文件重复删除返回首次处理结果或当前已删除状态。
+      parameters:
+      - $ref: '#/components/parameters/FileIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/IdempotencyKey'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/register:
+    post:
+      summary: 设备注册或激活
+      tags:
+      - Device
+      x-emoon-priority: P0
+      parameters: *id005
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DeviceRegisterRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceRegister'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/{deviceId}/heartbeat:
+    post:
+      summary: 设备心跳
+      tags:
+      - Device
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DeviceHeartbeatRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceHeartbeat'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/{deviceId}/scene:
+    get:
+      summary: 获取设备当前场景配置
+      tags:
+      - Device
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceScene'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    patch:
+      summary: 更新设备场景绑定
+      tags:
+      - Device
+      x-emoon-priority: P1
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DeviceSceneUpdateRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceScene'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/{deviceId}/events:
+    post:
+      summary: 上报设备事件
+      tags:
+      - Device
+      x-emoon-priority: P0
+      description: 异步事件,正常返回 202 Accepted。
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - *id006
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DeviceEventRequest'
+      responses:
+        '202':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseAccepted'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    get:
+      summary: 查询设备事件
+      tags:
+      - Device
+      x-emoon-priority: P1
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceEventList'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/{deviceId}/commands/poll:
+    get:
+      summary: 设备轮询待执行命令
+      tags:
+      - Device
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceCommands'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/{deviceId}/commands/{commandId}/ack:
+    post:
+      summary: 设备命令执行回执
+      tags:
+      - Device
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - $ref: '#/components/parameters/CommandIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - *id006
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/DeviceCommandAckRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /cards/{cardInstanceId}:
+    get:
+      summary: 查询卡片实例
+      tags:
+      - Card
+      x-emoon-priority: P0
+      parameters:
+      - $ref: '#/components/parameters/CardInstanceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseCardInstance'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /cards/{cardInstanceId}/actions/{actionName}:
+    post:
+      summary: 执行卡片动作
+      tags:
+      - Card
+      x-emoon-priority: P0
+      description: 所有产生业务后果的卡片动作必须幂等,且必须携带 operator 与 deviceContext。
+      parameters:
+      - $ref: '#/components/parameters/CardInstanceIdPath'
+      - $ref: '#/components/parameters/ActionNamePath'
+      - *id001
+      - *id002
+      - *id003
+      - *id006
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/CardActionRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseCardAction'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /tools/{toolName}/invoke:
+    post:
+      summary: 调用院内系统工具,面向受控适配方
+      tags:
+      - MCP Tool
+      x-emoon-priority: P0
+      description: 需要 tool:read 或 tool:write scope。普通设备厂商默认不开放。
+      parameters:
+      - $ref: '#/components/parameters/ToolNamePath'
+      - *id001
+      - *id002
+      - *id003
+      - *id006
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/ToolInvokeRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseToolInvoke'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+      x-emoon-required-scopes:
+      - tool:read
+      - tool:write
+      x-emoon-partner-level:
+      - trusted_system_adapter
+      - his_vendor
+      - emoon_internal
+  /usage/summary:
+    get:
+      summary: 查询用量汇总
+      tags:
+      - Usage
+      x-emoon-priority: P0
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/FromDate'
+      - $ref: '#/components/parameters/ToDate'
+      - $ref: '#/components/parameters/GroupBy'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseUsageSummary'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /meter/events:
+    get:
+      summary: 查询计量事件
+      tags:
+      - Usage
+      x-emoon-priority: P0
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/BillingEpisodeId'
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      - $ref: '#/components/parameters/QueryTraceId'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseMeterEvents'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+      description: 查询计量事件。P0 阶段仅支持按 billingEpisodeId 或 traceId 做精确查询,用于联调排障和计量核验;不作为正式账单查询接口,不开放项目级、设备级、时间范围复杂过滤给普通厂商。
+  /agents:
+    get:
+      summary: 查询可用智能体列表
+      tags:
+      - Agent
+      x-emoon-priority: P1
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/ScenarioCode'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseAgentList'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /agents/{agentId}:
+    get:
+      summary: 查询智能体详情
+      tags:
+      - Agent
+      x-emoon-priority: P1
+      parameters:
+      - $ref: '#/components/parameters/AgentIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseAgentDetail'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices:
+    get:
+      summary: 查询设备列表
+      tags:
+      - Device
+      x-emoon-priority: P1
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceList'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /devices/{deviceId}:
+    get:
+      summary: 查询设备详情
+      tags:
+      - Device
+      x-emoon-priority: P1
+      parameters:
+      - $ref: '#/components/parameters/DeviceIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceDetail'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /webhooks:
+    post:
+      summary: 注册 Webhook
+      tags:
+      - Webhook
+      x-emoon-priority: P1
+      parameters: *id005
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/WebhookRegisterRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseWebhook'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+    get:
+      summary: 查询 Webhook 列表
+      tags:
+      - Webhook
+      x-emoon-priority: P1
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseWebhookList'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /webhooks/{webhookId}:
+    delete:
+      summary: 删除 Webhook
+      tags:
+      - Webhook
+      x-emoon-priority: P1
+      parameters:
+      - $ref: '#/components/parameters/WebhookIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /events/ingest:
+    post:
+      summary: 接收外部系统业务事件
+      tags:
+      - Event
+      x-emoon-priority: P1
+      description: 用于 HIS、叫号、迪耐克/病房系统向医梦上报事件。
+      parameters: *id004
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/ExternalEventRequest'
+      responses:
+        '202':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseAccepted'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /tools/catalog:
+    get:
+      summary: 查询授权工具目录
+      tags:
+      - MCP Tool
+      x-emoon-priority: P1
+      parameters: *id005
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseToolCatalog'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+      x-emoon-required-scopes:
+      - tool:read
+  /licenses/devices:
+    get:
+      summary: 查询设备授权状态
+      tags:
+      - License
+      x-emoon-priority: P1
+      parameters:
+      - *id001
+      - *id002
+      - *id003
+      - $ref: '#/components/parameters/Page'
+      - $ref: '#/components/parameters/PageSize'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponseDeviceLicenses'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /partners:
+    post:
+      summary: 创建合作方接入档案
+      tags:
+      - Partner
+      x-emoon-priority: P2
+      description: P2 规划接口,不建议进入春节前 MVP。
+      parameters: *id005
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/PartnerCreateRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /contracts:
+    get:
+      summary: 查询合同与套餐
+      tags:
+      - Contract
+      x-emoon-priority: P2
+      description: P2 规划接口,不建议进入春节前 MVP。
+      parameters: *id005
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /credit/accounts:
+    get:
+      summary: 查询能力值账户
+      tags:
+      - Credit
+      x-emoon-priority: P2
+      description: P2 规划接口,不建议进入春节前 MVP。
+      parameters: *id005
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /billing/invoices:
+    get:
+      summary: 查询账单
+      tags:
+      - Billing
+      x-emoon-priority: P2
+      description: P2 规划接口,不建议进入春节前 MVP。
+      parameters: *id005
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /operation/reports:
+    get:
+      summary: 查询运营报告
+      tags:
+      - Operation
+      x-emoon-priority: P2
+      description: P2 规划接口,不建议进入春节前 MVP。
+      parameters: *id005
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /audit/logs:
+    get:
+      summary: 查询审计日志
+      tags:
+      - Audit
+      x-emoon-priority: P2
+      description: P2 规划接口,不建议进入春节前 MVP。
+      parameters: *id005
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /partners/{partnerId}:
+    get:
+      summary: 查询合作方档案
+      tags:
+      - Partner
+      x-emoon-priority: P2
+      parameters:
+      - $ref: '#/components/parameters/PartnerIdPath'
+      - *id001
+      - *id002
+      - *id003
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /credit/accounts/{accountId}/freeze:
+    post:
+      summary: 冻结能力值
+      tags:
+      - Credit
+      x-emoon-priority: P2
+      description: 内部高风险接口,通常不对外开放。
+      parameters:
+      - $ref: '#/components/parameters/AccountIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - *id006
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/CreditFreezeRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /credit/accounts/{accountId}/settle:
+    post:
+      summary: 结算能力值
+      tags:
+      - Credit
+      x-emoon-priority: P2
+      description: 内部高风险接口,通常不对外开放。
+      parameters:
+      - $ref: '#/components/parameters/AccountIdPath'
+      - *id001
+      - *id002
+      - *id003
+      - *id006
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/CreditSettleRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ApiResponse'
+        '400':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '401':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '403':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '429':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        '500':
+          description: Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+components:
+  securitySchemes:
+    BearerAuth:
+      type: http
+      scheme: bearer
+      bearerFormat: JWT
+    HmacAuth:
+      type: apiKey
+      in: header
+      name: Authorization
+      description: 格式:EMOON-HMAC <access_key>;同时必须携带 X-Emoon-Timestamp、X-Emoon-Nonce、X-Emoon-Signature。
+  parameters:
+    ProjectId:
+      name: X-Emoon-Project-Id
+      in: header
+      required: true
+      schema:
+        type: string
+      description: 项目/医院标识。
+    PartnerId:
+      name: X-Emoon-Partner-Id
+      in: header
+      required: true
+      schema:
+        type: string
+      description: 合作厂商标识。
+    TraceId:
+      name: X-Emoon-Trace-Id
+      in: header
+      required: false
+      schema:
+        type: string
+      description: 链路追踪 ID;不传则平台生成。
+    IdempotencyKey:
+      name: X-Emoon-Idempotency-Key
+      in: header
+      required: false
+      schema:
+        type: string
+      description: 写操作和产生业务事实的请求必须传。
+    Timestamp:
+      name: X-Emoon-Timestamp
+      in: header
+      required: false
+      schema:
+        type: string
+      description: HMAC 模式必填,Unix 毫秒时间戳。
+    Nonce:
+      name: X-Emoon-Nonce
+      in: header
+      required: false
+      schema:
+        type: string
+      description: HMAC 模式必填,防重放随机串。
+    Signature:
+      name: X-Emoon-Signature
+      in: header
+      required: false
+      schema:
+        type: string
+      description: HMAC 模式必填,HMAC-SHA256 签名。
+    DeviceIdHeader:
+      name: X-Emoon-Device-Id
+      in: header
+      required: false
+      schema:
+        type: string
+      description: 设备调用建议传,作为 deviceContext 兜底。
+    DeviceIdPath:
+      name: deviceId
+      in: path
+      required: true
+      schema:
+        type: string
+    ConversationIdPath:
+      name: conversationId
+      in: path
+      required: true
+      schema:
+        type: string
+    FileIdPath:
+      name: fileId
+      in: path
+      required: true
+      schema:
+        type: string
+    CardInstanceIdPath:
+      name: cardInstanceId
+      in: path
+      required: true
+      schema:
+        type: string
+    ActionNamePath:
+      name: actionName
+      in: path
+      required: true
+      schema:
+        type: string
+    CommandIdPath:
+      name: commandId
+      in: path
+      required: true
+      schema:
+        type: string
+    ToolNamePath:
+      name: toolName
+      in: path
+      required: true
+      schema:
+        type: string
+    WebhookIdPath:
+      name: webhookId
+      in: path
+      required: true
+      schema:
+        type: string
+    PartnerIdPath:
+      name: partnerId
+      in: path
+      required: true
+      schema:
+        type: string
+    AccountIdPath:
+      name: accountId
+      in: path
+      required: true
+      schema:
+        type: string
+    AgentIdPath:
+      name: agentId
+      in: path
+      required: true
+      schema:
+        type: string
+    Page:
+      name: page
+      in: query
+      required: false
+      schema:
+        type: integer
+        default: 1
+    PageSize:
+      name: pageSize
+      in: query
+      required: false
+      schema:
+        type: integer
+        default: 20
+        maximum: 200
+    FromDate:
+      name: from
+      in: query
+      required: true
+      schema:
+        type: string
+        format: date
+    ToDate:
+      name: to
+      in: query
+      required: true
+      schema:
+        type: string
+        format: date
+    GroupBy:
+      name: groupBy
+      in: query
+      required: false
+      schema:
+        type: string
+        enum:
+        - project
+        - partner
+        - agent
+        - device
+        - scenario
+        - department
+        - day
+    BillingEpisodeId:
+      name: billingEpisodeId
+      in: query
+      required: false
+      schema:
+        type: string
+      description: 按 billingEpisodeId 查询同一业务 episode 下的计量事件。P0 阶段 /meter/events 仅支持 billingEpisodeId 或 traceId 这类精确查询,不开放项目级、设备级、时间范围复杂过滤。
+    ScenarioCode:
+      name: scenarioCode
+      in: query
+      required: false
+      schema:
+        type: string
+    QueryTraceId:
+      name: traceId
+      in: query
+      required: false
+      schema:
+        type: string
+      description: 按 traceId 查询联调排障相关计量事件。P0 仅用于联调排障,不代表完整账单查询。
+  schemas:
+    ApiResponse:
+      type: object
+      properties:
+        code:
+          type: string
+          example: OK
+        message:
+          type: string
+          example: success
+        traceId:
+          type: string
+        requestId:
+          type: string
+        details:
+          type: object
+          additionalProperties: true
+        data:
+          type: object
+          additionalProperties: true
+    ErrorResponse:
+      type: object
+      properties:
+        code:
+          type: string
+          example: INVALID_PARAM
+        message:
+          type: string
+        details:
+          type: object
+          additionalProperties: true
+        traceId:
+          type: string
+        requestId:
+          type: string
+      required:
+      - code
+      - message
+      - traceId
+    ApiResponseAccepted:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/AcceptedData'
+    AcceptedData:
+      type: object
+      properties:
+        accepted:
+          type: boolean
+        eventId:
+          type: string
+        status:
+          type: string
+          enum:
+          - accepted
+          - duplicate
+          - ignored
+    TokenRequest:
+      type: object
+      required:
+      - grantType
+      - clientId
+      - clientSecret
+      - projectId
+      properties:
+        grantType:
+          type: string
+          enum:
+          - client_credentials
+        clientId:
+          type: string
+        clientSecret:
+          type: string
+        projectId:
+          type: string
+        scopes:
+          type: array
+          items:
+            type: string
+    TokenData:
+      type: object
+      properties:
+        accessToken:
+          type: string
+        tokenType:
+          type: string
+          example: Bearer
+        expiresIn:
+          type: integer
+          example: 7200
+        scopes:
+          type: array
+          items:
+            type: string
+    ApiResponseToken:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/TokenData'
+    HealthData:
+      type: object
+      properties:
+        status:
+          type: string
+          enum:
+          - UP
+          - DOWN
+          - DEGRADED
+        timestamp:
+          type: string
+          format: date-time
+        version:
+          type: string
+    ApiResponseHealth:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/HealthData'
+    CapabilitiesData:
+      type: object
+      properties:
+        projectId:
+          type: string
+        agents:
+          type: array
+          items:
+            $ref: '#/components/schemas/AgentSummary'
+        enabledFeatures:
+          type: array
+          items:
+            type: string
+        toolScopes:
+          type: array
+          items:
+            type: string
+        rateLimit:
+          $ref: '#/components/schemas/RateLimitPolicy'
+    ApiResponseCapabilities:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/CapabilitiesData'
+    RateLimitPolicy:
+      type: object
+      properties:
+        windowSeconds:
+          type: integer
+        limit:
+          type: integer
+    UserContext:
+      type: object
+      required:
+      - externalUserId
+      - role
+      properties:
+        externalUserId:
+          type: string
+        role:
+          type: string
+          enum:
+          - patient
+          - doctor
+          - nurse
+          - admin
+          - system
+          - anonymous
+        patientId:
+          type: string
+          description: HIS 患者 ID,第一期可选。
+        visitId:
+          type: string
+          description: 门诊/住院就诊 ID,第一期可选。
+        encounterId:
+          type: string
+        authorizationId:
+          type: string
+          description: 患者授权记录 ID。
+    DeviceContext:
+      type: object
+      properties:
+        deviceId:
+          type: string
+        deviceType:
+          type: string
+          enum:
+          - mobile_app
+          - guide_screen
+          - self_service_kiosk
+          - robot
+          - waiting_screen
+          - clinic_door_screen
+          - clinic_desktop_screen
+          - doctor_station
+          - four_diagnosis_device
+          - nurse_pda
+          - nursing_whiteboard
+          - bedside_screen
+          - ward_robot
+          - unknown
+          description: 设备类型。门诊类型可进入医梦统一入口直接管理;住院侧类型仅表示 AI 能力映射对象,不代表医梦接管住院硬件。
+        locationCode:
+          type: string
+        sceneCode:
+          type: string
+        clientVersion:
+          type: string
+        managementMode:
+          type: string
+          enum:
+          - emoon_managed
+          - partner_managed
+          - mapped_external
+          description: 设备管理模式。emoon_managed=医梦统一入口客户端和 Device Registry 管理;partner_managed=合作设备厂商管理,医梦通过标准接口接入;mapped_external=迪耐克/鸿蒙病房等外部体系管理,医梦只做映射和事件互通。
+        externalSystem:
+          type: string
+          description: 外部系统标识,例如 dinaike_ward、harmony_ward、his_queue。管理模式为 mapped_external 或 partner_managed 时建议填写。
+        externalDeviceId:
+          type: string
+          description: 外部系统中的设备或终端 ID,用于住院互通和第三方设备映射。
+      required:
+      - deviceId
+      description: 设备上下文。门诊设备原则上由医梦 Device Registry 管理;住院侧类型(nurse_pda / nursing_whiteboard / bedside_screen / ward_robot)通常通过迪耐克/鸿蒙病房体系映射接入,医梦只负责 AI 能力、事件互通和审计,不承担住院硬件基础管理责任。
+    BillingContext:
+      type: object
+      properties:
+        scenarioCode:
+          type: string
+        billingEpisodeId:
+          type: string
+        chargePolicy:
+          type: string
+          enum:
+          - customer_charge
+          - license_covered
+          - bundle_included
+          - internal_cost
+          - no_charge
+    ChatMessage:
+      type: object
+      required:
+      - type
+      - content
+      properties:
+        type:
+          type: string
+          enum:
+          - text
+          - image
+          - audio
+          - file
+        content:
+          type: string
+          description: 文本内容或 fileId。
+        fileId:
+          type: string
+          description: 多模态文件推荐传 fileId。
+        clientMessageId:
+          type: string
+    AgentChatRequest:
+      type: object
+      required:
+      - agentId
+      - message
+      properties:
+        agentId:
+          type: string
+        conversationId:
+          type: string
+          description: 首轮可为空,平台自动创建。
+        user:
+          $ref: '#/components/schemas/UserContext'
+        deviceContext:
+          $ref: '#/components/schemas/DeviceContext'
+        message:
+          $ref: '#/components/schemas/ChatMessage'
+        inputs:
+          type: object
+          additionalProperties: true
+        billingContext:
+          $ref: '#/components/schemas/BillingContext'
+    AgentChatData:
+      type: object
+      properties:
+        conversationId:
+          type: string
+        messageId:
+          type: string
+        answer:
+          type: string
+        cards:
+          type: array
+          items:
+            $ref: '#/components/schemas/CardRef'
+        riskLevel:
+          type: string
+          enum:
+          - low
+          - medium
+          - high
+          - critical
+          - unknown
+        usage:
+          $ref: '#/components/schemas/UsageBrief'
+    CardRef:
+      type: object
+      properties:
+        cardInstanceId:
+          type: string
+        cardKey:
+          type: string
+        status:
+          type: string
+    UsageBrief:
+      type: object
+      properties:
+        meterEventId:
+          type: string
+        billingEpisodeId:
+          type: string
+        chargePolicy:
+          type: string
+        credits:
+          type: number
+    ApiResponseAgentChat:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/AgentChatData'
+    ConversationCreateRequest:
+      type: object
+      properties:
+        agentId:
+          type: string
+        user:
+          $ref: '#/components/schemas/UserContext'
+        deviceContext:
+          $ref: '#/components/schemas/DeviceContext'
+        title:
+          type: string
+        metadata:
+          type: object
+          additionalProperties: true
+    Conversation:
+      type: object
+      properties:
+        conversationId:
+          type: string
+        agentId:
+          type: string
+        title:
+          type: string
+        status:
+          type: string
+          enum:
+          - active
+          - closed
+          - archived
+        createdAt:
+          type: string
+          format: date-time
+        updatedAt:
+          type: string
+          format: date-time
+    ApiResponseConversation:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/Conversation'
+    ApiResponseConversationList:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/Conversation'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    ConversationMessage:
+      type: object
+      properties:
+        messageId:
+          type: string
+        role:
+          type: string
+          enum:
+          - user
+          - assistant
+          - system
+          - tool
+        content:
+          type: string
+        cards:
+          type: array
+          items:
+            $ref: '#/components/schemas/CardRef'
+        createdAt:
+          type: string
+          format: date-time
+    ApiResponseMessageList:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/ConversationMessage'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    FileUploadRequest:
+      type: object
+      required:
+      - file
+      properties:
+        file:
+          type: string
+          format: binary
+        purpose:
+          type: string
+          enum:
+          - tongue_diagnosis
+          - face_diagnosis
+          - report_interpretation
+          - medical_record
+          - audio_transcription
+          - other
+        user:
+          $ref: '#/components/schemas/UserContext'
+        deviceContext:
+          $ref: '#/components/schemas/DeviceContext'
+        metadata:
+          type: object
+          additionalProperties: true
+        clientFileId:
+          type: string
+          description: 厂商侧文件唯一 ID。同一文件重试上传时必须保持不变,推荐用于文件上传幂等。
+        sha256:
+          type: string
+          description: 文件 SHA-256 摘要。用于完整性校验、重复文件识别和审计。
+        source:
+          type: string
+          enum:
+          - patient_app
+          - self_service_kiosk
+          - robot
+          - four_diagnosis_device
+          - doctor_station
+          - ward_terminal
+          - third_party_system
+          description: 文件来源终端或系统。
+        retentionPolicy:
+          type: string
+          enum:
+          - transient
+          - episode
+          - medical_record
+          - audit_only
+          description: 文件保存策略。transient=临时分析;episode=本次就诊 episode 内保存;medical_record=进入病历或正式医疗材料;audit_only=只保留审计摘要。
+      description: 文件上传请求。正式联调推荐幂等键 projectId + partnerId + clientFileId;clientFileId 缺失时可降级使用 projectId + partnerId + sha256 + purpose + user.visitId。
+    FileMeta:
+      type: object
+      properties:
+        fileId:
+          type: string
+        fileType:
+          type: string
+          enum:
+          - image
+          - audio
+          - document
+          - other
+        mimeType:
+          type: string
+        sizeBytes:
+          type: integer
+        sha256:
+          type: string
+        status:
+          type: string
+          enum:
+          - uploaded
+          - scanning
+          - available
+          - blocked
+          - deleted
+        uploadedAt:
+          type: string
+          format: date-time
+        clientFileId:
+          type: string
+        purpose:
+          type: string
+        retentionPolicy:
+          type: string
+          enum:
+          - transient
+          - episode
+          - medical_record
+          - audit_only
+        source:
+          type: string
+    ApiResponseFileUpload:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/FileMeta'
+    ApiResponseFileMeta:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/FileMeta'
+    DeviceRegisterRequest:
+      type: object
+      required:
+      - deviceCode
+      - deviceType
+      - vendor
+      - model
+      - os
+      - clientVersion
+      properties:
+        deviceCode:
+          type: string
+        deviceType:
+          type: string
+        vendor:
+          type: string
+        model:
+          type: string
+        os:
+          type: string
+          enum:
+          - android
+          - harmony
+          - windows
+          - linux
+          - web
+          - unknown
+        clientVersion:
+          type: string
+        capabilities:
+          $ref: '#/components/schemas/DeviceCapabilities'
+        location:
+          $ref: '#/components/schemas/DeviceLocation'
+    DeviceCapabilities:
+      type: object
+      properties:
+        touch:
+          type: boolean
+        screen:
+          type: boolean
+        voiceInput:
+          type: boolean
+        voiceOutput:
+          type: boolean
+        camera:
+          type: boolean
+        qrScan:
+          type: boolean
+        idCardReader:
+          type: boolean
+        medicalCardReader:
+          type: boolean
+        printer:
+          type: boolean
+        navigation:
+          type: boolean
+        nearbyDetect:
+          type: boolean
+    DeviceLocation:
+      type: object
+      properties:
+        building:
+          type: string
+        floor:
+          type: string
+        area:
+          type: string
+        room:
+          type: string
+        bed:
+          type: string
+        locationCode:
+          type: string
+    DeviceRegisterData:
+      type: object
+      properties:
+        deviceId:
+          type: string
+        deviceSecret:
+          type: string
+        admissionLevel:
+          type: string
+          enum:
+          - A
+          - B
+          - C
+          - D
+        defaultSceneCode:
+          type: string
+        status:
+          type: string
+    ApiResponseDeviceRegister:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/DeviceRegisterData'
+    DeviceHeartbeatRequest:
+      type: object
+      required:
+      - status
+      - clientVersion
+      properties:
+        status:
+          type: string
+          enum:
+          - online
+          - offline
+          - error
+          - maintenance
+        clientVersion:
+          type: string
+        ip:
+          type: string
+        currentSceneCode:
+          type: string
+        metrics:
+          type: object
+          additionalProperties: true
+        errors:
+          type: array
+          items:
+            $ref: '#/components/schemas/DeviceError'
+    DeviceError:
+      type: object
+      properties:
+        code:
+          type: string
+        message:
+          type: string
+        occurredAt:
+          type: string
+          format: date-time
+    DeviceHeartbeatData:
+      type: object
+      properties:
+        accepted:
+          type: boolean
+        serverTime:
+          type: string
+          format: date-time
+        nextHeartbeatSeconds:
+          type: integer
+    ApiResponseDeviceHeartbeat:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/DeviceHeartbeatData'
+    DeviceSceneData:
+      type: object
+      properties:
+        deviceId:
+          type: string
+        sceneCode:
+          type: string
+        uiTemplate:
+          type: string
+        agentBindings:
+          type: array
+          items:
+            $ref: '#/components/schemas/AgentBinding'
+        toolScopes:
+          type: array
+          items:
+            type: string
+        features:
+          type: object
+          additionalProperties: true
+        expiresAt:
+          type: string
+          format: date-time
+    AgentBinding:
+      type: object
+      properties:
+        agentId:
+          type: string
+        role:
+          type: string
+        default:
+          type: boolean
+    ApiResponseDeviceScene:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/DeviceSceneData'
+    DeviceSceneUpdateRequest:
+      type: object
+      properties:
+        sceneCode:
+          type: string
+        uiTemplate:
+          type: string
+        agentBindings:
+          type: array
+          items:
+            $ref: '#/components/schemas/AgentBinding'
+        effectiveAt:
+          type: string
+          format: date-time
+    DeviceEventRequest:
+      type: object
+      required:
+      - clientEventId
+      - eventType
+      - occurredAt
+      properties:
+        clientEventId:
+          type: string
+        eventType:
+          type: string
+          enum:
+          - qr_scanned
+          - card_read
+          - print_completed
+          - print_failed
+          - nearby_detected
+          - navigation_started
+          - navigation_completed
+          - device_error
+          - user_interaction
+          - custom
+        occurredAt:
+          type: string
+          format: date-time
+        payload:
+          oneOf:
+          - $ref: '#/components/schemas/NearbyDetectedPayload'
+          - type: object
+            additionalProperties: true
+        billingContext:
+          $ref: '#/components/schemas/BillingContext'
+    NearbyDetectedPayload:
+      type: object
+      properties:
+        subject:
+          type: object
+          properties:
+            type:
+              type: string
+              enum:
+              - temporary_token
+              - ble_device_id
+              - nfc_token
+              - qr_session
+            value:
+              type: string
+        signal:
+          type: object
+          properties:
+            type:
+              type: string
+              enum:
+              - ble
+              - nfc
+              - wifi
+              - harmony
+              - qrcode
+            rssi:
+              type: integer
+            confidence:
+              type: number
+              format: float
+        areaCode:
+          type: string
+    DeviceCommand:
+      type: object
+      properties:
+        commandId:
+          type: string
+        commandType:
+          type: string
+          enum:
+          - show_page
+          - render_card
+          - play_tts
+          - start_navigation
+          - stop_navigation
+          - return_dock
+          - print_receipt
+          - refresh_scene
+          - upgrade_client
+          - custom
+        payload:
+          type: object
+          additionalProperties: true
+        expiresAt:
+          type: string
+          format: date-time
+    ApiResponseDeviceCommands:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            commands:
+              type: array
+              items:
+                $ref: '#/components/schemas/DeviceCommand'
+    DeviceCommandAckRequest:
+      type: object
+      required:
+      - status
+      properties:
+        status:
+          type: string
+          enum:
+          - success
+          - failed
+          - processing
+          - rejected
+        message:
+          type: string
+        result:
+          type: object
+          additionalProperties: true
+    CardInstance:
+      type: object
+      properties:
+        cardInstanceId:
+          type: string
+        cardKey:
+          type: string
+        status:
+          type: string
+          enum:
+          - active
+          - submitted
+          - processing
+          - completed
+          - failed
+          - expired
+          - cancelled
+        renderPayload:
+          type: object
+          additionalProperties: true
+        actions:
+          type: array
+          items:
+            type: string
+        expiresAt:
+          type: string
+          format: date-time
+    ApiResponseCardInstance:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/CardInstance'
+    OperatorContext:
+      type: object
+      required:
+      - role
+      - externalUserId
+      properties:
+        role:
+          type: string
+          enum:
+          - patient
+          - doctor
+          - nurse
+          - admin
+          - system
+        externalUserId:
+          type: string
+        patientId:
+          type: string
+        doctorId:
+          type: string
+        nurseId:
+          type: string
+    CardActionRequest:
+      type: object
+      required:
+      - clientActionId
+      - operator
+      - deviceContext
+      - payload
+      properties:
+        clientActionId:
+          type: string
+        operator:
+          $ref: '#/components/schemas/OperatorContext'
+        deviceContext:
+          $ref: '#/components/schemas/DeviceContext'
+        payload:
+          type: object
+          additionalProperties: true
+        confirm:
+          type: boolean
+        billingContext:
+          $ref: '#/components/schemas/BillingContext'
+    CardActionData:
+      type: object
+      properties:
+        cardInstanceId:
+          type: string
+        actionName:
+          type: string
+        status:
+          type: string
+          enum:
+          - accepted
+          - processing
+          - completed
+          - failed
+          - duplicate
+        result:
+          type: object
+          additionalProperties: true
+        nextCard:
+          $ref: '#/components/schemas/CardRef'
+    ApiResponseCardAction:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/CardActionData'
+    ToolInvokeRequest:
+      type: object
+      required:
+      - toolCallId
+      - context
+      - input
+      properties:
+        toolCallId:
+          type: string
+        context:
+          $ref: '#/components/schemas/ToolContext'
+        input:
+          type: object
+          additionalProperties: true
+        billingContext:
+          $ref: '#/components/schemas/BillingContext'
+        riskLevel:
+          $ref: '#/components/schemas/ToolRiskLevel'
+      description: 受控工具调用请求。仅开放给医梦 MCP Tool Server、HIS/EMR/LIS/PACS/叫号/支付等受控适配方;普通设备厂商不得直接调用。写操作必须幂等,并按 riskLevel 执行确认、权限和审计策略。
+    ToolContext:
+      type: object
+      properties:
+        projectId:
+          type: string
+        user:
+          $ref: '#/components/schemas/UserContext'
+        deviceContext:
+          $ref: '#/components/schemas/DeviceContext'
+        cardInstanceId:
+          type: string
+        traceId:
+          type: string
+    ToolInvokeData:
+      type: object
+      properties:
+        toolCallId:
+          type: string
+        toolName:
+          type: string
+        status:
+          type: string
+          enum:
+          - success
+          - failed
+          - timeout
+          - forbidden
+        result:
+          type: object
+          additionalProperties: true
+        error:
+          $ref: '#/components/schemas/ErrorResponse'
+    ApiResponseToolInvoke:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/ToolInvokeData'
+    WebhookRegisterRequest:
+      type: object
+      required:
+      - url
+      - eventTypes
+      properties:
+        url:
+          type: string
+          format: uri
+        eventTypes:
+          type: array
+          items:
+            type: string
+        secret:
+          type: string
+        description:
+          type: string
+    WebhookData:
+      type: object
+      properties:
+        webhookId:
+          type: string
+        url:
+          type: string
+        eventTypes:
+          type: array
+          items:
+            type: string
+        status:
+          type: string
+          enum:
+          - active
+          - disabled
+    ApiResponseWebhook:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/WebhookData'
+    ApiResponseWebhookList:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/WebhookData'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    ExternalEventRequest:
+      type: object
+      required:
+      - eventId
+      - eventType
+      - occurredAt
+      - sourceSystem
+      properties:
+        eventId:
+          type: string
+        eventType:
+          type: string
+        sourceSystem:
+          type: string
+        occurredAt:
+          type: string
+          format: date-time
+        user:
+          $ref: '#/components/schemas/UserContext'
+        deviceContext:
+          $ref: '#/components/schemas/DeviceContext'
+        payload:
+          type: object
+          additionalProperties: true
+    ToolCatalogItem:
+      type: object
+      properties:
+        toolName:
+          type: string
+        toolType:
+          type: string
+          enum:
+          - read
+          - write
+        description:
+          type: string
+        inputSchema:
+          type: object
+          additionalProperties: true
+        outputSchema:
+          type: object
+          additionalProperties: true
+        riskLevel:
+          $ref: '#/components/schemas/ToolRiskLevel'
+    ApiResponseToolCatalog:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            tools:
+              type: array
+              items:
+                $ref: '#/components/schemas/ToolCatalogItem'
+    UsageSummaryData:
+      type: object
+      properties:
+        from:
+          type: string
+          format: date
+        to:
+          type: string
+          format: date
+        groupBy:
+          type: string
+        items:
+          type: array
+          items:
+            type: object
+            additionalProperties: true
+    ApiResponseUsageSummary:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/UsageSummaryData'
+    MeterEvent:
+      type: object
+      properties:
+        meterEventId:
+          type: string
+        billingEpisodeId:
+          type: string
+        projectId:
+          type: string
+        partnerId:
+          type: string
+        agentId:
+          type: string
+        deviceId:
+          type: string
+        scenarioCode:
+          type: string
+        sourceType:
+          type: string
+          enum:
+          - openplatform
+          - dify
+          - mcp
+          - card
+          - device
+          - file
+          - webhook
+        sourceId:
+          type: string
+        meterItem:
+          type: string
+        rawQuantity:
+          type: number
+        credits:
+          type: number
+        chargePolicy:
+          type: string
+          enum:
+          - customer_charge
+          - license_covered
+          - bundle_included
+          - internal_cost
+          - no_charge
+        status:
+          type: string
+          enum:
+          - pending
+          - settled
+          - reversed
+          - failed
+        occurredAt:
+          type: string
+          format: date-time
+    ApiResponseMeterEvents:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/MeterEvent'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    DeviceLicense:
+      type: object
+      properties:
+        deviceId:
+          type: string
+        licenseStatus:
+          type: string
+          enum:
+          - active
+          - expired
+          - suspended
+          - not_bound
+        licenseType:
+          type: string
+        validTo:
+          type: string
+          format: date
+    ApiResponseDeviceLicenses:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/DeviceLicense'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    AgentSummary:
+      type: object
+      properties:
+        agentId:
+          type: string
+        name:
+          type: string
+        scenarioCode:
+          type: string
+        status:
+          type: string
+    ApiResponseAgentList:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/AgentSummary'
+    AgentDetail:
+      allOf:
+      - $ref: '#/components/schemas/AgentSummary'
+      properties:
+        description:
+          type: string
+        supportedMessageTypes:
+          type: array
+          items:
+            type: string
+        supportedCards:
+          type: array
+          items:
+            type: string
+    ApiResponseAgentDetail:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/AgentDetail'
+    DeviceDetail:
+      type: object
+      properties:
+        deviceId:
+          type: string
+        deviceCode:
+          type: string
+        deviceType:
+          type: string
+        vendor:
+          type: string
+        model:
+          type: string
+        location:
+          $ref: '#/components/schemas/DeviceLocation'
+        capabilities:
+          $ref: '#/components/schemas/DeviceCapabilities'
+        status:
+          type: string
+        clientVersion:
+          type: string
+        lastHeartbeat:
+          type: string
+          format: date-time
+    ApiResponseDeviceDetail:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          $ref: '#/components/schemas/DeviceDetail'
+    ApiResponseDeviceList:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/DeviceDetail'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    ApiResponseDeviceEventList:
+      allOf:
+      - $ref: '#/components/schemas/ApiResponse'
+      properties:
+        data:
+          type: object
+          properties:
+            items:
+              type: array
+              items:
+                $ref: '#/components/schemas/DeviceEventRequest'
+            page:
+              $ref: '#/components/schemas/PageInfo'
+    PageInfo:
+      type: object
+      properties:
+        page:
+          type: integer
+        pageSize:
+          type: integer
+        total:
+          type: integer
+    PartnerCreateRequest:
+      type: object
+      properties:
+        partnerName:
+          type: string
+        partnerType:
+          type: string
+        scopes:
+          type: array
+          items:
+            type: string
+    CreditFreezeRequest:
+      type: object
+      properties:
+        sourceEventId:
+          type: string
+        credits:
+          type: number
+        reason:
+          type: string
+    CreditSettleRequest:
+      type: object
+      properties:
+        freezeId:
+          type: string
+        meterEventId:
+          type: string
+        actualCredits:
+          type: number
+    ToolRiskLevel:
+      type: string
+      enum:
+      - L1_QUERY
+      - L2_SENSITIVE_READ
+      - L3_BUSINESS_WRITE
+      - L4_FINANCIAL_ACTION
+      - L5_MEDICAL_WRITE
+      description: 工具风险等级。L1 查询、L2 敏感查询、L3 业务写入、L4 财务动作、L5 医疗正式写入。
+x-emoon-api-version: 2026-05-29-v1.2
+x-emoon-document-status: 正式联调基准版

+ 707 - 0
docs/接口文档/医梦AI中台对外接口设计文档_v1.2_正式联调基准版.md

@@ -0,0 +1,707 @@
+# 医梦 AI 中台对外接口设计文档 v1.2|正式联调基准版
+
+> 文档定位:厂商对接接口全貌 + MVP 可落地接口契约说明  
+> 适用对象:HIS/EMR/LIS/PACS/叫号/支付厂商、机器人厂商、自助机/导诊屏/诊室屏/大屏厂商、四诊仪厂商、迪耐克/鸿蒙病房互通方、医院信息科、医梦内部研发与测试  
+> OpenAPI 文件:`emoon-ai-openplatform-api-v1.2.openapi.yaml`  
+> 版本:v1.2  
+> 生成时间:2026-05-29 15:30
+
+---
+
+## 0. 本版结论
+
+本版不是把接口无限做大,而是把接口分成三层:
+
+| 优先级 | 定位 | 研发策略 | 是否建议春节前稳定 |
+|---|---|---|---|
+| **P0** | 厂商联调前必须稳定的最小闭环 | 必须进入 OpenAPI;请求/响应 schema 要相对完整 | 是 |
+| **P1** | 第一轮试点上线增强能力 | 先进入接口规划和 OpenAPI,占位或局部实现 | 视项目节奏 |
+| **P2** | 生产运营、商业闭环、区域化能力 | 规划全貌,避免未来重构;不承诺春节前完成 | 否 |
+
+本版在 v1.1 基础上采纳本轮审查中合理的问题,形成正式联调基准版:补充文件删除幂等、文件上传幂等语义、`clientFileId`、`sha256`、`retentionPolicy`、住院设备映射边界、工具风险等级、`/meter/events` 查询范围收敛,并将 FHIR 可选映射从主 OpenAPI YAML 移出,避免厂商误解为当前交付承诺。
+
+
+
+### v1.2 相比 v1.1 的关键修正
+
+| 修正项 | 处理方式 | 影响 |
+|---|---|---|
+| 文件删除幂等 | `DELETE /files/{fileId}` 增加 `X-Emoon-Idempotency-Key`,重复删除返回首次结果或已删除状态 | 防止敏感文件删除操作重试时审计不一致 |
+| 文件上传幂等 | `FileUploadRequest` 增加 `clientFileId`、`sha256`、`source`、`retentionPolicy` | 支撑舌诊、面诊、报告、音频等文件的安全上传与重复上传识别 |
+| 住院设备边界 | `DeviceContext` 增加 `managementMode`、`externalSystem`、`externalDeviceId` | 区分医梦直管门诊设备与迪耐克/鸿蒙病房映射设备 |
+| 工具风险等级 | `ToolInvokeRequest` / `ToolCatalogItem` 增加 `riskLevel` | 明确查询、敏感查询、业务写入、财务动作、医疗正式写入的不同控制要求 |
+| 计量查询收敛 | `/meter/events` P0 只支持 `billingEpisodeId` / `traceId` 精确查询 | 防止普通厂商误以为可查询完整项目账单或全量成本明细 |
+| FHIR 降噪 | `/fhir/{resourceType}/$translate` 从主 YAML 移除,仅保留为远期可选规划 | 避免 HIS 厂商误解医梦当前要提供完整 FHIR 网关 |
+
+关键取舍:
+
+1. **不直接暴露 Dify Key**:所有 AI 调用统一走 OpenPlatform。
+2. **普通设备厂商不得直接调用 `/tools/{toolName}/invoke`**:设备只能走设备接口、智能体接口、卡片动作接口。
+3. **写操作必须幂等**:挂号、签到、缴费、卡片动作、HIS 写接口必须带 `X-Emoon-Idempotency-Key`。
+4. **文件先上传后引用**:舌诊、面诊、报告解读等多模态场景先调用 `/files/upload` 获得 `fileId`,再传入 `/agent/chat`。
+5. **设备上下文进入每次关键调用**:`deviceContext` 进入 chat、card action、device event、meter event。
+6. **P0 能真实联调,P1/P2 先给全貌**:防止接口规划缺口导致未来返工。
+
+---
+
+## 1. 接口设计边界
+
+### 1.1 医梦中台对外开放的不是“数据库接口”
+
+对外接口只开放业务能力和设备能力,不开放内部表结构。厂商不应关心医梦内部使用 Dify、MCP、Card Runtime、Metering 还是其他实现。
+
+```text
+外部厂商 -> OpenPlatform API -> 鉴权/限流/幂等/审计 -> Agent/Card/Device/Tool -> 医院系统或设备
+```
+
+### 1.2 三类厂商的接入边界
+
+| 厂商类型 | 推荐接入方式 | 禁止事项 |
+|---|---|---|
+| 机器人/自助机/屏幕厂商 | Device API + Agent API + Card Action API | 不直接调用 HIS Tool,不直接持有 Dify Key |
+| HIS/EMR/LIS/PACS/叫号/支付厂商 | Tool API + Webhook/Event API | 不直接参与 AI 会话编排,不绕过医梦审计链路 |
+| 四诊仪/专用设备厂商 | Device API + File API + Agent/Card API | 不把原始敏感图像暴露给非授权系统 |
+
+---
+
+## 2. 全量接口规划:按优先级分类
+
+### P0 接口清单
+
+|模块|主要厂商/对象|方法|路径|接口|说明|
+|---|---|---|---|---|---|
+|通用平台|全部厂商|POST|`/auth/token`|获取短期访问 Token|厂商使用 client_credentials 获取 accessToken;也可用于服务端系统对接。|
+|通用平台|全部厂商|GET|`/health`|平台健康检查|用于联调前探活,不返回敏感配置。|
+|通用平台|全部厂商|GET|`/capabilities`|查询项目已开通能力|让厂商知道当前项目可调用哪些 Agent、工具、设备能力和计费策略。|
+|智能体与会话|机器人/自助机/导诊屏/诊室屏|POST|`/agent/chat`|智能体同步对话|首轮 conversationId 可为空,平台自动创建会话并返回。|
+|智能体与会话|机器人/自助机/导诊屏/诊室屏|POST|`/agent/chat/stream`|智能体流式对话 SSE|POST + SSE;必须支持断线后按 conversationId 查询最终消息和卡片。|
+|智能体与会话|终端厂商/患者端/医生端|POST|`/conversations`|创建会话|可选预创建;chat 不传 conversationId 时自动创建。|
+|智能体与会话|终端厂商/患者端/医生端|GET|`/conversations/{conversationId}`|查询会话详情|用于断线恢复、历史状态恢复。|
+|智能体与会话|终端厂商/患者端/医生端|GET|`/conversations/{conversationId}/messages`|查询会话消息|用于 SSE 断线后的最终文本和卡片恢复。|
+|文件与多模态|四诊仪/自助机/患者端/医生端|POST|`/files/upload`|上传图片/音频/文档|舌诊、面诊、报告解读等场景先上传文件,chat 只传 fileId。|
+|文件与多模态|四诊仪/自助机/患者端/医生端|GET|`/files/{fileId}`|查询文件元数据|只返回元数据和受控访问状态,不默认返回公开 URL。|
+|设备接入|机器人/自助机/大屏/诊室屏|POST|`/devices/register`|设备注册或激活|设备拿到 deviceId、deviceSecret、准入等级、默认场景。|
+|设备接入|机器人/自助机/大屏/诊室屏|POST|`/devices/{deviceId}/heartbeat`|设备心跳|上报在线状态、版本、当前场景、错误摘要。|
+|设备接入|机器人/自助机/大屏/诊室屏|GET|`/devices/{deviceId}/scene`|获取设备场景配置|返回 UI 模板、Agent 绑定、工具权限、设备能力开关。|
+|设备接入|机器人/自助机/大屏/诊室屏|POST|`/devices/{deviceId}/events`|上报设备事件|扫码、打印、读卡、靠近、导航完成、异常等事件。|
+|设备接入|机器人/自助机/大屏/诊室屏|GET|`/devices/{deviceId}/commands/poll`|轮询待执行命令|MVP 采用轮询,避免设备厂商必须开放公网回调。|
+|设备接入|机器人/自助机/大屏/诊室屏|POST|`/devices/{deviceId}/commands/{commandId}/ack`|设备命令回执|设备执行命令后回报 success/failed。|
+|卡片动作|终端厂商/HIS适配方|GET|`/cards/{cardInstanceId}`|查询卡片实例|用于卡片恢复、状态查询、历史审计。|
+|卡片动作|终端厂商/HIS适配方|POST|`/cards/{cardInstanceId}/actions/{actionName}`|执行卡片动作|挂号、签到、建档、确认病历等确定性动作统一走卡片动作。|
+|受控工具|HIS/EMR/LIS/PACS/叫号/支付适配方|POST|`/tools/{toolName}/invoke`|调用院内系统工具|仅开放给受控系统适配方;普通设备厂商不得直接调用。|
+|计量与运营|医院信息科/医梦运营|GET|`/usage/summary`|查询基础用量汇总|春节前先做项目/设备/智能体/场景维度基础统计。|
+|计量与运营|医院信息科/医梦运营|GET|`/meter/events`|查询计量事件|用于排障、能力值流水追踪,P0 只读。|
+### P1 接口清单
+
+|模块|主要厂商/对象|方法|路径|接口|说明|
+|---|---|---|---|---|---|
+|智能体与会话|终端厂商/患者端/医生端|GET|`/conversations`|查询会话列表|患者端历史咨询、医生端历史 AI 交互。|
+|智能体目录|终端厂商/运营后台|GET|`/agents`|查询可用智能体列表|让终端动态展示可用能力。|
+|智能体目录|终端厂商/运营后台|GET|`/agents/{agentId}`|查询智能体详情|返回 Agent 名称、场景、输入能力、输出卡片类型。|
+|设备治理|设备厂商/医梦运营|GET|`/devices`|查询设备列表|设备运维、授权、在线率统计。|
+|设备治理|设备厂商/医梦运营|GET|`/devices/{deviceId}`|查询设备详情|设备能力、版本、位置、场景绑定。|
+|设备治理|医梦运营/设备厂商|PATCH|`/devices/{deviceId}/scene`|更新设备场景绑定|灰度切换 UI 模板和 Agent 绑定。|
+|设备治理|医梦运营/设备厂商|GET|`/devices/{deviceId}/events`|查询设备事件|定位设备问题和事件回放。|
+|Webhook|HIS/叫号/机器人/设备厂商|POST|`/webhooks`|注册 Webhook|第三方系统回调事件订阅。|
+|Webhook|HIS/叫号/机器人/设备厂商|GET|`/webhooks`|查询 Webhook 列表|运营和联调管理。|
+|Webhook|HIS/叫号/机器人/设备厂商|DELETE|`/webhooks/{webhookId}`|删除 Webhook|关闭事件订阅。|
+|事件接入|HIS/叫号/迪耐克/病房系统|POST|`/events/ingest`|接收外部系统业务事件|挂号成功、叫号变更、护理预警等外部事件接入。|
+|受控工具|HIS/EMR/LIS/PACS/叫号/支付适配方|GET|`/tools/catalog`|查询授权工具目录|让受控适配方知道当前项目可调用工具和 schema。|
+|设备授权|医院信息科/医梦运营|GET|`/licenses/devices`|查询设备授权状态|设备授权、入口客户端授权和运维服务统计。|
+### P2 接口清单
+
+> P2 是远期规划能力,不进入春节前门诊主交付。FHIR 可选映射已从主 OpenAPI YAML 移除,仅在医院已有 FHIR 网关且项目合同明确要求时,另行提供独立 `emoon-ai-fhir-future.openapi.yaml`。
+
+
+|模块|主要厂商/对象|方法|路径|接口|说明|
+|---|---|---|---|---|---|
+|伙伴与准入|医梦运营/合作厂商|POST|`/partners`|创建合作方接入档案|正式厂商准入、scope、密钥、设备类型绑定。|
+|伙伴与准入|医梦运营/合作厂商|GET|`/partners/{partnerId}`|查询合作方档案|合规、联调、授权审计。|
+|合同与套餐|医院/医梦商务运营|GET|`/contracts`|查询合同与套餐|对接合同、套餐、能力值包。|
+|账户与额度|医院/医梦商务运营|GET|`/credit/accounts`|查询能力值账户|主账户、科室账户、区域账户。|
+|账户与额度|医梦内部系统|POST|`/credit/accounts/{accountId}/freeze`|冻结能力值|高成本任务预冻结,通常不对外开放。|
+|账户与额度|医梦内部系统|POST|`/credit/accounts/{accountId}/settle`|结算能力值|由计量账本驱动,通常不对外开放。|
+|账单结算|医院/医梦商务运营|GET|`/billing/invoices`|查询账单|月账单、超量账单、科室分摊。|
+|运营报表|医院/医梦运营|GET|`/operation/reports`|查询运营报告|用量、设备、智能体、科室、成本和质量指标。|
+|审计合规|信息科/医梦运维|GET|`/audit/logs`|查询审计日志|敏感操作、工具调用、卡片动作、设备事件。|
+
+---
+
+## 3. 全量接口规划:按厂商分类
+
+### 全部厂商
+
+|建议接入接口|
+|---|
+|`POST /auth/token`|
+|`GET /health`|
+|`GET /capabilities`|
+### 机器人厂商
+
+|建议接入接口|
+|---|
+|`POST /devices/register`|
+|`POST /devices/{deviceId}/heartbeat`|
+|`GET /devices/{deviceId}/scene`|
+|`POST /devices/{deviceId}/events`|
+|`GET /devices/{deviceId}/commands/poll`|
+|`POST /devices/{deviceId}/commands/{commandId}/ack`|
+|`POST /agent/chat`|
+|`POST /agent/chat/stream`|
+### 自助机/导诊屏/大屏/诊室屏厂商
+
+|建议接入接口|
+|---|
+|`POST /devices/register`|
+|`POST /devices/{deviceId}/heartbeat`|
+|`GET /devices/{deviceId}/scene`|
+|`POST /devices/{deviceId}/events`|
+|`GET /cards/{cardInstanceId}`|
+|`POST /cards/{cardInstanceId}/actions/{actionName}`|
+|`POST /agent/chat`|
+### HIS/EMR/LIS/PACS/叫号/支付厂商
+
+|建议接入接口|
+|---|
+|`POST /tools/{toolName}/invoke`|
+|`GET /tools/catalog`|
+|`POST /webhooks`|
+|`POST /events/ingest`|
+|`GET /meter/events`|
+### 四诊仪厂商
+
+|建议接入接口|
+|---|
+|`POST /devices/register`|
+|`POST /devices/{deviceId}/heartbeat`|
+|`POST /files/upload`|
+|`POST /agent/chat`|
+|`GET /cards/{cardInstanceId}`|
+|`POST /cards/{cardInstanceId}/actions/{actionName}`|
+### 迪耐克/鸿蒙病房/住院互通方
+
+|建议接入接口|
+|---|
+|`POST /events/ingest`|
+|`POST /devices/register(仅映射终端)`|
+|`POST /devices/{deviceId}/events`|
+|`POST /agent/chat`|
+|`GET /tools/catalog`|
+### 医院信息科/运营方
+
+|建议接入接口|
+|---|
+|`GET /usage/summary`|
+|`GET /meter/events`|
+|`GET /licenses/devices`|
+|`GET /operation/reports`|
+|`GET /audit/logs`|
+
+---
+
+## 4. 通用规范
+
+### 4.1 Base URL 与版本
+
+| 环境 | Base URL |
+|---|---|
+| 沙箱 | `https://sandbox-api.emoon-ai.com/api/v1` |
+| 医院私有化 | `https://api.{hospital}.emoon.local/api/v1` |
+
+版本策略:
+
+| 规则 | 说明 |
+|---|---|
+| URL 版本 | 破坏性变更升级 `/api/v2` |
+| 非破坏性变更 | 新增可选字段、返回新增字段,不升级 URL |
+| 枚举扩展 | 厂商必须能忽略未知枚举或降级处理 |
+| 废弃策略 | 生产接口废弃前至少提前通知,具体周期写入合同或联调协议 |
+
+### 4.2 鉴权方式
+
+本版保留两种鉴权。MVP 阶段可以先稳定 Bearer Token,P0 高风险写操作逐步增加 HMAC。
+
+| 鉴权 | 适用对象 | Header |
+|---|---|---|
+| Bearer Token | HIS 厂商、后台系统、受控服务器端 | `Authorization: Bearer <access_token>` |
+| HMAC 签名 | 自助机、机器人、设备侧高风险调用 | `Authorization: EMOON-HMAC <access_key>` + 签名头 |
+
+HMAC 必要请求头:
+
+| Header | 必填 | 说明 |
+|---|---:|---|
+| `X-Emoon-Project-Id` | 是 | 项目/医院标识 |
+| `X-Emoon-Partner-Id` | 是 | 合作方标识 |
+| `X-Emoon-Timestamp` | 是 | Unix 毫秒时间戳,默认允许 ±300 秒 |
+| `X-Emoon-Nonce` | 是 | 随机串,防重放 |
+| `X-Emoon-Signature` | HMAC 模式必填 | HMAC-SHA256 签名 |
+| `X-Emoon-Trace-Id` | 建议 | 链路排障 ID |
+| `X-Emoon-Idempotency-Key` | 写接口必填 | 幂等键 |
+| `X-Emoon-Device-Id` | 设备调用建议 | 设备上下文兜底 |
+
+推荐签名串:
+
+```text
+HTTP_METHOD + "
+" +
+PATH_WITH_QUERY + "
+" +
+X-Emoon-Timestamp + "
+" +
+X-Emoon-Nonce + "
+" +
+SHA256_HEX(request_body)
+```
+
+### 4.3 Scope 与高风险接口
+
+| Scope | 能力 |
+|---|---|
+| `agent:invoke` | 调用智能体 |
+| `device:write` | 设备注册、心跳、事件上报 |
+| `card:action` | 执行卡片动作 |
+| `file:write` | 文件上传 |
+| `tool:read` | 调用查询类院内工具 |
+| `tool:write` | 调用写类院内工具,高风险,仅受控适配方 |
+| `usage:read` | 查询用量和计量事件 |
+| `webhook:manage` | 管理 Webhook |
+
+`/tools/{toolName}/invoke` 必须有 `tool:read` 或 `tool:write`,并且只允许受控系统适配方调用。普通设备厂商不得直接调用。
+
+### 4.4 幂等
+
+所有产生业务事实的接口必须支持 `X-Emoon-Idempotency-Key`。
+
+| 接口类型 | 幂等键建议 |
+|---|---|
+| 对话请求 | `projectId + agentId + clientMessageId` |
+| 设备事件 | `projectId + deviceId + eventType + clientEventId` |
+| 卡片动作 | `projectId + cardInstanceId + actionName + clientActionId` |
+| Tool 写操作 | `projectId + toolName + businessKey` |
+| 文件上传 | `projectId + sha256 + clientFileId` |
+
+重复请求规则:
+
+1. 同幂等键、同请求摘要、首次成功:返回第一次结果。
+2. 同幂等键、不同请求摘要:返回 `IDEMPOTENCY_CONFLICT`。
+3. 首次处理中:返回处理中状态或让客户端稍后查询。
+4. HIS 写操作状态不明:先查询最终状态,禁止直接重复写。
+
+### 4.5 统一响应
+
+成功响应:
+
+```json
+{
+  "code": "OK",
+  "message": "success",
+  "traceId": "trace_001",
+  "requestId": "req_001",
+  "data": {}
+}
+```
+
+错误响应:
+
+```json
+{
+  "code": "INVALID_PARAM",
+  "message": "参数错误",
+  "details": {
+    "field": "deviceContext.deviceId",
+    "reason": "deviceId is required"
+  },
+  "traceId": "trace_001",
+  "requestId": "req_001"
+}
+```
+
+常见错误码:
+
+| 错误码 | 含义 |
+|---|---|
+| `AUTH_INVALID_TOKEN` | Token 无效或过期 |
+| `AUTH_SIGNATURE_INVALID` | HMAC 签名错误 |
+| `AUTH_TIMESTAMP_EXPIRED` | 时间戳过期 |
+| `PERMISSION_DENIED` | Scope 或项目授权不足 |
+| `RATE_LIMIT_EXCEEDED` | 触发限流 |
+| `IDEMPOTENCY_CONFLICT` | 同一幂等键请求内容不一致 |
+| `DEVICE_NOT_REGISTERED` | 设备未注册或未激活 |
+| `CARD_EXPIRED` | 卡片已过期 |
+| `TOOL_FORBIDDEN` | 工具未授权或调用方无权限 |
+| `HIS_TIMEOUT` | 院内系统超时 |
+| `BILLING_QUOTA_EXCEEDED` | 额度不足或超限 |
+
+---
+
+## 5. P0 接口详细设计
+
+### 5.1 智能体对话接口
+
+`POST /agent/chat`
+
+请求要点:
+
+| 字段 | 说明 |
+|---|---|
+| `agentId` | 对外智能体 ID |
+| `conversationId` | 可为空;为空时平台自动创建并返回 |
+| `user` | 用户上下文,预留 `patientId/visitId/encounterId/authorizationId` |
+| `deviceContext` | 设备上下文,设备调用必须传 |
+| `message` | 文本、图片、音频、文件引用;多模态内容使用 `fileId` |
+| `billingContext` | 场景和计量上下文 |
+
+### 5.2 文件上传接口
+
+`POST /files/upload`
+
+MVP 只处理:
+
+- 舌象/面象图片;
+- 报告图片或 PDF;
+- 诊间音频片段可后置。
+
+要求:
+
+1. 文件必须鉴权上传。
+2. 平台返回 `fileId`,后续 Agent 调用只传 `fileId`。
+3. 不默认返回可公开访问 URL。
+4. 原图、报告、面部图片按敏感数据处理。
+
+
+文件上传幂等规则:
+
+| 字段 / Header | 说明 |
+|---|---|
+| `X-Emoon-Idempotency-Key` | 正式联调建议必传。推荐值:`projectId + partnerId + clientFileId`。 |
+| `clientFileId` | 厂商侧文件唯一 ID;同一文件重试上传时保持不变。 |
+| `sha256` | 文件摘要,用于完整性校验和重复文件识别。 |
+| `source` | 文件来源:患者端、自助机、机器人、四诊仪、医生站、病区终端、第三方系统等。 |
+| `retentionPolicy` | 文件保存策略:`transient`、`episode`、`medical_record`、`audit_only`。 |
+
+文件上传降级幂等规则:如果 `clientFileId` 缺失,平台可降级使用 `projectId + partnerId + sha256 + purpose + user.visitId` 识别重复上传;但正式厂商接入仍应提供 `clientFileId`。
+
+`DELETE /files/{fileId}` 删除或作废文件时也应传 `X-Emoon-Idempotency-Key`。同一文件重复删除返回首次删除结果或当前已删除状态,不重复产生业务副作用。
+
+### 5.3 设备接入三步握手
+
+```text
+设备启动 -> /devices/register -> /devices/{deviceId}/scene -> /agent/chat 或 /cards/actions
+                    -> 定时 /devices/{deviceId}/heartbeat
+                    -> 有事件时 /devices/{deviceId}/events
+```
+
+
+
+设备管理边界:
+
+| 管理模式 | 说明 | 典型设备 |
+|---|---|---|
+| `emoon_managed` | 由医梦统一入口客户端和 Device Registry 管理,包含注册、心跳、场景、事件、版本和基础状态。 | 导诊屏、自助机、门诊机器人、诊室屏、叫号/候诊屏 |
+| `partner_managed` | 由合作设备厂商管理硬件与驱动,医梦通过标准 API/SDK 接入能力。 | 第三方机器人、四诊仪、专用采集设备 |
+| `mapped_external` | 由外部体系管理,医梦仅记录映射关系和 AI 事件互通。 | 迪耐克/鸿蒙病房床头屏、护理白板、护士 PDA、病区机器人 |
+
+门诊设备原则上进入医梦 Device Registry;住院侧设备类型仅表示 AI 能力映射对象,不代表医梦承担住院硬件注册、心跳、升级和故障主责。
+
+设备事件 MVP 类型:
+
+| eventType | 说明 |
+|---|---|
+| `qr_scanned` | 扫码完成 |
+| `card_read` | 身份证/医保卡读取 |
+| `print_completed` | 打印成功 |
+| `print_failed` | 打印失败 |
+| `nearby_detected` | 近场检测到患者或临时授权令牌 |
+| `navigation_started` | 机器人开始导航 |
+| `navigation_completed` | 机器人导航完成 |
+| `device_error` | 设备异常 |
+| `user_interaction` | 触控、点击、语音交互摘要 |
+
+`nearby_detected` 推荐 payload:
+
+```json
+{
+  "eventType": "nearby_detected",
+  "clientEventId": "evt_001",
+  "occurredAt": "2026-05-29T10:00:00+08:00",
+  "payload": {
+    "subject": {
+      "type": "temporary_token",
+      "value": "tmp_nearby_token_xxx"
+    },
+    "signal": {
+      "type": "ble",
+      "rssi": -65,
+      "confidence": 0.82
+    },
+    "areaCode": "OPD_2F_ROOM_201_DOOR"
+  }
+}
+```
+
+注意:不要让设备直接上报身份证号、手机号等敏感信息。服务找人只保存“到达诊室门口服务区域”的必要事件,不保存连续轨迹。
+
+### 5.4 卡片动作接口
+
+`POST /cards/{'{cardInstanceId}'}/actions/{'{actionName}'}`
+
+卡片动作必须传:
+
+| 字段 | 说明 |
+|---|---|
+| `clientActionId` | 客户端动作 ID,参与幂等 |
+| `operator` | 操作者,患者/医生/护士/系统 |
+| `deviceContext` | 动作发生在哪个设备、什么位置 |
+| `payload` | 动作业务参数 |
+| `confirm` | 是否已由用户或医护确认 |
+
+卡片动作原则:
+
+1. 前端不直接调用 HIS。
+2. 卡片动作先校验卡片状态、权限、幂等和有效期。
+3. 需要写 HIS 时由 Card Runtime 进入 MCP Tool Server。
+4. 医疗高风险动作必须人工确认。
+
+### 5.5 受控工具接口
+
+`POST /tools/{'{toolName}'}/invoke`
+
+工具接口只对以下对象开放:
+
+- 医梦 MCP Tool Server;
+- HIS/EMR/LIS/PACS/叫号/支付等系统适配方;
+- 医梦内部可信服务。
+
+普通设备厂商、机器人厂商、自助机厂商默认不开放。
+
+工具命名建议:
+
+| 工具域 | 示例 |
+|---|---|
+| HIS 基础 | `his.queryDepartments`、`his.queryDoctors` |
+| 排班号源 | `his.querySchedules`、`his.lockSchedule`、`his.releaseSchedule` |
+| 挂号 | `his.createRegistration`、`his.cancelRegistration` |
+| 叫号签到 | `queue.checkIn`、`queue.queryStatus` |
+| EMR | `emr.createDraft`、`emr.queryRecord` |
+| LIS/PACS | `lis.queryReports`、`pacs.queryImages` |
+| 支付 | `payment.createOrder`、`payment.queryStatus` |
+
+
+工具风险等级:
+
+| 风险等级 | 工具类型 | 控制要求 |
+|---|---|---|
+| `L1_QUERY` | 查询科室、医生、院区、FAQ | 可自动调用,记录审计。 |
+| `L2_SENSITIVE_READ` | 查询患者、报告、费用、处方摘要 | 需要患者授权、医护身份或项目授权。 |
+| `L3_BUSINESS_WRITE` | 锁号、签到、建档、挂号 | 必须幂等、用户确认、卡片动作审计。 |
+| `L4_FINANCIAL_ACTION` | 支付、退费、补缴 | 强确认、支付系统回调、财务审计。 |
+| `L5_MEDICAL_WRITE` | EMR 正式写回、医嘱、处方 | 医生确认、医院系统签名、全链路审计。 |
+
+MVP 阶段建议优先开放 `L1_QUERY` 和少量 `L3_BUSINESS_WRITE`。`L4_FINANCIAL_ACTION` 与 `L5_MEDICAL_WRITE` 不建议春节前作为通用厂商接口开放。
+
+---
+
+## 6. SSE 流式接口
+
+`POST /agent/chat/stream`
+
+Header:
+
+```http
+Accept: text/event-stream
+Content-Type: application/json
+```
+
+事件类型:
+
+| 事件 | 优先级 | 说明 |
+|---|---|---|
+| `message_delta` | P0 | 文本增量 |
+| `message_completed` | P0 | 文本完成,返回 messageId/conversationId |
+| `card_created` | P0 | 平台已创建卡片实例,前端可渲染 |
+| `error` | P0 | 流式错误 |
+| `usage_reported` | P1 | 本次调用用量或能力值事件,仅展示用途,不作为账本唯一来源 |
+
+断线恢复:
+
+1. 客户端记录 `conversationId` 和最后收到的 `eventId`。
+2. 断线后调用 `GET /conversations/{'{conversationId}'}/messages` 查询最终消息和卡片。
+3. 卡片实例以 `GET /cards/{'{cardInstanceId}'}` 为准。
+
+---
+
+## 7. Webhook 与外部事件
+
+### 7.1 Webhook 签名
+
+第三方回调医梦或医梦回调第三方,都应使用签名头:
+
+| Header | 说明 |
+|---|---|
+| `X-Emoon-Webhook-Event-Id` | 事件唯一 ID |
+| `X-Emoon-Webhook-Timestamp` | Unix 毫秒时间戳 |
+| `X-Emoon-Webhook-Nonce` | 随机串 |
+| `X-Emoon-Webhook-Signature` | HMAC-SHA256 签名 |
+
+重试策略建议:
+
+| 项 | 建议 |
+|---|---|
+| 单次超时 | 5 秒 |
+| 重试间隔 | 1min / 5min / 30min / 2h |
+| 最大重试 | 5 次 |
+| 成功条件 | HTTP 2xx |
+| 幂等键 | `eventId` |
+
+### 7.2 外部事件接入
+
+`POST /events/ingest` 用于 HIS、叫号、迪耐克/病房系统等向医梦上报业务事件,例如:
+
+| eventType | 来源 |
+|---|---|
+| `registration_created` | HIS |
+| `registration_cancelled` | HIS |
+| `queue_status_changed` | 叫号系统 |
+| `checkin_completed` | 签到系统 |
+| `report_available` | LIS/PACS |
+| `ward_alert_created` | 迪耐克/病房系统 |
+| `nursing_task_closed` | 护理系统 |
+
+---
+
+## 8. 限流、计量和计费
+
+### 8.1 限流不是计费
+
+限流用于保护系统,计费用于商业结算。限流命中不代表扣费,调用成功也不一定直接扣费,必须根据 `chargePolicy` 和计量账本判断。
+
+限流响应头建议:
+
+| Header | 说明 |
+|---|---|
+| `X-RateLimit-Limit` | 当前窗口额度 |
+| `X-RateLimit-Remaining` | 剩余额度 |
+| `X-RateLimit-Reset` | 重置时间 |
+| `Retry-After` | 建议重试秒数 |
+
+### 8.2 计量事件
+
+标准计量事件包含:
+
+- `projectId`
+- `partnerId`
+- `agentId`
+- `deviceId`
+- `scenarioCode`
+- `billingEpisodeId`
+- `sourceType`
+- `sourceId`
+- `meterItem`
+- `rawQuantity`
+- `credits`
+- `chargePolicy`
+
+`chargePolicy` 枚举:
+
+| 值 | 说明 |
+|---|---|
+| `customer_charge` | 客户能力值扣费 |
+| `license_covered` | 设备授权覆盖 |
+| `bundle_included` | 服务包包含 |
+| `internal_cost` | 只记内部成本 |
+| `no_charge` | 不计费,仅审计 |
+
+
+`/meter/events` 查询范围:
+
+| 阶段 | 支持范围 | 不支持范围 |
+|---|---|---|
+| P0 | 按 `billingEpisodeId` 或 `traceId` 做精确查询,用于联调排障和计量核验 | 不开放项目级、设备级、时间范围、成本明细和完整账单查询给普通厂商 |
+| P1 | 医院信息科和医梦运营可按项目、智能体、设备做基础汇总 | 不直接替代正式月账单 |
+| P2 | 进入账单、分摊、运营报告和区域能力池 | 需合同、权限、审计体系同步成熟 |
+
+正式账单查询不使用 `/meter/events`,后续由 `/billing/invoices` 或账单中心接口承接。
+
+---
+
+## 9. MVP 实施建议
+
+### 9.1 P0 开发顺序
+
+1. 通用鉴权、HMAC Header、错误响应、幂等键。
+2. `/agent/chat` + `/conversations/{'{id}'}` + `/conversations/{'{id}'}/messages`。
+3. `/files/upload` + `/files/{'{fileId}'}` + `DELETE /files/{'{fileId}'}` 幂等删除。
+4. `/devices/register` + `/heartbeat` + `/scene`。
+5. `/cards/{'{id}'}` + `/cards/{'{id}'}/actions/{'{actionName}'}`。
+6. `/tools/{'{toolName}'}/invoke` 只接查询类 HIS 工具。
+7. `/meter/events` 只按 `billingEpisodeId` / `traceId` 精确查询 + 计量事件落库。
+8. `/agent/chat/stream` 做 P0 四类 SSE 事件。
+
+### 9.2 不建议第一期实现的内容
+
+- 完整 OAuth2/IAM;
+- mTLS;
+- 完整账单中心;
+- 区域多院能力池;
+- FHIR 网关 / FHIR Translate 主接口;
+- 第三方卡片插件市场;
+- 全院鸿蒙无感定位;
+- 所有设备深度控制命令。
+
+---
+
+## 10. v1.1 相比 v1.0 的变更
+
+| 变更 | 说明 |
+|---|---|
+| 新增会话接口 | `/conversations`、`/conversations/{'{id}'}`、`/messages` |
+| 新增文件接口 | `/files/upload`、`/files/{'{fileId}'}` |
+| 卡片动作增加 `deviceContext` | 解决设备审计和设备计量归因 |
+| 补 HMAC Headers | Timestamp、Nonce、Signature |
+| 补核心上下文 schema | UserContext、DeviceContext、BillingContext |
+| 补错误 details | 方便厂商联调排障 |
+| 补设备事件和命令 schema | 支撑机器人、自助机、服务找人 |
+| 补 Tool scope 说明 | 避免普通设备厂商误调 HIS 工具 |
+| 补 Webhook 签名和重试 | 支撑外部事件接入 |
+| 补 P0/P1/P2 全量接口目录 | 兼顾接口全貌和 MVP 落地 |
+
+---
+
+## 11. 对外发送建议
+
+正式发给厂商时建议提供一个资料包:
+
+```text
+医梦AI中台厂商对接资料包/
+├── 医梦AI中台对外接口设计文档_v1.1_全量规划版.md
+├── emoon-ai-openplatform-api-v1.1.openapi.yaml
+├── Postman Collection
+├── HMAC签名示例-Java
+├── HMAC签名示例-TypeScript
+├── HIS工具接口Mock说明
+├── 机器人设备接入示例
+└── 设备事件与命令测试用例
+```
+
+当前这版已经可以作为内部评审和厂商预沟通底稿。若要发正式联调版,应再补 Postman Collection、HMAC 示例和 HIS Mock Server。
+
+
+## 12. v1.2 正式联调基准结论
+
+v1.2 可以作为厂商正式联调基准,但仍需坚持以下边界:
+
+1. **主 YAML 只代表当前联调基准**:P0/P1 是主要联调范围;P2 只做规划说明,不代表春节前承诺。
+2. **FHIR 不在主基线内**:如医院已有 FHIR 网关,需要单独评估后另出 FHIR 映射文件。
+3. **住院设备不按门诊总包处理**:护理白板、床头屏、护士 PDA 等住院终端默认通过迪耐克/鸿蒙病房体系映射接入。
+4. **文件和工具接口是高风险区域**:文件必须有用途、来源、保存策略和幂等;工具必须有 riskLevel、scope、幂等和审计。
+5. **计量查询不是账单接口**:`/meter/events` 只用于联调排障和计量核验,正式对账以后续账单中心为准。
+
+对外发送建议:
+
+| 对象 | 发送内容 | 备注 |
+|---|---|---|
+| HIS / EMR / LIS / PACS / 叫号 / 支付厂商 | Markdown + v1.2 YAML + Tool 接口章节 | 强调 tools 受控开放和风险等级 |
+| 机器人 / 自助机 / 大屏 / 诊室屏厂商 | Markdown + v1.2 YAML + Device / Agent / Card 章节 | 强调不得直接调用 Tool |
+| 四诊仪厂商 | Markdown + v1.2 YAML + File / Device / Agent 章节 | 强调文件上传幂等和敏感数据策略 |
+| 迪耐克 / 鸿蒙病房互通方 | Markdown + v1.2 YAML + Device 管理边界 / Event 章节 | 强调 mapped_external,不承担住院硬件主责 |
+| 医院信息科 | Markdown + v1.2 YAML + 鉴权 / 审计 / 计量章节 | 强调权限隔离、日志、计量不是账单 |

File diff suppressed because it is too large
+ 448 - 96
docs/架构文档/AI中台二期+三期需求技术方案.md


+ 372 - 5
docs/AI中台工程约束.md → docs/架构文档/AI中台工程约束.md

@@ -2,12 +2,13 @@
 
 | 项目 | 内容 |
 | --- | --- |
-| 文档定位 | 工程结构、模块边界、依赖规则约束 |
-| 当前章节 | Maven 工程依赖规范约束 |
-| 适用范围 | 现有后端工程、二期 AI Agent / Card / MCP、三期计量 / 账务 / 运营 |
+| 文档定位 | 工程结构、模块边界、依赖规则、接口归属、调用方向约束 |
+| 当前章节 | Maven 工程依赖 + Controller/Service 调用方向 + 接口→模块映射 |
+| 适用范围 | 现有后端工程、二期 AI Agent / Card / MCP / Device / File、三期计量 / 账务 / 运营 |
 | 约束目标 | 高内聚、低耦合、可演进、可测试、可独立部署 |
-| 版本 | v1.0 |
-| 更新时间 | 2026-05-25 |
+| 版本 | v2.0 |
+| 更新时间 | 2026-05-29 |
+| 变更说明 | 新增第 2 章:基于 v1.2 接口文档的接口归属、Controller/Service 层规则、调用方向禁止清单、Device/File 新模块职责、PR 评审检查清单 |
 
 ## 1. Maven 工程依赖规范约束
 
@@ -76,6 +77,8 @@ emoon-backend
 │   │       ├── emoon-ai-agent-api
 │   │       ├── emoon-ai-card-api
 │   │       ├── emoon-ai-mcp-api
+│   │       ├── emoon-ai-device-api      # 🆕 设备注册中心 API
+│   │       ├── emoon-ai-file-api        # 🆕 文件服务 API
 │   │       ├── emoon-ai-meter-api
 │   │       ├── emoon-ai-billing-api
 │   │       ├── emoon-ai-contract-api
@@ -88,6 +91,8 @@ emoon-backend
 │           ├── emoon-ai-agent
 │           ├── emoon-ai-card
 │           ├── emoon-ai-mcp
+│           ├── emoon-ai-device          # 🆕 设备注册中心实现
+│           ├── emoon-ai-file            # 🆕 文件服务实现
 │           ├── emoon-ai-meter
 │           ├── emoon-ai-billing
 │           ├── emoon-ai-contract
@@ -380,6 +385,89 @@ Outbox 或定时任务汇总到 operation 宽表
 emoon-ai-operation 面向后台和客户报表查询
 ```
 
+#### 1.4.10 Device 设备注册模块
+
+| 模块 | 规划职责 |
+| --- | --- |
+| `emoon-ai-device-api` | 定义设备注册、心跳、场景配置、事件上报、命令下发、设备查询契约 |
+| `emoon-ai-device` | 实现 Device Registry、设备身份管理、准入等级校验(A/B/C/D)、心跳监控、Scene Orchestrator、命令轮询与回执、设备事件接入 |
+
+关键约束:
+
+```text
+门诊设备(导诊屏、自助机、机器人、诊室屏等)由医梦 Device Registry 管理,包含注册、心跳、场景和基础状态。
+住院侧设备类型(nurse_pda / nursing_whiteboard / bedside_screen / ward_robot)
+  默认 managementMode = mapped_external,由迪耐克/鸿蒙病房体系管理硬件生命周期,
+  医梦只记录映射关系和 AI 事件互通,不承担住院硬件注册、心跳升级和故障主责。
+设备事件只上报业务摘要,不保存患者连续轨迹。
+```
+
+设备管理模式枚举:
+
+| managementMode | 说明 | 典型设备 |
+|---|---|---|
+| `emoon_managed` | 医梦统一入口客户端和 Device Registry 管理 | 导诊屏、自助机、门诊机器人、诊室屏 |
+| `partner_managed` | 合作厂商管理硬件,医梦通过标准接口接入 | 第三方机器人、四诊仪 |
+| `mapped_external` | 外部体系管理,医梦只做映射和事件互通 | 迪耐克床头屏、护理白板、护士 PDA |
+
+允许依赖:
+
+```text
+emoon-ai-device-api
+emoon-system-api(查询医院/科室绑定)
+必要的 emoon-common-*
+```
+
+禁止依赖:
+
+```text
+emoon-ai-agent(设备不编排 AI 流程)
+emoon-ai-card(设备不管理卡片状态)
+emoon-ai-billing(设备不计费)
+emoon-openplatform(设备不反向依赖入口层)
+```
+
+#### 1.4.11 File 文件服务模块
+
+| 模块 | 规划职责 |
+| --- | --- |
+| `emoon-ai-file-api` | 定义文件上传、元数据查询、删除、安全扫描状态契约 |
+| `emoon-ai-file` | 实现文件接收、对象存储适配、安全扫描对接、retentionPolicy 管理、幂等去重 |
+
+关键约束:
+
+```text
+文件必须先上传获得 fileId,后续 Agent/Card 调用只传 fileId。
+不默认返回可公开访问 URL。
+舌诊/面诊图片、报告原文按敏感医疗数据治理。
+retentionPolicy 不传时,按 purpose 推断默认值:
+  tongue_diagnosis / face_diagnosis → episode
+  report_interpretation → episode
+  medical_record → medical_record
+  audio_transcription → transient
+  other → transient
+DELETE 必须支持幂等,同一文件重复删除返回首次结果或当前已删除状态。
+文件上传幂等键建议:projectId + partnerId + clientFileId。
+```
+
+允许依赖:
+
+```text
+emoon-ai-file-api
+emoon-system-api
+emoon-common-oss
+必要的 emoon-common-*
+```
+
+禁止依赖:
+
+```text
+emoon-ai-agent
+emoon-ai-card
+emoon-ai-mcp
+emoon-openplatform
+```
+
 ### 1.5 依赖白名单
 
 #### 1.5.1 API 模块依赖白名单
@@ -825,3 +913,282 @@ R14. 任何违反规则的例外必须补 ADR。
 | 第二阶段 | `com.emoon.mcp.*` 迁移后的 Agent / Card / MCP 模块 |
 | 第三阶段 | `com.emoon.openplatform.*` 禁止直连 Mapper 和实现层 |
 | 第四阶段 | 全工程禁止 API 模块依赖实现层和重依赖 |
+
+---
+
+## 2. 接口归属与调用方向约束
+
+> 本章基于 v1.2 对外接口文档(`docs/接口文档/emoon-ai-openplatform-api-v1.2.openapi.yaml`),
+> 明确每个接口的 Controller 归属模块和核心 Service 归属模块,
+> 并定义 Controller → Service 的合法调用方向和禁止调用模式。
+
+### 2.1 Controller 层强制规则
+
+所有对外 API 的 Controller 统一放在 `emoon-openplatform` 模块的 `controller/v1/` 包下。
+
+Controller 只做四件事:
+
+| 允许 | 不允许 |
+|------|--------|
+| 解析 HTTP 请求参数和 Header | 直接调用 Mapper |
+| 调用鉴权拦截器/工具类 | 直接调用 Dify API 或第三方 LLM SDK |
+| 调用领域 Service(通过 API 接口) | 编写业务逻辑(状态机、计费、HIS 调用) |
+| 封装统一响应(`R<T>` 或 SSE) | 直接操作数据库表 |
+
+为什么 Controller 不能直接调 Mapper:
+
+```text
+Controller 只负责 HTTP 协议适配。如果直接调 Mapper,会绕过 Application Service、
+状态机、幂等校验、审计、计量和 Outbox。后续排障时无法通过 traceId 还原完整调用链。
+```
+
+### 2.2 接口 → 模块归属表
+
+下表来自 v1.2 正式联调基准版,每个接口对应**一个 Controller 模块**和**一个核心 Service 实现模块**。
+
+#### P0 接口(联调前必须稳定,21 个)
+
+| 方法 | 路径 | Controller 所在模块 | 核心 Service 实现模块 | 当前工程状态 |
+|------|------|--------------------|---------------------|-------------|
+| POST | `/auth/token` | emoon-openplatform | emoon-openplatform | 🔴 需新建(Bearer Token 签发) |
+| GET | `/health` | emoon-openplatform | emoon-openplatform | 🟡 部分存在 |
+| GET | `/capabilities` | emoon-openplatform | emoon-openplatform + emoon-system | 🔴 需新建 |
+| POST | `/agent/chat` | emoon-openplatform | emoon-ai-agent | 🟡 需升级鉴权 |
+| POST | `/agent/chat/stream` | emoon-openplatform | emoon-ai-agent | 🟡 同上 |
+| POST | `/conversations` | emoon-openplatform | emoon-openplatform | 🟡 路径需 RESTful |
+| GET | `/conversations` | emoon-openplatform | emoon-openplatform | 🟡 P1,来自旧 conversation/list |
+| GET | `/conversations/{id}` | emoon-openplatform | emoon-openplatform | 🟡 路径需对齐 |
+| GET | `/conversations/{id}/messages` | emoon-openplatform | emoon-openplatform | 🔴 需新建 |
+| POST | `/files/upload` | emoon-openplatform | emoon-ai-file | 🔴 需新建模块 |
+| GET | `/files/{id}` | emoon-openplatform | emoon-ai-file | 🔴 同上 |
+| DELETE | `/files/{id}` | emoon-openplatform | emoon-ai-file | 🔴 同上(P1) |
+| POST | `/devices/register` | emoon-openplatform | emoon-ai-device | 🔴 需新建模块 |
+| POST | `/devices/{id}/heartbeat` | emoon-openplatform | emoon-ai-device | 🔴 同上 |
+| GET | `/devices/{id}/scene` | emoon-openplatform | emoon-ai-device | 🔴 同上 |
+| POST | `/devices/{id}/events` | emoon-openplatform | emoon-ai-device | 🔴 同上 |
+| GET | `/devices/{id}/commands/poll` | emoon-openplatform | emoon-ai-device | 🔴 同上 |
+| POST | `/devices/{id}/commands/{cmd}/ack` | emoon-openplatform | emoon-ai-device | 🔴 同上 |
+| GET | `/cards/{id}` | emoon-openplatform | emoon-ai-card | 🔴 需新建 |
+| POST | `/cards/{id}/actions/{name}` | emoon-openplatform | emoon-ai-card | 🟡 路径需对齐 |
+| POST | `/tools/{name}/invoke` | emoon-openplatform | emoon-ai-mcp | 🟡 雏形存在 |
+| GET | `/usage/summary` | emoon-openplatform | emoon-ai-meter | 🔴 meter 模块空壳 |
+| GET | `/meter/events` | emoon-openplatform | emoon-ai-meter | 🔴 同上 |
+
+#### P1 接口(试点上线增强,14 个)
+
+| 方法 | 路径 | Controller 所在模块 | 核心 Service 实现模块 |
+|------|------|--------------------|---------------------|
+| GET | `/agents` | emoon-openplatform | emoon-system(查 ai_agent_app) |
+| GET | `/agents/{agentId}` | emoon-openplatform | emoon-system |
+| GET | `/devices` | emoon-openplatform | emoon-ai-device |
+| GET | `/devices/{id}` | emoon-openplatform | emoon-ai-device |
+| PATCH | `/devices/{id}/scene` | emoon-openplatform | emoon-ai-device |
+| GET | `/devices/{id}/events` | emoon-openplatform | emoon-ai-device |
+| POST | `/webhooks` | emoon-openplatform | emoon-openplatform |
+| GET | `/webhooks` | emoon-openplatform | emoon-openplatform |
+| DELETE | `/webhooks/{id}` | emoon-openplatform | emoon-openplatform |
+| POST | `/events/ingest` | emoon-openplatform | emoon-openplatform + emoon-ai-device |
+| GET | `/tools/catalog` | emoon-openplatform | emoon-ai-mcp |
+| GET | `/licenses/devices` | emoon-openplatform | emoon-ai-contract + emoon-ai-device |
+
+#### P2 接口(远期规划,9 个)
+
+| 方法 | 路径 | Controller 所在模块 | 核心 Service 实现模块 |
+|------|------|--------------------|---------------------|
+| POST | `/partners` | emoon-openplatform | emoon-openplatform |
+| GET | `/partners/{id}` | emoon-openplatform | emoon-openplatform |
+| GET | `/contracts` | emoon-openplatform | emoon-ai-contract |
+| GET | `/credit/accounts` | emoon-openplatform | emoon-ai-billing |
+| POST | `/credit/accounts/{id}/freeze` | emoon-ai-billing(内部) | emoon-ai-billing |
+| POST | `/credit/accounts/{id}/settle` | emoon-ai-billing(内部) | emoon-ai-billing |
+| GET | `/billing/invoices` | emoon-openplatform | emoon-ai-billing |
+| GET | `/operation/reports` | emoon-openplatform | emoon-ai-operation |
+| GET | `/audit/logs` | emoon-openplatform | emoon-system(扩展审计表) |
+
+### 2.3 合法调用方向
+
+```mermaid
+flowchart LR
+    subgraph 入口层
+        OC["emoon-openplatform<br/>Controller v1"]
+    end
+    subgraph 领域实现
+        AG["emoon-ai-agent"]
+        CD["emoon-ai-card"]
+        MC["emoon-ai-mcp"]
+        DV["emoon-ai-device"]
+        FL["emoon-ai-file"]
+        MT["emoon-ai-meter"]
+        BL["emoon-ai-billing"]
+        CT["emoon-ai-contract"]
+        OP["emoon-ai-operation"]
+    end
+    subgraph 基础域
+        SY["emoon-system"]
+        KN["emoon-knowledge"]
+    end
+    subgraph 外部
+        DF["Dify / LLM"]
+        HS["HIS / EMR / LIS / PACS"]
+    end
+
+    OC --> AG & CD & MC & DV & FL & MT & SY
+    AG --> CD & MC & MT & CT & KN & SY & DF
+    CD --> MC
+    MC --> HS
+    DV --> SY
+    FL --> SY
+    MT --> CT
+    BL --> MT & CT
+    CT --> SY
+    OP --> MT & BL & CT
+```
+
+图中箭头 = 允许调用方向。没有画出的方向默认不允许。关键约束:
+
+| 调用方向 | 是否允许 | 原因 |
+|---------|---------|------|
+| Controller → Service(通过 API 接口) | ✅ 允许 | 标准调用路径 |
+| emoon-ai-card → emoon-ai-mcp | ✅ 允许 | 卡片动作触发的 HIS 写操作必须通过 MCP |
+| emoon-ai-agent → emoon-ai-mcp | ✅ 允许 | Dify Workflow 工具调用必须通过 MCP |
+| emoon-ai-mcp → emoon-ai-agent | ❌ 禁止 | 工具不编排 Agent 流程 |
+| emoon-ai-card → emoon-ai-agent | ❌ 禁止 | 卡片不发起 Agent 调用 |
+| emoon-ai-device → emoon-ai-agent | ❌ 禁止 | 设备不编排 AI 流程 |
+| emoon-ai-billing → emoon-ai-agent | ❌ 禁止 | 账务不反向依赖调用链 |
+| Controller → Mapper | ❌ 禁止 | 绕过 Service 层和状态机 |
+| 任意模块 → Dify HTTP 直连 | ❌ 禁止 | 必须经过 AgentEngine |
+| 前端/设备 → `/tools/{name}/invoke` | ❌ 禁止 | 必须经过卡片动作或 Agent |
+
+### 2.4 禁止的调用模式(红线清单)
+
+以下 6 条违反即拒绝 PR:
+
+**F1. Controller 直接调 Mapper**
+
+```java
+// ❌ 禁止
+@RestController
+public class FooController {
+    @Autowired
+    private AiConversationMapper mapper;  // Controller 不应注入 Mapper
+}
+```
+
+正确做法:Controller → Service 接口(API 模块定义)→ Service 实现(实现模块内调 Mapper)。
+
+**F2. 任意模块直接 import DifyApiClient**
+
+```java
+// ❌ 禁止:在 emoon-ai-card、emoon-ai-device 等非 Agent 模块中
+import com.emoon.openplatform.dify.DifyApiClient;
+```
+
+Dify 调用只能出现在 `emoon-ai-agent` 的 `adapter` 包中,通过 `AgentEngine` 接口对外暴露。
+
+**F3. 卡片绕过 Agent 直接调 MCP 工具**
+
+```java
+// ❌ 禁止:在 emoon-ai-card 中
+import com.emoon.ai.mcp.application.HisLockScheduleService;
+```
+
+卡片需要写 HIS 时,必须通过 Agent 编排链路(Card → Agent → MCP → HIS),
+不能出现 Card → MCP 的短路调用。
+
+**F4. 前端/设备端直接调 `/tools/{toolName}/invoke`**
+
+```text
+❌ 禁止:机器人、自助机、导诊屏厂商的 HTTP Client 直接 POST /api/v1/tools/his.createRegistration/invoke
+```
+
+普通设备厂商只能通过 `/agent/chat`、`/cards/{id}/actions/{name}` 和 `/devices/*` 接入。
+Tool Invoke 仅开放给医梦 MCP Tool Server、HIS/EMR/LIS/PACS 等受控系统适配方。
+
+**F5. Service 层写 HMAC 签名逻辑**
+
+```java
+// ❌ 禁止:在 Service 实现类中
+String sign = SignUtil.hmacSha256(payload, project.getSecretKey());
+```
+
+鉴权逻辑只能出现在 `emoon-openplatform` 的拦截器或 Controller 层。
+Service 层接收的是已验证的 `projectId/tenantId` 上下文。
+
+**F6. 把 Dify API Key 暴露到日志或前端**
+
+```text
+❌ 禁止:log.info("Dify key: {}", difyApiKey)
+❌ 禁止:响应体中包含 apiKey 字段
+❌ 禁止:前端 JS 中硬编码 Dify Key
+```
+
+Dify API Key 只能存在于 `ai_agent_engine_config.config_json` 的加密字段中,
+运行时由 Agent 模块解密后使用,不出现在任何日志、响应或前端代码中。
+
+### 2.5 PR 评审检查清单
+
+每个涉及后端 API 或模块的 PR 必须通过以下检查。此清单同时维护在 `.github/pull_request_template.md` 中。
+
+#### Controller 检查
+
+- [ ] 新 Controller 放在 `emoon-openplatform/controller/v1/` 包下?
+- [ ] 路径和方法名与 v1.2 YAML 一致?
+- [ ] 已接入 HMAC 或 Bearer Token 鉴权(非 legacy MD5)?
+- [ ] 写操作已强制 `X-Emoon-Idempotency-Key`?
+- [ ] Controller 未直接注入 Mapper?
+- [ ] Controller 未直接 import DifyApiClient?
+
+#### Service 检查
+
+- [ ] Service 接口在 `*-api` 模块、实现在 `*-modules` 模块?
+- [ ] 新 Service 放在了正确的模块?(参考 §2.2 归属表)
+- [ ] 没有违反 §2.4 中的任何一条禁止调用模式?
+- [ ] 涉及 HIS 写操作的,已通过 `emoon-ai-mcp` 工具封装?
+
+#### 模块边界检查
+
+- [ ] 没有在 `emoon-openplatform` 里写业务逻辑?
+- [ ] 新模块经过技术负责人确认?(禁止私自创建顶级模块)
+- [ ] 没有把 DifyApiClient 暴露给不应见的模块?
+
+#### 数据与合规检查
+
+- [ ] 新表有 `project_id` 字段?
+- [ ] 新表有必要的索引(至少覆盖 project、time、status 三个维度)?
+- [ ] 日志中不包含身份证、手机号、完整病历、API Key?
+- [ ] 敏感字段有加密或脱敏?
+
+#### 接口文档对齐
+
+- [ ] 新接口已在 v1.2 接口设计文档中定义?
+- [ ] 请求/响应字段与 YAML schema 一致?
+- [ ] 返回的 HTTP 状态码与接口文档一致?
+
+### 2.6 工程师落地新接口的速查流程
+
+接手一个新接口时,按以下顺序确认归属:
+
+```text
+1. 查 §2.2 接口归属表 → 确定 Controller 放 emoon-openplatform,Service 放哪个模块。
+2. 如果接口不在表中 → 先在 PR 描述中讨论归属,不得私自决定。
+3. 创建 Controller → 放在 emoon-openplatform/controller/v1 包下。
+4. Controller 中只写参数解析 + 鉴权调用 + Service 调用 + 响应封装。
+5. Service 接口放在对应 `*-api` 模块,实现放在对应 `*-modules` 模块。
+6. 完成后对照 §2.5 检查清单自检。
+7. 运行架构边界测试(`mvn -pl emoon-admin -Dtest=AiPlatformArchitectureTest test`)。
+```
+
+### 2.7 本章强制规则摘要
+
+```text
+C1. Controller 统一放在 emoon-openplatform/controller/v1/。
+C2. Controller 只做参数解析、鉴权、Service 调用、响应封装。
+C3. Controller 禁止注入 Mapper、禁止直接调 Dify、禁止写业务逻辑。
+C4. 每个接口的 Service 归属以 §2.2 表为准。
+C5. Card → MCP 必须经过 Agent 编排,禁止短路调用。
+C6. 前端/设备端禁止直接调 /tools/{name}/invoke。
+C7. DifyApiClient 只能被 emoon-ai-agent 的 adapter 包 import。
+C8. 鉴权逻辑只能出现在 emoon-openplatform 的拦截器或 Controller。
+C9. Dify API Key 不出现在日志、响应、前端代码中。
+C10. 新模块必须经过技术负责人确认,禁止私建顶级模块。
+```

+ 0 - 0
docs/工程规约生效说明.md → docs/架构文档/工程规约生效说明.md


+ 1292 - 0
docs/需求文档/医梦AI未来医院整体解决方案_深度汇总分析版.md

@@ -0,0 +1,1292 @@
+# 医梦 AI 未来医院整体解决方案|深度汇总分析版
+
+> 版本:v2.0 深度汇总分析版  
+> 日期:2026-05-29  
+> 适用范围:内部战略统一、管理层汇报、医院甲方沟通、后续会议讲解、研发拆解、设备生态谈判、商务报价前置材料  
+> 基础资料:前序核心分析答案 + 5 份 Markdown 文档 + AI 门诊/AI 住院原方案 + 商业模式与能力值定价方案 + 最新会议纪要与录音逐字稿  
+> 核心定位:**门诊整体总包 + 住院 AI 大脑层协同 + 统一入口控流量 + 设备适配平台 + 鸿蒙服务找人试点**
+
+---
+
+## 0. 执行摘要
+
+### 0.1 本轮方案变化的本质
+
+本轮会议确认的变化不是“AI 门诊多加几个设备”,也不是“机器人方案扩大化”,而是医梦在医院项目中的控制位发生变化。
+
+此前医梦的定位更接近:
+
+> **AI 医院软件方案商 / AI 智能体能力提供商 / 部分机器人与设备接入方。**
+
+现在应升级为:
+
+> **门诊软硬件整体入口总包方 + 住院 AI 大脑层协同方 + 医院多终端统一入口控制方 + 设备生态准入与适配方 + AI 场景持续运营方。**
+
+这意味着医梦不再只是把 AI 接进医院,而是要把医院门诊和部分住院场景中的“人机交互入口、AI 能力入口、设备入口、业务闭环入口、数据回流入口”统一收敛到自己的平台体系中。
+
+---
+
+### 0.2 最新总口径
+
+建议后续所有方案、PPT、商务话术和技术说明统一使用以下总口径:
+
+> 医梦 AI 未来医院方案由 **场景层、AI 层、鸿蒙协同层** 三层构成。  
+> **场景层** 面向门诊与住院两大核心业务,其中门诊由医梦主导软硬件整体解决方案建设,住院侧与既有迪耐克/鸿蒙病房体系协同;  
+> **AI 层** 由医梦 AI 中台、智能体中心、统一入口客户端、设备适配平台、MCP 工具网关、Card Runtime、知识库与规则引擎共同构成,负责把各类终端、设备、业务系统和 AI 能力统一调度;  
+> **鸿蒙协同层** 以国产化设备生态、近场感知、设备互联和“服务找人”为亮点,打造可演示、可验收、可复制的智慧就医体验。  
+> 医梦的核心价值不是单点 AI 能力,而是 **平台 + 专精模型 + 场景交付 + 设备入口 + 持续运营**。
+
+---
+
+### 0.3 门诊和住院边界必须清晰
+
+| 维度 | AI 门诊 | AI 住院 |
+|---|---|---|
+| 项目定位 | **医梦主导整体总包** | **医梦负责 AI 大脑层与互通层** |
+| 硬件责任 | 医梦主导设备规划、准入、选型、统一入口适配、联调验收 | 住院硬件以迪耐克/鸿蒙病房既有体系为主,医梦不抢病房硬件总包 |
+| AI 责任 | 导诊、预问诊、分诊、挂号、签到叫号、诊中辅助、诊后随访 | 预住院评估、智慧病历、CDSS、护理任务、IoMT 预警解释、出院小结、随访 |
+| 设备入口 | 导诊屏、自助机、机器人、叫号屏、医生介绍屏、诊室屏、四诊仪等 | 护士 PDA、床头屏、护理白板、病区机器人、IoMT 设备等通过迪耐克体系互通 |
+| 控制目标 | 控制门诊流量入口和终端入口 | 控制住院 AI 能力、智能体逻辑和数据闭环 |
+| 优先级 | **最高优先级** | 同步推进,但避免硬件责任扩散 |
+
+必须避免一个错误:把门诊和住院都做成“全硬件总包”。当前战略更合理的边界是:
+
+- **门诊:攻入口,做总包,控制流量。**
+- **住院:控 AI,大脑协同,打通闭环。**
+
+---
+
+### 0.4 三个最关键战略产品
+
+| 战略产品 | 为什么重要 | 对应工作 |
+|---|---|---|
+| **统一入口客户端** | 所有屏幕、机器人、自助机、医生屏、护士屏都通过它加载医梦能力 | 多设备运行时、虚拟形象、语音交互、智能体绑定、UI 模板 |
+| **设备适配平台** | 设备厂家会很多,没有适配平台会被硬件拖死 | Device Registry、Device Adapter、设备 SDK、远程配置、状态监控 |
+| **AI 中台 / Agent 中心** | 真正承载医梦医疗 AI 价值 | OpenPlatform、Dify Workflow、MCP、Card Runtime、知识库、计量、审计 |
+
+---
+
+### 0.5 后续会议讲解建议
+
+后续会议不建议直接讲“AI 医院总体架构”,容易过大。建议按以下顺序讲:
+
+1. **为什么医梦定位变了:从 AI 软件商到门诊入口总包方。**
+2. **门诊为什么优先:流量入口、设备入口、患者入口都在门诊。**
+3. **统一入口客户端为什么是战略产品:不是 App,而是多终端运行时。**
+4. **设备适配为什么必须独立成线:否则中台会被硬件联调拖死。**
+5. **住院为什么不抢硬件总包:迪耐克已做鸿蒙病房,医梦要做 AI 大脑层。**
+6. **鸿蒙服务找人怎么做成可落地 POC:主动签到、候诊提醒、检查导航。**
+7. **商业上怎么收费:项目实施费 + 设备集成费 + 统一入口授权 + 能力值 + 综合运营服务费。**
+
+---
+
+## 1. 第一主题:医梦 AI 未来医院最新总体方案 v2
+
+### 1.1 总体定位
+
+医梦 AI 未来医院方案的最新定位应统一为:
+
+> **面向医院的 AI 未来医院软硬件一体化建设方案。门诊侧由医梦主导整体入口总包,住院侧与迪耐克/鸿蒙病房体系互通,医梦通过 AI 中台、智能体中心、统一入口客户端和设备适配平台控制医院 AI 服务入口、设备入口和持续运营入口。**
+
+这里需要注意三个关键词:
+
+| 关键词 | 正确理解 | 错误理解 |
+|---|---|---|
+| 门诊总包 | 门诊软硬件入口统一规划、建设、接入、适配、运营 | 医梦生产所有硬件 |
+| 住院 AI 大脑 | 医梦提供 AI 能力、智能体、闭环逻辑、数据互通 | 医梦接管迪耐克病房硬件 |
+| 统一入口 | 所有终端加载医梦统一客户端和智能体能力 | 只做一个患者 App |
+
+---
+
+### 1.2 新旧方案对比
+
+| 维度 | 原方案 | 最新方案 | 影响 |
+|---|---|---|---|
+| 公司定位 | AI 医院软件方案商 | 门诊总包 + 住院 AI 大脑 + 设备入口控制方 | 商业控制位显著提高 |
+| 门诊范围 | 导诊、预问诊、机器人、医生站 | 所有门诊带屏设备、机器人、自助机、叫号、诊室终端、四诊仪 | 需要设备规划和供应商管理 |
+| 住院范围 | AI 住院全流程 + 部分设备设想 | 与迪耐克/鸿蒙病房互通,主做 AI 层 | 边界收缩,风险降低 |
+| 中台职责 | 调用智能体、连接 HIS | 控制多终端入口、智能体路由、设备事件、计量运营 | 中台需要增加设备维度 |
+| 硬件职责 | 部分自研、部分接入 | 门诊设备准入、适配、联调、验收;住院协同互通 | 需要设备适配团队 |
+| 鸿蒙价值 | 未单独成层 | 国产化 + 近场感知 + 服务找人 | 适合做标杆亮点 |
+| 商业模式 | AI 能力 + 项目交付 | AI 能力 + 项目实施 + 设备集成 + 入口授权 + 运营服务 | 收费项需要扩展 |
+
+---
+
+### 1.3 三层对外架构
+
+#### 1.3.1 场景层
+
+场景层面向医院业务人员和管理者,必须简单:
+
+```mermaid
+flowchart LR
+    A[AI 未来医院] --> B[AI 门诊\n医梦整体总包]
+    A --> C[AI 住院\n医梦 AI 大脑层协同]
+    B --> B1[导诊/预问诊]
+    B --> B2[分诊挂号]
+    B --> B3[签到叫号]
+    B --> B4[诊间辅助]
+    B --> B5[诊后随访]
+    C --> C1[预住院评估]
+    C --> C2[智慧病历]
+    C --> C3[护理任务]
+    C --> C4[IoMT 预警解释]
+    C --> C5[出院随访]
+```
+
+对外表达:
+
+> 门诊由医梦作为整体方案主导方,从导诊大屏、自助机、机器人、叫号屏、诊室屏到 AI 中台统一建设;住院与既有鸿蒙病房体系互通,医梦提供住院 AI 大脑层能力。
+
+---
+
+#### 1.3.2 AI 层
+
+AI 层是医梦真正的控制层。
+
+| 模块 | 核心职责 | 为什么必须做 |
+|---|---|---|
+| OpenPlatform 统一入口 | 统一认证、租户、会话、权限、日志、业务网关 | 多终端接入没有统一入口会失控 |
+| Agent 智能体中心 | 管理导诊、病历、质控、护理、随访等智能体 | 让不同设备加载不同智能体 |
+| Dify Workflow 编排 | 编排 AI 流程、工具调用、知识库问答、多步骤任务 | 快速配置业务流程 |
+| MCP Tool Server | 封装 HIS、EMR、LIS、PACS、叫号、支付、床位等工具 | 防止 AI 直接操作业务系统 |
+| Card Runtime | 承载挂号、签到、缴费、报告、任务等确定性动作 | 所有关键动作必须可控、可审计、可回滚 |
+| 统一入口客户端 | 所有屏幕与机器人运行医梦客户端 | 控制流量入口 |
+| Device Registry | 管理设备身份、位置、型号、能力、状态 | 支撑大规模设备治理 |
+| Device Adapter | 适配不同厂家、系统、屏幕、SDK、驱动 | 避免项目被硬件联调拖死 |
+| IoMT Event Hub | 接收设备事件、预警事件、护理事件 | 支撑住院 IoMT 闭环 |
+| Metering / Billing | 能力值计量、设备维度计量、场景计量 | 支撑商业闭环 |
+
+---
+
+#### 1.3.3 鸿蒙协同层
+
+鸿蒙层不是 AI 本体,而是方案亮点和国产化抓手。
+
+对外不建议说:
+
+> “我们基于星闪实现所有设备自动互联。”
+
+更稳妥的说法是:
+
+> 医梦优先适配鸿蒙生态设备,结合近场感知、设备发现、统一身份、业务状态判断和事件触发能力,打造“服务找人”的智慧就医体验。
+
+鸿蒙层的核心落地不应是“大而全”,而应是 1-2 个可演示的 POC:
+
+| POC | 场景 | 展示效果 | 技术关键 |
+|---|---|---|---|
+| 主动签到提醒 | 患者到诊室门口 | 手机主动弹出签到卡片,一键确认签到 | 近场感知 + 叫号系统 + HIS 签到 |
+| 检查路线提醒 | 患者到检查区域 | 推送检查准备和路线 | 位置感知 + 检查预约 + 室内地图 |
+| 候诊状态同步 | 患者在候诊区 | 手机、叫号屏、机器人同步显示状态 | 叫号系统 + 设备事件总线 |
+| 医生上下文准备 | 患者进入诊室 | 医生屏自动准备预问诊、报告摘要、病历草稿 | 患者身份 + 就诊状态 + EMR/LIS/PACS |
+
+---
+
+### 1.4 对外建设原则
+
+| 原则 | 解释 | 工程约束 |
+|---|---|---|
+| 医生主导 | AI 只做助手,临床决策权在医生 | 诊断、处方、医嘱、出院小结必须人工确认 |
+| 统一入口 | 所有设备入口都应接入医梦客户端或协议 | 避免各设备各自为政 |
+| 场景驱动 | 不按技术模块卖,而按门诊/住院闭环落地 | 每个场景必须有业务指标 |
+| 设备可治理 | 不只是接入,还要可监控、可升级、可验收 | Device Registry 必须先建 |
+| 业务可审计 | AI 输出、工具调用、人工修改都要留痕 | 日志审计必须进入第一期 |
+| 商业可计量 | 能力值、设备、场景、科室维度可统计 | Metering 必须与中台绑定 |
+| 分阶段实施 | 医疗场景不能大爆炸上线 | P0 闭环先打穿 |
+
+---
+
+## 2. 第二主题:门诊软硬件统一建设清单与职责边界
+
+### 2.1 门诊为什么是优先级最高的战场
+
+门诊是医院最典型的高频入口场景:
+
+- 患者流量最大;
+- 入口设备最多;
+- 问答、导诊、挂号、缴费、签到、叫号、报告、导航等需求高频;
+- 甲方可见度最高;
+- AI 效果最容易被院领导、患者、科室感知;
+- 一旦统一入口形成,后续新增门诊业务都可能通过医梦平台扩展。
+
+所以门诊不是单个 AI 导诊项目,而是入口控制项目。
+
+---
+
+### 2.2 门诊整体建设对象
+
+#### 2.2.1 门诊大厅
+
+| 设备/入口 | 核心职责 | AI 能力 | 医梦责任 | 优先级 |
+|---|---|---|---|---|
+| 导诊大屏 | 院内信息查询、导诊问答、路径引导 | FAQ、导诊、导航、分诊入口 | 统一客户端、知识库、UI、语音 | P0 |
+| 自助机 | 挂号、缴费、签到、报告查询 | 挂号 Agent、缴费 Agent、报告解读入口 | 对接 HIS/支付/LIS/PACS | P0 |
+| 门诊机器人 | 问答、导诊、带路、挂号引导 | 语音问答、导航、带路 Agent | 机器人应用、语音、导航、联调 | P0 |
+| 桌面服务屏 | 快速查询、人工导诊辅助 | FAQ、叫号、科室查询 | 可作为轻入口 | P2 |
+| 综合服务屏 | 展示医院服务、AI 宣教 | 宣教、活动、服务引导 | 内容管理与展示 | P2 |
+
+---
+
+#### 2.2.2 门诊分区 / 候诊区
+
+| 设备/入口 | 核心职责 | AI 能力 | 医梦责任 | 优先级 |
+|---|---|---|---|---|
+| 叫号大屏 | 排队状态、过号提醒、候诊展示 | 叫号智能体、候诊提醒 | 对接叫号系统,展示状态 | P0 |
+| 候诊屏 | 科室信息、候诊说明、宣教 | 科室宣教、预计等待时间 | 内容编排、叫号状态联动 | P0 |
+| 诊区自助机 | 签到、改签、缴费、报告查询 | 签到 Agent、缴费 Agent | 自助机客户端适配 | P1 |
+| 诊区机器人 | 局部导诊、带路、候诊提醒 | 科室 FAQ、带路、叫号提醒 | 机器人场景配置 | P1 |
+
+---
+
+#### 2.2.3 诊室门口
+
+| 设备/入口 | 核心职责 | AI 能力 | 医梦责任 | 优先级 |
+|---|---|---|---|---|
+| 医生介绍屏 | 医生简介、专长、当前叫号 | 医生画像、患者教育 | 对接排班/叫号 | P1 |
+| 诊室门口小屏 | 签到、叫号、候诊提醒 | 服务找人、主动签到 | 与鸿蒙 POC 绑定 | P0/P1 |
+| 候诊提醒屏 | 当前患者、过号提醒 | 状态同步 | 对接叫号系统 | P1 |
+
+---
+
+#### 2.2.4 诊室内
+
+| 设备/入口 | 核心职责 | AI 能力 | 医梦责任 | 优先级 |
+|---|---|---|---|---|
+| 诊室桌面屏 | 诊中 AI 辅助主屏 | 语音病历、CDSS、质控 | 诊间客户端、EMR 对接 | P0 |
+| 诊室副屏 | 展示患者摘要、检查结果、患教 | 预问诊摘要、报告摘要 | 多屏联动 | P1 |
+| 医生站插件 | 嵌入医生工作流 | CDSS、病历生成、质控 | EMR/HIS 插件或侧边栏 | P0 |
+| 麦克风阵列 | 采集问诊语音 | ASR、医患分离、结构化 | 语音服务与转写 | P0 |
+| 四诊仪 | 中医特色采集 | 舌诊、面诊、脉诊、问诊 | 专精模型与设备接入 | P1/P0(中医科) |
+
+---
+
+### 2.3 门诊 P0 试点闭环
+
+第一期不应把所有设备一次性铺开。建议 P0 闭环固定为:
+
+```text
+患者触达 → 导诊问答 → 预问诊 → 分诊推荐 → 挂号预约 → 到院签到 → 叫号候诊 → 诊间语音病历/质控 → 诊后小结/随访
+```
+
+#### 2.3.1 P0 硬件清单
+
+| 类别 | 设备 | 目的 |
+|---|---|---|
+| 入口 | 导诊大屏 | 控制到院入口 |
+| 入口 | 自助机 | 挂号/缴费/签到闭环 |
+| 展示 | 叫号屏/候诊屏 | 候诊状态可视化 |
+| 交互 | 门诊机器人 | 展示性强,医院明确要求 |
+| 诊中 | 诊室桌面屏/医生站插件 | 体现临床价值 |
+| 采集 | 诊室麦克风 | 语音病历基础 |
+| 特色 | 四诊仪 | 中医专精模型抓手 |
+| 鸿蒙 | 诊室门口近场设备/小屏 | 服务找人 POC |
+
+---
+
+### 2.4 设备-场景-智能体绑定模型
+
+门诊总包最关键的不是买设备,而是让设备加载正确的智能体和 UI。
+
+| 设备 | 场景 | 默认智能体 | 可调用工具 | 输出结果 |
+|---|---|---|---|---|
+| 导诊大屏 | 门诊大厅 | 导诊问答 Agent、院内知识库 Agent | 科室查询、地图查询、排班查询 | 科室建议、路线、FAQ 答案 |
+| 自助机 | 门诊大厅/诊区 | 挂号 Agent、缴费 Agent、报告查询 Agent | HIS、支付、LIS/PACS | 挂号单、缴费结果、报告解释 |
+| 机器人 | 大厅/诊区 | 语音导诊 Agent、带路 Agent | 地图、叫号、科室 FAQ | 语音回复、带路、导航 |
+| 叫号屏 | 候诊区 | 叫号状态 Agent、候诊提醒 Agent | 叫号系统、排队系统 | 当前叫号、预计等待 |
+| 医生介绍屏 | 诊室门口 | 医生画像 Agent、患者提醒 Agent | 排班、叫号、医生档案 | 医生介绍、候诊提醒 |
+| 诊室桌面屏 | 诊室内 | 语音病历 Agent、CDSS Agent、质控 Agent | EMR、LIS、PACS、药典 | SOAP 草案、诊疗建议、质控问题 |
+| 四诊仪 | 诊室内 | 舌诊 Agent、面诊 Agent、脉诊 Agent、问诊 Agent | 四诊设备 SDK、专精模型 | 四诊分析、辅助辨证 |
+| 患者端小程序 | 全流程 | 随访 Agent、报告解读 Agent、用药提醒 Agent | LIS/PACS、处方、随访系统 | 诊后小结、报告解释、随访问卷 |
+
+---
+
+### 2.5 设备准入标准
+
+未来医院会有大量设备厂家想进入项目。如果没有准入标准,医梦会承担不可控责任。
+
+建议建立 A/B/C/D 四级准入:
+
+| 等级 | 标准 | 能否进入核心闭环 | 医梦责任 |
+|---|---|---|---|
+| A 级 | 支持医梦统一客户端,开放 API/SDK,支持远程运维和日志 | 可以进入核心闭环 | 医梦深度适配与验收 |
+| B 级 | 支持 Web/H5 嵌入或标准接口,能完成基础业务 | 可进入部分闭环 | 医梦轻量适配 |
+| C 级 | 只能播放内容或展示页面,不开放接口 | 不进入核心闭环 | 仅展示,不承诺业务闭环 |
+| D 级 | 无接口、无系统、无法远程管理、稳定性差 | 不建议采购 | 建议替换或排除 |
+
+---
+
+### 2.6 门诊验收标准
+
+不能以“设备通电、系统能打开”为验收标准。门诊总包应按照业务闭环验收。
+
+| 验收项 | 验收标准 |
+|---|---|
+| 统一入口 | 所有 P0 设备能加载医梦统一客户端或标准接入协议 |
+| 导诊问答 | 常见问题命中率、错误率、人工转接机制符合要求 |
+| 预问诊 | 能生成结构化病史、主诉、过敏史、用药史 |
+| 分诊推荐 | 能给出科室推荐、紧急度判断、可解释依据 |
+| 挂号签到 | 能完成号源查询、挂号、签到、叫号状态同步 |
+| 机器人 | 能完成语音问答、导诊、带路或至少局部导航 |
+| 诊中病历 | 能生成 SOAP 草案,医生可修改确认 |
+| 病历质控 | 能提示完整性、逻辑性、时限性问题 |
+| 诊后服务 | 能生成患者版小结、用药提醒、随访任务 |
+| 审计 | AI 输出、工具调用、人工修改全链路可追溯 |
+
+---
+
+## 3. 第三主题:AI 中台补充建设方案
+
+### 3.1 为什么原 AI 中台不够
+
+原来的 AI 中台如果只解决“智能体调用、知识库、MCP 工具、计量”,在新方案下是不够的。原因是:
+
+1. 设备数量大量增加;
+2. 不同设备有不同系统、分辨率、输入方式、接口能力;
+3. 每个设备需要加载不同智能体;
+4. 机器人、自助机、屏幕、医生站、护理白板等都需要统一入口;
+5. 设备状态、版本、日志、异常告警需要统一管理;
+6. 商业计量需要按设备、科室、场景、智能体统计。
+
+因此 AI 中台必须升级为:
+
+> **AI 中台 + 统一入口客户端 + 设备注册中心 + 设备适配平台 + 场景编排器 + 设备事件总线 + 计量运营系统。**
+
+---
+
+### 3.2 新版中台能力地图
+
+```mermaid
+flowchart TB
+    A[统一入口客户端 Terminal Runtime] --> B[OpenPlatform 统一入口]
+    C[设备注册中心 Device Registry] --> B
+    D[设备适配层 Device Adapter] --> B
+    E[虚拟形象/语音交互引擎] --> A
+    F[Scene Orchestrator 场景编排器] --> A
+    B --> G[Agent 智能体中心]
+    B --> H[Dify Workflow 编排]
+    B --> I[Card Runtime 卡片运行时]
+    B --> J[MCP Tool Server 工具网关]
+    B --> K[Knowledge / Rules 知识库与规则]
+    B --> L[IoMT Event Hub 设备事件中心]
+    B --> M[Metering / Billing 计量结算]
+    J --> N[HIS/EMR/LIS/PACS/支付/叫号/床位/护理]
+```
+
+---
+
+### 3.3 统一入口客户端设计
+
+#### 3.3.1 产品定位
+
+统一入口客户端不是普通 App,而是:
+
+> **医梦部署在所有终端设备上的多设备 AI 场景运行时。**
+
+它要解决的问题不是“展示一个网页”,而是:
+
+- 在不同设备上加载不同 UI 模板;
+- 根据设备位置和角色绑定不同智能体;
+- 支持语音、触控、扫码、摄像头、打印等设备能力;
+- 支持虚拟形象和统一 AI 人设;
+- 支持与中台统一会话、统一身份、统一权限;
+- 支持远程升级、远程配置、异常上报。
+
+---
+
+#### 3.3.2 统一入口客户端配置模型
+
+```yaml
+DeviceProfile:
+  deviceId: EMOON-OPD-KIOSK-001
+  deviceType: self_service_kiosk
+  vendor: xxx
+  os: android/harmony/windows/web
+  screen: 27inch_touch
+  input:
+    - touch
+    - scan_code
+    - id_card
+    - voice
+  output:
+    - screen
+    - speaker
+    - printer
+  location:
+    building: 门诊楼
+    floor: 1F
+    area: 门诊大厅
+  sceneProfile: outpatient_registration
+  agentBindings:
+    - registration_agent
+    - payment_agent
+    - report_query_agent
+  permissions:
+    - query_schedule
+    - lock_number
+    - create_payment_order
+    - query_report
+```
+
+这个模型对研发非常关键。没有这个抽象,后续每台设备都会变成定制开发。
+
+---
+
+#### 3.3.3 多设备技术形态
+
+| 设备类型 | 推荐技术形态 | 原因 |
+|---|---|---|
+| 大屏/叫号屏 | Web 全屏 / Chromium kiosk | 部署简单,统一更新 |
+| 自助机 | Android 壳 + WebView / 原生 Bridge | 需要扫码、打印、读卡能力 |
+| 机器人 | 原生机器人 SDK + 医梦场景客户端 | 需要底盘、导航、语音、传感器能力 |
+| 诊室桌面屏 | Web / Electron / 医生站插件 | 便于与 EMR 联动 |
+| 医生站插件 | iframe/侧边栏/桌面插件 | 贴近医生工作流 |
+| 护理白板 | Web 大屏客户端 | 任务看板适合 Web 化 |
+| 床头屏 | 由迪耐克能力决定,优先 Web/H5 嵌入 | 不抢硬件主责 |
+| PDA | Android/Harmony App 或 H5 | 需要扫码、任务确认 |
+
+---
+
+### 3.4 Device Registry 设备注册中心
+
+#### 3.4.1 为什么必须先做
+
+设备注册中心是设备治理的基础。如果没有它,项目会出现:
+
+- 不知道哪些设备在线;
+- 不知道设备在哪个科室;
+- 不知道设备加载了哪个智能体;
+- 无法远程配置;
+- 无法统计设备使用量;
+- 出问题时无法定位责任。
+
+#### 3.4.2 设备注册中心字段
+
+| 字段 | 说明 |
+|---|---|
+| deviceId | 医梦全局唯一设备 ID |
+| hospitalId | 医院 ID |
+| departmentId | 科室 ID |
+| location | 楼栋、楼层、区域、诊室、床位 |
+| deviceType | 导诊屏、自助机、机器人、叫号屏等 |
+| vendor | 厂商 |
+| model | 设备型号 |
+| os | 系统类型 |
+| capabilities | 支持的能力:屏幕、语音、打印、扫码、摄像头等 |
+| agentBindings | 绑定的智能体 |
+| uiTemplate | 加载的 UI 模板 |
+| status | 在线、离线、异常、维护中 |
+| version | 客户端版本 |
+| lastHeartbeat | 最后心跳时间 |
+| slaLevel | SLA 等级 |
+
+---
+
+### 3.5 Device Adapter 设备适配层
+
+设备适配层要把不同厂家的差异收敛成医梦标准能力。
+
+```mermaid
+flowchart LR
+    A[第三方设备 SDK/API] --> B[Device Adapter]
+    C[自研机器人驱动] --> B
+    D[自助机读卡/打印/扫码] --> B
+    E[四诊仪采集接口] --> B
+    F[迪耐克病房接口] --> B
+    B --> G[医梦标准设备能力协议]
+    G --> H[统一入口客户端]
+    G --> I[AI 中台]
+```
+
+#### 3.5.1 标准设备能力协议
+
+| 能力 | 标准动作 |
+|---|---|
+| 屏幕显示 | renderCard、showPage、showAlert |
+| 语音采集 | startRecord、stopRecord、sendAudio |
+| 语音播放 | playTTS、stopTTS |
+| 摄像头 | captureImage、scanFace、scanQRCode |
+| 打印 | printReceipt、printWristband、printReport |
+| 读卡 | readIdCard、readMedicalCard |
+| 导航 | startNavigation、stopNavigation、getLocation |
+| 机器人 | moveTo、followPatient、speak、returnDock |
+| IoMT | subscribeEvent、ackEvent、closeEvent |
+
+---
+
+### 3.6 Scene Orchestrator 场景编排器
+
+场景编排器解决一个核心问题:
+
+> 同一个设备,在不同位置、时间、用户、业务状态下,应该加载哪个智能体、展示哪个界面、调用哪些工具。
+
+#### 示例:诊室门口小屏
+
+| 条件 | 加载内容 |
+|---|---|
+| 开诊前 | 医生介绍、科室介绍、当日排班 |
+| 候诊中 | 当前叫号、候诊队列、预计等待 |
+| 患者靠近 | 主动签到提醒、就诊准备提醒 |
+| 医生暂停 | 停诊/延迟说明、改签引导 |
+| 异常情况 | 人工导诊入口、服务台电话 |
+
+#### 示例:机器人
+
+| 场景 | 智能体 | 能力 |
+|---|---|---|
+| 大厅咨询 | FAQ Agent | 院内常见问题 |
+| 症状咨询 | 分诊 Agent | 症状采集、科室推荐 |
+| 患者迷路 | 导航 Agent | 路线规划、带路 |
+| 挂号引导 | 挂号 Agent | 号源查询、自助机引导 |
+| 老年患者 | 人工转接 Agent | 转人工导诊 |
+
+---
+
+### 3.7 Card Runtime 设计
+
+AI 不能直接改业务系统,必须通过确定性卡片动作。
+
+| 卡片 | 场景 | 动作 | 审计要求 |
+|---|---|---|---|
+| 科室推荐卡 | 分诊后 | 展示推荐科室、理由、备选科室 | 记录推荐依据 |
+| 号源卡 | 挂号 | 选择医生/时间/号源 | 记录查询与选择 |
+| 锁号卡 | 挂号 | 锁定号源 | 幂等、防重复 |
+| 支付卡 | 缴费 | 创建支付订单 | 防重复扣款 |
+| 签到卡 | 到院 | 确认签到 | 记录位置与确认人 |
+| 报告解读卡 | 诊后/检查后 | 展示报告解释 | 标注 AI 仅供参考 |
+| 病历草案卡 | 诊中 | 生成 SOAP 草案 | 医生确认后写回 |
+| 质控问题卡 | 病历提交前 | 展示问题、修改建议 | 记录医生处理结果 |
+| 护理任务卡 | 住院 | 生成护理任务 | 护士确认执行 |
+| 预警工单卡 | IoMT | 展示预警、建议处理 | 闭环关闭 |
+
+---
+
+## 4. 第四主题:AI 医院整体解决方案技术规划会议纪要深度解读
+
+### 4.1 会议核心共识拆解
+
+#### 共识 1:门诊由医梦整体包掉
+
+会议中的真实含义是:
+
+- 门诊设备谁能进,医梦应有技术建议权和准入权;
+- 门诊所有带屏设备应接入医梦统一入口客户端;
+- 门诊机器人、自助机、导诊屏、叫号屏、诊室屏、四诊仪都应成为医梦中台的终端节点;
+- 医院未来门诊新增 AI 服务、新增终端、新增场景,应优先通过医梦平台扩展。
+
+这不是“我们生产所有设备”,而是“我们控制设备入口标准”。
+
+---
+
+#### 共识 2:住院不做硬件总包,做 AI 大脑层
+
+住院侧已经有迪耐克/鸿蒙病房体系,医梦如果强行做住院硬件总包,会带来三个问题:
+
+1. 与既有厂商冲突;
+2. 承担过多硬件运维责任;
+3. 项目范围爆炸。
+
+正确策略是:
+
+> 医梦提供住院智能体、AI 大脑层、IoMT 预警解释、护理任务生成、病历质控、出院随访,与迪耐克的床头屏、护理白板、护士站、病房设备互通。
+
+---
+
+#### 共识 3:“机器人无处不在”不是到处放机器人
+
+会议中提到“机器人无处不在”,真正应解释为:
+
+> **AI 机器人能力无处不在。**
+
+也就是说:
+
+- 在门诊大厅,它表现为实体机器人;
+- 在导诊屏上,它表现为虚拟形象;
+- 在自助机上,它表现为挂号/缴费助手;
+- 在叫号屏上,它表现为候诊提醒智能体;
+- 在诊室屏上,它表现为病历/质控助手;
+- 在床头屏上,它表现为虚拟护士;
+- 在护理白板上,它表现为护理任务和预警助手。
+
+这是一套 AI 能力的多终端分身,而不是物理机器人 everywhere。
+
+---
+
+#### 共识 4:统一入口客户端是战略控制点
+
+会议中“把所有流量入口做到我们手上”这句话非常关键。它意味着:
+
+- 医梦未来不是只卖 Agent;
+- 医梦要控制患者与 AI 的第一触点;
+- 医梦要控制设备上显示什么服务;
+- 医梦要控制设备与中台之间的协议;
+- 医梦要控制用量、日志、运营、持续服务。
+
+这会带来长期商业价值,但也带来研发压力:统一入口客户端必须产品化,而不是项目临时页面。
+
+---
+
+#### 共识 5:鸿蒙层的价值是“服务找人”
+
+鸿蒙层在方案中不能只写“国产化适配”,否则价值弱。它必须转化为业务体验:
+
+> 患者不是去找机器、找窗口、找二维码,而是医院服务主动找到患者。
+
+最适合讲给甲方的故事是:
+
+1. 患者到诊室附近;
+2. 手机收到“是否签到”提醒;
+3. 患者一键确认;
+4. 叫号屏、诊室屏、医生站、手机状态同步;
+5. AI 推送候诊提醒、医生介绍、就诊准备。
+
+---
+
+### 4.2 会议场景逐项拆解
+
+#### 4.2.1 门诊大屏场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 初次到院患者、家属、老年患者 |
+| 痛点 | 不知道挂哪个科、不知道去哪、不知道流程 |
+| AI 能力 | 导诊问答、科室推荐、院内导航、FAQ |
+| 设备能力 | 大屏触控、语音输入、二维码展示 |
+| 关键系统 | 科室知识库、室内地图、排班系统 |
+| 输出 | 科室建议、路线、挂号入口、二维码转手机 |
+| 风险 | 大屏输入隐私问题;复杂症状不能强行给诊断 |
+| MVP | FAQ + 科室推荐 + 路线展示 + 扫码到手机 |
+
+---
+
+#### 4.2.2 自助机场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 到院办理挂号、缴费、签到、报告查询的患者 |
+| 痛点 | 自助机菜单复杂、不会操作、排队长 |
+| AI 能力 | 意图识别、挂号辅助、缴费解释、报告解读入口 |
+| 设备能力 | 读卡、扫码、打印、支付、触摸屏 |
+| 关键系统 | HIS、支付、LIS/PACS、叫号系统 |
+| 输出 | 挂号单、缴费凭证、签到结果、报告查询 |
+| 风险 | 支付和挂号必须幂等;不能重复扣款/重复挂号 |
+| MVP | AI 引导挂号 + 缴费 + 签到 + 报告查询入口 |
+
+---
+
+#### 4.2.3 门诊机器人成景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 患者、家属、老年患者、外地患者 |
+| 痛点 | 找不到科室、不会操作自助机、需要人工导诊 |
+| AI 能力 | 语音问答、导诊分诊、FAQ、导航、带路 |
+| 设备能力 | 底盘、避障、定位、语音、屏幕、表情 |
+| 关键系统 | 室内地图、科室知识库、叫号/挂号系统 |
+| 输出 | 语音回答、路线、带路、挂号引导 |
+| 风险 | 导航稳定性、噪声环境、老人识别、跌倒碰撞安全 |
+| MVP | 门诊大厅 FAQ + 局部带路 + 挂号引导 |
+
+机器人必须保留,但定位要清楚:
+
+> 它是门诊入口的强展示终端之一,不是全部方案本身。
+
+---
+
+#### 4.2.4 叫号屏 / 候诊屏场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 候诊患者、护士、分诊台 |
+| 痛点 | 不知道还要等多久、过号后不知道怎么办 |
+| AI 能力 | 候诊解释、预计等待、过号引导、宣教推荐 |
+| 设备能力 | 大屏展示、状态刷新、语音播报 |
+| 关键系统 | 叫号系统、排队系统、HIS |
+| 输出 | 当前叫号、候诊列表、预计等待、过号处理 |
+| 风险 | 显示隐私;不能泄露诊断信息 |
+| MVP | 叫号状态 + 预计等待 + 过号处理说明 |
+
+---
+
+#### 4.2.5 诊室门口屏 / 医生介绍屏场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 候诊患者、医生、护士 |
+| 痛点 | 患者不了解医生、找不到是否到号、签到麻烦 |
+| AI 能力 | 医生介绍、主动签到提醒、候诊提醒 |
+| 设备能力 | 小屏展示、二维码、近场感知 |
+| 关键系统 | 排班、叫号、HIS、鸿蒙/近场能力 |
+| 输出 | 医生介绍、当前叫号、签到卡片 |
+| 风险 | 自动签到误触发;必须患者确认 |
+| MVP | 医生介绍 + 当前叫号 + 手机主动签到提醒 |
+
+---
+
+#### 4.2.6 诊室桌面屏 / 医生站场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 门诊医生 |
+| 痛点 | 病历书写耗时、检查结果分散、质控滞后 |
+| AI 能力 | 语音转写、SOAP 草案、CDSS、病历质控、报告摘要 |
+| 设备能力 | 桌面屏、麦克风、EMR 插件 |
+| 关键系统 | EMR、HIS、LIS、PACS、药典、质控规则 |
+| 输出 | 病历草案、诊疗建议、质控问题、患教材料 |
+| 风险 | 医生必须确认;AI 建议不能直接成为诊断/处方 |
+| MVP | 语音病历 + 病历质控 + 检查报告摘要 |
+
+---
+
+#### 4.2.7 四诊仪场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 中医科医生、患者 |
+| 痛点 | 中医望闻问切主观性强,数据难沉淀 |
+| AI 能力 | 舌诊、面诊、脉诊、问诊综合分析 |
+| 设备能力 | 图像采集、脉象采集、音频采集、结构化问诊 |
+| 关键系统 | 四诊仪 SDK、专精模型、EMR |
+| 输出 | 舌象分析、面象分析、脉象特征、辅助辨证 |
+| 风险 | 中医结论必须是辅助;模型需要持续校准 |
+| MVP | 舌诊 + 问诊 + 医生确认报告 |
+
+---
+
+#### 4.2.8 护理白板场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 护士长、责任护士、病区医生 |
+| 痛点 | 任务多、风险患者难追踪、交班信息分散 |
+| AI 能力 | 护理任务生成、风险分级、交班摘要、重点患者看板 |
+| 设备能力 | 大屏展示、触控、任务状态更新 |
+| 关键系统 | 护理系统、EMR、IoMT、迪耐克病房系统 |
+| 输出 | 护理任务、风险患者、未完成事项、交班摘要 |
+| 风险 | 医梦不能对迪耐克屏幕硬件故障承担主责 |
+| MVP | 护理任务 + IoMT 预警 + 交班摘要嵌入 |
+
+---
+
+#### 4.2.9 床头屏场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 住院患者、家属、护士 |
+| 痛点 | 患者频繁询问费用、检查、报告、医生、注意事项 |
+| AI 能力 | 床旁问答、报告解读、宣教、检查提醒、满意度调查 |
+| 设备能力 | 触屏、语音、呼叫、视频播放 |
+| 关键系统 | 迪耐克床头屏、HIS、LIS/PACS、护理系统 |
+| 输出 | 费用查询、检查提醒、报告解释、宣教内容 |
+| 风险 | 屏幕硬件和基础呼叫功能不归医梦主责 |
+| MVP | AI 服务嵌入床头屏:FAQ + 报告解读 + 宣教 |
+
+---
+
+#### 4.2.10 病区机器人场景
+
+| 项 | 内容 |
+|---|---|
+| 用户 | 住院患者、护士 |
+| 痛点 | 病区问答、物品配送、巡视提醒、人手不足 |
+| AI 能力 | 病区 FAQ、虚拟护士、巡房提醒、风险提示 |
+| 设备能力 | 移动底盘、语音、屏幕、导航、载物 |
+| 关键系统 | 护理系统、床位系统、IoMT、迪耐克系统 |
+| 输出 | 问答、提醒、巡查记录、任务反馈 |
+| 风险 | 送餐/购物等场景容易超出医疗主线,第一期不宜扩大 |
+| MVP | 病区问答 + 呼叫响应 + 简单巡视提醒 |
+
+---
+
+## 5. 第五主题:医梦新增职责与实施路线
+
+### 5.1 医梦新增职责总表
+
+| 新职责 | 具体内容 | 必要性 | 风险 |
+|---|---|---|---|
+| 门诊总体方案主责 | 方案、设备、系统、AI、验收 | 控制门诊入口 | 范围过大 |
+| 设备选型与准入 | 制定准入标准、推荐厂商、验收设备 | 控制硬件质量 | 合规风险 |
+| 统一入口客户端 | 多设备运行时、虚拟形象、语音、UI 模板 | 控制流量入口 | 研发复杂 |
+| 设备适配平台 | 设备注册、SDK、远程配置、状态监控 | 防止联调失控 | 需要专门团队 |
+| 第三方设备生态 | 供应商规范、接入测试、联合验收 | 门诊总包必须做 | 商务边界复杂 |
+| 住院 AI 互通 | 对接迪耐克、护理系统、IoMT、床头屏 | 保持住院控制力 | 边界不清会背锅 |
+| 鸿蒙服务找人 POC | 主动签到、检查提醒、候诊状态同步 | 标杆亮点 | 技术实现不确定 |
+| 商业计量运营 | 能力值、设备授权、运营服务 | 支撑长期收入 | 计量体系复杂 |
+
+---
+
+### 5.2 团队拆分建议
+
+不能让一个研发组同时做中台、机器人、设备、医生站、鸿蒙、住院互通。建议拆成以下小组:
+
+| 团队 | 阶段人数 | 核心职责 |
+|---|---:|---|
+| AI 中台组 | 3-5 | OpenPlatform、MCP、Card、计量、权限、审计 |
+| 智能体/医疗场景组 | 2-4 | 门诊/住院 Agent、知识库、Dify 流程、医学规则 |
+| 统一入口客户端组 | 3-5 | 多终端 UI、虚拟形象、语音交互、设备模板 |
+| 设备适配组 | 2-4 | Device Registry、Adapter、SDK、驱动桥接、远程运维 |
+| 机器人组 | 2-3 | 机器人导诊、导航、语音、硬件驱动 |
+| 项目集成/PMO | 2-3 | 医院接口、供应商联调、验收、排期管理 |
+| 供应链/生态合作 | 1-2 | 设备准入、厂商谈判、合同边界 |
+| 测试与运维 | 1-2 | 设备测试、场景测试、压力测试、上线保障 |
+
+---
+
+### 5.3 三阶段实施路线
+
+#### 阶段 0:两周边界锁定
+
+| 任务 | 输出 |
+|---|---|
+| 门诊存量设备盘点 | 存量设备清单、系统能力、接口情况 |
+| 门诊新增设备规划 | P0/P1/P2 设备清单 |
+| 迪耐克接口摸底 | 住院互通接口清单、责任边界 |
+| 鸿蒙 POC 可行性确认 | 主动签到场景技术方案 |
+| 统一入口客户端 PRD | 多设备运行时需求 v1 |
+| 设备准入标准 | A/B/C/D 分级与验收标准 |
+
+---
+
+#### 阶段 1:1-2 个月,门诊 MVP
+
+| 任务 | 输出 |
+|---|---|
+| AI 中台补设备维度 | 设备注册、场景绑定、智能体绑定 |
+| 统一入口客户端 MVP | 导诊屏/自助机/机器人/诊室屏可运行 |
+| 门诊导诊 Agent | FAQ、分诊、挂号引导 |
+| 机器人导诊 | 语音问答、局部导航、带路 |
+| 叫号/签到对接 | 签到候诊状态同步 |
+| 诊室屏 | 语音病历、病历质控基础版 |
+| 鸿蒙服务找人设计 | 进入开发准备 |
+
+---
+
+#### 阶段 2:2-4 个月,打穿闭环
+
+| 任务 | 输出 |
+|---|---|
+| 门诊分诊挂号闭环 | 预问诊、科室推荐、号源推荐、签到 |
+| 诊中语音病历 | 医生站/诊室屏联动 |
+| 四诊仪接入 | 舌诊/问诊/脉诊进入诊间 |
+| 诊后小结/随访 | 患者端闭环 |
+| 迪耐克住院互通 | 护理白板/床头屏/IoMT 事件接入 |
+| 护理任务闭环 | AI 生成任务,护士确认执行 |
+| IoMT 预警解释 | 风险分级、处置建议、闭环记录 |
+
+---
+
+#### 阶段 3:4-6 个月,标杆方案
+
+| 任务 | 输出 |
+|---|---|
+| 门诊多设备统一运营 | P0/P1 设备纳入管理后台 |
+| 鸿蒙服务找人上线 | 主动签到/候诊提醒示范 |
+| 运营看板 | 设备、流量、智能体、业务效果统计 |
+| 能力值计量 | 按智能体、设备、科室、场景统计 |
+| 供应商生态 | 设备准入、第三方适配、联合验收 |
+| 标杆汇报材料 | 甲方快乐图、演示视频、验收报告 |
+
+---
+
+## 6. 门诊全流程深度方案
+
+### 6.1 门诊主线
+
+```text
+患者到院/线上触达
+  → AI 导诊问答
+  → 多模态预问诊
+  → 风险分级与科室推荐
+  → 医生/号源推荐
+  → 挂号缴费
+  → 到院签到
+  → 叫号候诊
+  → 诊中语音病历 + CDSS + 质控
+  → 检查检验/缴费/报告解读
+  → 诊后小结
+  → 用药提醒
+  → AI 随访
+  → 复诊推荐
+```
+
+---
+
+### 6.2 门诊导诊意图分流设计
+
+| 意图类型 | 判断依据 | 后续动作 | 风险控制 |
+|---|---|---|---|
+| 症状咨询 | 患者描述身体不适 | 多轮症状采集、风险分级、科室推荐 | 红旗症状转急诊/人工 |
+| 院内 FAQ | 问科室位置、费用、流程 | 院内知识库回答 | 不涉及诊疗建议 |
+| 挂号预约 | 明确想挂号/找医生 | 号源推荐、锁号、支付 | 幂等、防重复 |
+| 报告解读 | 查询检验/影像报告 | 报告通俗解读、异常提示 | 标注需医生确认 |
+| 缴费/导航 | 问缴费、检查地点 | 查询待缴费、室内导航 | 支付动作可追溯 |
+| 投诉/人工 | 情绪强烈或无法识别 | 转人工服务台 | 不强行 AI 回复 |
+
+---
+
+### 6.3 诊前预问诊设计
+
+#### 输入
+
+- 主诉;
+- 现病史;
+- 既往史;
+- 过敏史;
+- 用药史;
+- 家族史;
+- 检查报告;
+- 患者既往就诊记录;
+- 文本、语音、结构化选项。
+
+#### 输出
+
+| 输出 | 用途 |
+|---|---|
+| 结构化预问诊报告 | 给医生诊前浏览 |
+| 分诊建议 | 推荐科室/医生 |
+| 紧急度等级 | 急诊/优先/常规 |
+| 就诊准备清单 | 检查资料、空腹、证件 |
+| 患者画像 | 后续随访和复诊 |
+
+---
+
+### 6.4 诊中协同设计
+
+#### 诊间语音病历
+
+| 环节 | 说明 |
+|---|---|
+| 采集 | 诊室麦克风阵列采集医患对话 |
+| 转写 | ASR 实时转文字,区分医生/患者 |
+| 抽取 | 抽取症状、体征、诊断、药品、检查 |
+| 生成 | 生成 SOAP 草案、主诉、现病史 |
+| 确认 | 医生修改确认后写回 EMR |
+
+#### CDSS
+
+| 能力 | 说明 |
+|---|---|
+| 辅助诊断 | 根据症状、体征、检查给出鉴别诊断 |
+| 检查推荐 | 推荐必要检查,避免过度/不足检查 |
+| 合理用药 | 提示禁忌、相互作用、剂量异常 |
+| 治疗建议 | 给出指南路径,但医生主导 |
+
+#### 病历质控
+
+| 质控类型 | 示例 |
+|---|---|
+| 完整性 | 缺少主诉、诊断依据、过敏史 |
+| 逻辑性 | 症状与诊断不匹配 |
+| 时限性 | 病历未及时完成 |
+| 内涵质控 | 跨段落、跨文本、跨就诊记录矛盾 |
+
+---
+
+## 7. 住院全流程深度方案
+
+### 7.1 住院主线
+
+```text
+门诊/急诊发起入院建议
+  → 预住院评估
+  → 入院办理
+  → 床位调度
+  → 病区接收
+  → 医生查房/智慧病历
+  → 护理风险评估
+  → 护理任务派发
+  → IoMT 事件采集
+  → AI 风险解释
+  → 护士处置闭环
+  → 出院条件评估
+  → 出院小结/康复计划
+  → AI 随访/远程管理
+```
+
+---
+
+### 7.2 预住院评估设计
+
+| 输入 | 输出 |
+|---|---|
+| 门诊诊断、入院建议 | 入院必要性评估 |
+| 患者病史、症状、过敏史 | 结构化预评估报告 |
+| 检查检验结果 | 检查补充建议 |
+| 护理风险因素 | 护理等级建议 |
+| 床位与资源状态 | 入院时间建议 |
+
+---
+
+### 7.3 医生工作流设计
+
+| 场景 | AI 能力 | 写回方式 |
+|---|---|---|
+| 查房记录 | 语音转写、结构化摘要 | 医生确认后写回 EMR |
+| 病程记录 | 自动生成病程草案 | 医生修改签名 |
+| 手术记录 | 根据术式模板生成草案 | 医生确认 |
+| CDSS | 辅助诊断、检查、治疗建议 | 仅展示,不自动医嘱 |
+| 病历质控 | 实时/终末质控 | 医生处理后归档 |
+| 出院意向 | 触发出院准备 | 医生确认 |
+
+---
+
+### 7.4 护理工作流设计
+
+| 场景 | AI 能力 | 执行终端 |
+|---|---|---|
+| 入科宣教 | 自动生成宣教内容 | 床头屏/护士 PDA |
+| 风险评估 | 压疮、跌倒、VTE、谵妄评分 | 护士 PDA |
+| 任务派发 | 翻身、输液巡视、生命体征、宣教 | 护理系统/PDA |
+| 交班摘要 | 重点患者、未完成任务、风险事件 | 护理白板 |
+| 预警闭环 | 预警工单、处置建议、关闭记录 | PDA/护理白板 |
+
+---
+
+### 7.5 IoMT 事件闭环设计
+
+| 环节 | 说明 |
+|---|---|
+| 设备采集 | 生命体征、输液、毫米波雷达、床垫压力 |
+| 网关归一 | 统一患者 ID、床位 ID、设备 ID、时间戳 |
+| 事件识别 | 离床、跌倒、输液异常、体征异常、压疮风险 |
+| AI 解释 | 结合病历解释风险含义和处置优先级 |
+| 工单生成 | 生成护理任务或预警工单 |
+| 护士处置 | 护士确认、处理、关闭 |
+| 审计回流 | 记录响应时间、处置结果、闭环状态 |
+
+关键原则:
+
+> **设备事件不是业务闭环,护士确认处置后才是业务闭环。**
+
+---
+
+## 8. 鸿蒙服务找人专题方案
+
+### 8.1 核心定义
+
+“服务找人”不是设备自动控制患者,而是:
+
+> 根据患者身份、位置、预约状态、叫号状态、检查任务等上下文,让医院服务主动以卡片、提醒、屏幕联动或机器人交互的形式触达患者。
+
+---
+
+### 8.2 主动签到 POC
+
+```mermaid
+sequenceDiagram
+    participant P as 患者手机
+    participant N as 近场设备/诊室门口屏
+    participant A as 医梦 AI 中台
+    participant Q as 叫号系统
+    participant H as HIS/签到系统
+    P->>N: 到达诊室门口区域
+    N->>A: 上报患者靠近事件
+    A->>Q: 查询当前队列与患者状态
+    A->>H: 查询是否可签到
+    A->>P: 推送签到服务卡片
+    P->>A: 一键确认签到
+    A->>H: 调用签到接口
+    A->>Q: 同步叫号状态
+    A->>P: 返回候诊提醒
+```
+
+---
+
+### 8.3 技术兜底路径
+
+不要把方案写死为“必须鸿蒙 + 星闪”。应设计多种路径:
+
+| 路径 | 用途 | 风险 |
+|---|---|---|
+| 鸿蒙生态设备发现 | 标杆展示 | 取决于设备生态成熟度 |
+| 星闪/近场通信 | 低延迟设备发现 | 医院设备覆盖有限 |
+| BLE Beacon | 低成本定位 | 精度和授权问题 |
+| Wi-Fi/院内定位 | 区域判断 | 信息科配合度 |
+| 二维码/NFC | 最低成本兜底 | 体验不如主动触达 |
+| 小程序位置授权 | 患者端兜底 | 用户授权率问题 |
+
+---
+
+## 9. 商业模式与收费补充
+
+### 9.1 原有商业口径
+
+医梦商业模式原本已经形成:
+
+- 三层合作模式:L1 标准能力接入、L2 院级场景建设、L3 区域平台运营;
+- 四类收费口径:接入开通费、项目实施费、综合运营服务费、能力值包/合理不限量服务包;
+- 两种用量选择:按量版、合理不限量版;
+- 一套能力值定价标准。
+
+---
+
+### 9.2 新方案需要增加的收费项
+
+| 收费项 | 原因 | 建议归属 |
+|---|---|---|
+| 设备集成服务费 | 门诊设备大量增加,接入、适配、测试成本高 | 项目实施费扩展项 |
+| 统一入口客户端授权费 | 每台设备运行医梦客户端 | 按设备/场景/年度授权 |
+| 设备运维服务费 | 设备巡检、状态监控、远程配置、升级 | 综合运营服务费扩展项 |
+| 供应链管理服务费 | 医梦做设备选型、准入、采购建议、验收 | 项目服务费/管理服务费 |
+| 鸿蒙化试点建设费 | 服务找人 POC 需要独立设计、联调、验收 | 标杆亮点专项 |
+| 机器人场景服务费 | 机器人导诊、带路、语音服务有持续成本 | 硬件费 + 服务费 + 能力值 |
+| 第三方设备准入测试费 | 厂家接入医梦生态需要测试验收 | 生态合作服务费 |
+
+---
+
+### 9.3 合规口径替换
+
+内部可以讨论商业收益,但对外不能出现“回扣”等高风险词。
+
+| 禁用表达 | 正式表达 |
+|---|---|
+| 回扣 | 渠道合作折扣 / 生态合作服务费 |
+| 谁给钱谁进 | 符合准入标准并通过联调验收 |
+| 我们说哪家进哪家进 | 医梦提供技术准入评估和推荐选型,最终由医院采购流程确定 |
+| 设备利润 | 供应链服务收益 / 集成服务收益 |
+| 不给回扣不让进 | 不符合技术准入和交付保障要求,不纳入推荐清单 |
+
+---
+
+## 10. 风险分析与纠偏建议
+
+### 10.1 范围爆炸风险
+
+当前项目同时涉及:门诊总包、住院互通、机器人、统一入口客户端、设备适配、鸿蒙、AI 中台、Dify、MCP、Card、HIS/EMR/LIS/PACS、迪耐克对接、供应链。
+
+如果不做边界控制,项目会失控。
+
+**纠偏:**
+
+| 第一阶段只做 | 暂缓 |
+|---|---|
+| 门诊 P0 设备闭环 | 智能镜 |
+| 机器人大厅导诊 | 全院机器人巡逻 |
+| 诊室语音病历 | 复杂 3D 虚拟人 |
+| 诊室门口主动签到 | 全院鸿蒙化 |
+| 住院迪耐克互通 | 医梦自建住院硬件总包 |
+
+---
+
+### 10.2 设备适配风险
+
+| 风险 | 处理 |
+|---|---|
+| 厂商不开放接口 | 不进入核心闭环 |
+| 屏幕只支持播放 | 降级为展示屏 |
+| SDK 不稳定 | 建立验收测试和责任边界 |
+| 设备离线 | Device Registry + 心跳监控 |
+| 版本碎片化 | 统一客户端版本管理 |
+
+---
+
+### 10.3 住院边界风险
+
+必须提前明确:
+
+| 问题 | 主责 |
+|---|---|
+| 床头屏硬件故障 | 迪耐克/设备方 |
+| 护理白板无法显示 | 迪耐克/设备方 |
+| AI 服务不可用 | 医梦 |
+| 接口字段缺失 | 双方按接口协议确认 |
+| IoMT 原始数据异常 | 设备方/迪耐克 |
+| AI 风险解释错误 | 医梦负责解释逻辑,医护最终确认 |
+
+---
+
+### 10.4 鸿蒙过度承诺风险
+
+不要承诺:
+
+- 全院所有设备鸿蒙化;
+- 所有患者自动识别;
+- 无需授权自动签到;
+- 所有手机都能无感服务找人。
+
+建议承诺:
+
+- 在试点诊区实现“服务找人”演示闭环;
+- 支持鸿蒙生态优先,同时保留 BLE/NFC/二维码兜底;
+- 所有签到、支付、挂号等动作必须患者确认。
+
+---
+
+## 11. 后续会议拆解提纲
+
+### 11.1 第一场:战略边界会
+
+| 议题 | 目标 |
+|---|---|
+| 医梦最新定位 | 统一门诊总包 + 住院 AI 协同口径 |
+| 门诊和住院边界 | 防止住院硬件责任扩散 |
+| 统一入口价值 | 确认客户端是战略产品 |
+| 设备生态规则 | 确认医梦有技术准入权 |
+| 第一阶段范围 | 锁定 P0 闭环 |
+
+---
+
+### 11.2 第二场:门诊业务闭环会
+
+| 议题 | 输出 |
+|---|---|
+| 门诊患者旅程 | 患者从入院到诊后的完整流程 |
+| P0 设备清单 | 导诊屏、自助机、机器人、叫号屏、诊室屏等 |
+| 导诊分诊逻辑 | 症状、FAQ、挂号、报告、缴费导航 |
+| 诊中协同 | 语音病历、CDSS、质控 |
+| 验收指标 | 按业务闭环验收 |
+
+---
+
+### 11.3 第三场:AI 中台与设备适配会
+
+| 议题 | 输出 |
+|---|---|
+| 统一入口客户端架构 | 多设备运行时方案 |
+| Device Registry | 设备注册字段和接口 |
+| Device Adapter | 设备能力标准协议 |
+| Agent Binding | 设备与智能体绑定规则 |
+| 运维与计量 | 设备在线、日志、能力值统计 |
+
+---
+
+### 11.4 第四场:住院互通会
+
+| 议题 | 输出 |
+|---|---|
+| 迪耐克接口清单 | 床头屏、护理白板、IoMT、护士站接口 |
+| AI 大脑层职责 | 医梦负责哪些智能体 |
+| 护理任务闭环 | AI 生成、护士执行、白板展示 |
+| IoMT 预警解释 | 设备事件到护理工单 |
+| 责任边界 | 医梦/迪耐克/医院各自责任 |
+
+---
+
+### 11.5 第五场:鸿蒙服务找人 POC 会
+
+| 议题 | 输出 |
+|---|---|
+| POC 场景选择 | 主动签到优先 |
+| 技术路径 | 鸿蒙/近场/BLE/NFC/二维码兜底 |
+| 业务流程 | 患者确认、叫号同步、日志留痕 |
+| 演示脚本 | 给院领导看的完整演示 |
+| 验收方式 | 可用性、准确率、误触发率 |
+
+---
+
+## 12. 结论
+
+医梦当前最重要的不是继续扩写“AI 门诊”和“AI 住院”的功能清单,而是完成以下战略动作:
+
+1. **把门诊总包边界锁死:医梦主导门诊软硬件入口、设备准入、统一客户端、AI 中台和业务闭环。**
+2. **把住院边界收敛:医梦做 AI 大脑层和迪耐克互通,不抢病房硬件总包。**
+3. **把统一入口客户端提升为核心产品:所有带屏设备都要加载医梦统一入口。**
+4. **把设备适配平台尽快立项:没有 Device Registry 和 Device Adapter,设备越多项目越乱。**
+5. **把鸿蒙服务找人做成一个小而硬的标杆 POC:主动签到最适合第一期。**
+6. **把商业模式从 AI 能力收费升级为:项目实施费 + 设备集成服务费 + 入口客户端授权 + 能力值 + 综合运营服务费。**
+7. **把后续会议拆成专题会,不要在一个会里同时谈战略、设备、AI、鸿蒙、住院和商务。**
+
+最终要形成的不是一堆智能设备,而是一套可控的医院 AI 入口体系:
+
+> **患者从哪里进入,医梦就在哪里出现;医生在哪里工作,医梦就在哪里辅助;设备在哪里产生事件,医梦就在哪里解释;医院在哪里需要运营,医梦就在哪里计量和闭环。**
+
+这才是本项目真正的商业和技术控制位。

Some files were not shown because too many files changed in this diff