From e18c6613682f83d34a0d08d5ee1c5a5eb26d2c79 Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Sat, 7 Feb 2026 15:48:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(api-converter):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=BF=9B=E9=94=80=E5=AD=98=E9=87=87=E8=B4=AD=E8=AE=A2=E5=8D=95?= =?UTF-8?q?API=E9=85=8D=E7=BD=AE=E5=B9=B6=E5=AE=9E=E7=8E=B0=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增api_config_9p04kww1pu.json配置文件,包含进销存采购订单相关的四个核心 API接口(查询列表、新建、详情、编辑),完善了load_api_configs函数, 增加本地文件缓存机制,支持从本地文件加载配置并在配置变更时同步保存, 优化refresh_api_configs函数以同步清理本地文件缓存。 BREAKING CHANGE: API配置方式调整,引入本地缓存机制可能影响原有部署流程 --- .../src/api_config_9p04kww1pu.json | 483 +++++++++++++++ .../src/create_mcp.py | 46 +- lzwcai_mcp_api_converter/main.py | 6 +- .../logs/lzwcai_mcp_sqlexecutor.log | 358 +++++++++++ .../logs/lzwcai_mcp_sqlexecutor_daily.log | 581 +++++++++++------- ...zwcai_mcp_sqlexecutor_daily.log.2026-01-26 | 223 +++++++ .../logs/mcp_services.log | 61 ++ .../lzwcai_mcp_sqlexecutor/pyproject.toml | 4 +- lzwcai_mcp_sqlexecutor/main.py | 6 +- lzwcai_mcp_sqlexecutor/pyproject.toml | 2 +- .../businessQueries.json | 12 +- .../logs/lzwcai_mcp_sqlexecutor.log | 143 +++++ .../logs/lzwcai_mcp_sqlexecutor_daily.log | 202 +++--- ...zwcai_mcp_sqlexecutor_daily.log.2026-01-26 | 109 ++++ .../logs/mcp_services.log | 53 ++ .../pyproject.toml | 2 +- .../businessQueries.json | 12 +- .../pyproject.toml | 2 +- .../__pycache__/main.cpython-312.pyc | Bin 0 -> 14593 bytes .../schema_converter.cpython-312.pyc | Bin 0 -> 5537 bytes .../logs/lzwcai_mcpskills_template.log | 89 +++ .../logs/lzwcai_mcpskills_template_daily.log | 89 +++ .../logs/lzwcai_mcpskills_template_error.log | 8 + .../lzwcai_mcpskills_template/main.py | 100 ++- .../schema_converter.py | 17 + .../tools_config.json | 42 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 707 bytes .../__pycache__/api_client.cpython-312.pyc | Bin 0 -> 7295 bytes .../__pycache__/env_config.cpython-312.pyc | Bin 0 -> 1946 bytes .../__pycache__/json_helper.cpython-312.pyc | Bin 0 -> 1940 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 0 -> 5886 bytes lzwcai_mcpskills_template/pyproject.toml | 2 +- .../lzwcai_workflow_to_mcp/main.py | 29 +- .../schema_converter.py | 52 +- lzwcai_workflow_to_mcp/main.py | 4 +- 35 files changed, 2375 insertions(+), 362 deletions(-) create mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_9p04kww1pu.json create mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 create mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/main.cpython-312.pyc create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/schema_converter.cpython-312.pyc create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template.log create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/api_client.cpython-312.pyc create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/env_config.cpython-312.pyc create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/json_helper.cpython-312.pyc create mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/logger_config.cpython-312.pyc diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_9p04kww1pu.json b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_9p04kww1pu.json new file mode 100644 index 0000000..1072a01 --- /dev/null +++ b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_9p04kww1pu.json @@ -0,0 +1,483 @@ +{ + "serverName": "lzwcai_mcp_api_converter", + "description": "业务API集合", + "domainUrl": "https://erp.lzwc.cn/api", + "packageName": "lzwcai-mcp-dyntoolapi", + "version": "1.0.0", + "apiConfig": [ + { + "id": "2019327913872793602", + "enterpriseId": "1932095424144715777", + "bizSysId": "1970385781825785858", + "domainUrl": "https://erp.lzwc.cn/api", + "interfaceName": "获取内容列表(进销存-采购订单)", + "businessPrompts": "", + "returnType": "JSON", + "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", + "header": null, + "apiUrl": "/jxcPurchase/queryPageList", + "parametersFormat": "0", + "method": "POST", + "status": 1, + "version": "v1.0", + "authenticationRequired": 1, + "responseExample": null, + "crudType": "0", + "isView": 0, + "templateType": "markdown", + "viewTemplates": null, + "parameters": [ + { + "keyParam": null, + "required": 1, + "paramName": "Authorization", + "paramType": "STRING", + "paramPrompts": "注意Bearer后面有一个空格,即Bearer+空格+登录返回的token", + "defaultValue": "Bearer {{token}}", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "header", + "dataFormat": null, + "validity": null, + "sort": "1", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "page", + "paramType": "INTEGER", + "paramPrompts": "当前页码", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "2", + "tags": "0", + "example": "1" + }, + { + "keyParam": null, + "required": 0, + "paramName": "limit", + "paramType": "INTEGER", + "paramPrompts": "每页条数", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "3", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "search", + "paramType": "STRING", + "paramPrompts": "搜索关键字", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "4", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "searchList[]", + "paramType": "STRING", + "paramPrompts": "高级搜索条件数组,注意用的时候参数名去掉中括号", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "5", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "sceneId", + "paramType": "INTEGER", + "paramPrompts": "场景ID", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "6", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "type", + "paramType": "INTEGER", + "paramPrompts": "", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019327913872793602", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "7", + "tags": "0", + "example": "10-采购订单" + } + ] + }, + { + "id": "2019328946728542210", + "enterpriseId": "1932095424144715777", + "bizSysId": "1970385781825785858", + "domainUrl": "https://erp.lzwc.cn/api", + "interfaceName": "新建内容(进销存-采购订单)", + "businessPrompts": "", + "returnType": "JSON", + "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", + "header": null, + "apiUrl": "/jxcPurchase/add", + "parametersFormat": "0", + "method": "POST", + "status": 1, + "version": "v1.0", + "authenticationRequired": 1, + "responseExample": null, + "crudType": "0", + "isView": 0, + "templateType": "markdown", + "viewTemplates": null, + "parameters": [ + { + "keyParam": null, + "required": 1, + "paramName": "Authorization", + "paramType": "STRING", + "paramPrompts": "注意Bearer后面有一个空格,即Bearer+空格+登录返回的token", + "defaultValue": "Bearer {{token}}", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019328946728542210", + "requestType": "header", + "dataFormat": null, + "validity": null, + "sort": "1", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "entity", + "paramType": "ARRAY", + "paramPrompts": "基本信息,参考合同", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019328946728542210", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "2", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "examineFlowData", + "paramType": "ARRAY", + "paramPrompts": "审批流相关信息,参考合同", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019328946728542210", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "3", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "field", + "paramType": "ARRAY", + "paramPrompts": "字段相关信息,参考合同", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019328946728542210", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "4", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "productList", + "paramType": "ARRAY", + "paramPrompts": "产品列表", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019328946728542210", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "5", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "expendList", + "paramType": "ARRAY", + "paramPrompts": "扩展数据列表,字段参数参考为空", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019328946728542210", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "6", + "tags": "0", + "example": "" + } + ] + }, + { + "id": "2019329359485804545", + "enterpriseId": "1932095424144715777", + "bizSysId": "1970385781825785858", + "domainUrl": "https://erp.lzwc.cn/api", + "interfaceName": "获取内容详情(进销存-采购订单)", + "businessPrompts": "", + "returnType": "JSON", + "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", + "header": null, + "apiUrl": "/jxcPurchase/queryById/{id}", + "parametersFormat": "0", + "method": "POST", + "status": 1, + "version": "v1.0", + "authenticationRequired": 1, + "responseExample": null, + "crudType": "0", + "isView": 0, + "templateType": "markdown", + "viewTemplates": null, + "parameters": [ + { + "keyParam": null, + "required": 1, + "paramName": "Authorization", + "paramType": "STRING", + "paramPrompts": "注意Bearer后面有一个空格,即Bearer+空格+登录返回的token", + "defaultValue": "Bearer {{token}}", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019329359485804545", + "requestType": "header", + "dataFormat": null, + "validity": null, + "sort": "1", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 1, + "paramName": "id", + "paramType": "STRING", + "paramPrompts": "", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019329359485804545", + "requestType": "path", + "dataFormat": null, + "validity": null, + "sort": "2", + "tags": "0", + "example": "" + } + ] + }, + { + "id": "2019330096911556610", + "enterpriseId": "1932095424144715777", + "bizSysId": "1970385781825785858", + "domainUrl": "https://erp.lzwc.cn/api", + "interfaceName": "编辑内容(进销存-采购订单)", + "businessPrompts": "", + "returnType": "JSON", + "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", + "header": null, + "apiUrl": "/jxcPurchase/update", + "parametersFormat": "0", + "method": "POST", + "status": 1, + "version": "v1.0", + "authenticationRequired": 1, + "responseExample": null, + "crudType": "0", + "isView": 0, + "templateType": "markdown", + "viewTemplates": null, + "parameters": [ + { + "keyParam": null, + "required": 1, + "paramName": "Authorization", + "paramType": "STRING", + "paramPrompts": "注意Bearer后面有一个空格,即Bearer+空格+登录返回的token", + "defaultValue": "Bearer {{token}}", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019330096911556610", + "requestType": "header", + "dataFormat": null, + "validity": null, + "sort": "1", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "entity", + "paramType": "STRING", + "paramPrompts": "基本信息,参考合同", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019330096911556610", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "2", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "examineFlowData", + "paramType": "STRING", + "paramPrompts": "审批流相关信息,参考合同", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019330096911556610", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "3", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "field", + "paramType": "STRING", + "paramPrompts": "字段相关信息,参考合同", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019330096911556610", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "4", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "productList", + "paramType": "STRING", + "paramPrompts": "产品列表", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019330096911556610", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "5", + "tags": "0", + "example": "" + }, + { + "keyParam": null, + "required": 0, + "paramName": "expendList", + "paramType": "STRING", + "paramPrompts": "扩展数据列表,字段参数参考为空", + "defaultValue": "", + "assoKey": null, + "assoApiId": null, + "memory": 0, + "apiId": "2019330096911556610", + "requestType": "formdata", + "dataFormat": null, + "validity": null, + "sort": "6", + "tags": "0", + "example": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py index 3809e6a..b2783b7 100644 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py +++ b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py @@ -138,13 +138,32 @@ def load_api_configs(): config_key = f"business{business_uuid}" logger.info(f"租户配置变量名: {config_key}") + # 构建租户专属的配置文件路径 + memory_config_path = os.path.join(current_dir, f"api_config_{business_uuid}.json") + # 检查内存中是否已有该租户的配置 if config_key in business_configs: logger.info(f"从内存中获取租户 {business_uuid} 的配置") return business_configs[config_key] - # 内存中没有,从业务平台获取 - logger.info(f"内存中没有租户 {business_uuid} 的配置,开始从业务平台获取...") + # 内存中没有,尝试从本地文件加载 + if os.path.exists(memory_config_path): + try: + logger.info(f"尝试从本地文件加载租户配置: {memory_config_path}") + with open(memory_config_path, "r", encoding="utf-8") as f: + config = json.load(f) + logger.info(f"成功从本地文件加载租户 {business_uuid} 的配置,包含 {len(config.get('apiConfig', []))} 个API配置") + + # 存储到内存中 + business_configs[config_key] = config + logger.info(f"配置已加载到内存变量: {config_key}") + + return config + except Exception as e: + logger.warning(f"从本地文件加载配置失败: {str(e)},将从业务平台重新获取") + + # 本地文件不存在或加载失败,从业务平台获取 + logger.info(f"本地文件不存在,开始从业务平台获取租户 {business_uuid} 的配置...") try: # 从环境变量获取API ID列表 @@ -181,6 +200,15 @@ def load_api_configs(): logger.info(f"配置已存储到内存变量: {config_key}") logger.info(f"当前内存中共有 {len(business_configs)} 个租户配置") + # 保存到本地文件作为备份 + try: + logger.info(f"保存配置到本地文件: {memory_config_path}") + with open(memory_config_path, "w", encoding="utf-8") as f: + json.dump(config, f, ensure_ascii=False, indent=2) + logger.info("配置文件保存成功") + except Exception as save_error: + logger.warning(f"保存配置到本地文件失败(不影响运行): {str(save_error)}") + return config except Exception as e: @@ -639,7 +667,7 @@ def refresh_api_configs(): 这个函数实现了配置的热加载功能,支持两种模式: - 文件模式:当检测到配置文件变化时会被调用 - - 内存模式:强制重新从业务平台获取配置并更新内存 + - 内存模式:强制重新从业务平台获取配置并更新内存和本地文件 全局变量更新: - api_configs: 重新加载的API配置 @@ -658,7 +686,7 @@ def refresh_api_configs(): # 获取配置模式 config_mode = os.getenv('configMode', 'memory').lower() - # 内存模式下需要清除当前租户的缓存配置,强制重新获取 + # 内存模式下需要清除当前租户的缓存配置和本地文件,强制重新获取 if config_mode == 'memory': business_uuid = os.getenv('businessUuid') if business_uuid: @@ -666,6 +694,16 @@ def refresh_api_configs(): if config_key in business_configs: logger.info(f"清除租户 {business_uuid} 的缓存配置") del business_configs[config_key] + + # 删除本地配置文件,强制从业务平台重新获取 + current_dir = os.path.dirname(os.path.abspath(__file__)) + memory_config_path = os.path.join(current_dir, f"api_config_{business_uuid}.json") + if os.path.exists(memory_config_path): + try: + os.remove(memory_config_path) + logger.info(f"已删除本地配置文件: {memory_config_path}") + except Exception as e: + logger.warning(f"删除本地配置文件失败: {str(e)}") # 重新加载API配置 api_configs = load_api_configs() diff --git a/lzwcai_mcp_api_converter/main.py b/lzwcai_mcp_api_converter/main.py index 7b1bb25..db69f49 100644 --- a/lzwcai_mcp_api_converter/main.py +++ b/lzwcai_mcp_api_converter/main.py @@ -2,11 +2,11 @@ import os os.environ["modelId"] = "1946471611735015425" os.environ["bizSysId"] = "1970385781825785858" -os.environ["bizSysApiIds"] = "[\"1970386761072058369\",\"1970386761185304578\",\"1970386761583763457\",\"1970386761420185602\"]" -os.environ["businessUuid"] = "u9ua9ss2l8c" +os.environ["bizSysApiIds"] = "[\"2019327913872793602\",\"2019328946728542210\",\"2019329359485804545\",\"2019330096911556610\"]" +os.environ["businessUuid"] = "9p04kww1pu" os.environ["LZWCAI_CORP_MANAGER_URL"] = "http://192.168.2.236:8088" # 导入模块 from lzwcai_mcp_api_converter.src.create_mcp import run_main if __name__ == "__main__": - run_main() \ No newline at end of file + run_main() diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log index 5d46ebb..d066ca9 100644 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log +++ b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log @@ -272,3 +272,361 @@ Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skil 2025-12-31 15:00:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest 2025-12-31 15:00:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 +2026-01-28 16:37:23 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2026-01-28 16:37:23 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 16:37:24 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 16:37:24 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 16:37:24 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 "HTTP/1.1 200 " +2026-01-28 16:37:24 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': '根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': '根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': '根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}]} +2026-01-28 16:37:24 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:322] - 成功处理 3 条技能数据 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': '根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': '根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': '根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:35:19 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2026-01-28 21:35:19 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 21:35:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 21:35:20 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:35:20 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 "HTTP/1.1 200 " +2026-01-28 21:35:20 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} +2026-01-28 21:35:20 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:322] - 成功处理 3 条技能数据 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:36:46 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭 +2026-01-28 21:44:42 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2026-01-28 21:44:42 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 21:49:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:49:54 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 "HTTP/1.1 200 " +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:322] - 成功处理 3 条技能数据 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: { + "datasourceId": "37", + "businessName": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44", + "businessDescription": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细", + "sqlTemplate": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date", + "parameters": { + "type": "object", + "required": [ + "payerAccountNo", + "transactionDate" + ], + "properties": { + "payerAccountNo": { + "type": "string", + "description": "付款人账号", + "examples": [ + "667866722135" + ] + }, + "transactionDate": { + "type": "string", + "description": "交易日期,格式为YYYY-MM-DD", + "examples": [ + "2026-01-26" + ] + }, + "targetDatabaseName": { + "type": "string", + "description": "目标数据库名称", + "default": "" + } + } + }, + "testParams": { + "payerAccountNo": "667866722135", + "transactionDate": "2026-01-26", + "targetDatabaseName": "" + } +} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 37 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}, 'targetDatabaseName': {'type': 'string', 'description': '目标数据库名称', 'default': ''}}} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {'payerAccountNo': '667866722135', 'transactionDate': '2026-01-26', 'targetDatabaseName': ''} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:142] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:151] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-28 21:49:54 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:168] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:169] - test_sql_with_schema 接口返回的数据: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - HTTP状态码: 200 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - 响应数据类型: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - 响应数据内容: { + "msg": "操作成功", + "code": 200, + "data": { + "resultCount": 6, + "data": [ + { + "记录标识号": "2", + "交易类型": "往账", + "业务类型": "转账支出", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "6217852000006363041", + "收款人名称": "范红霞", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:25", + "交易货币": "CNY", + "交易金额": -787.5, + "交易后余额": 170220.05, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282055238690", + "摘要": "282055238999986690999986690", + "用途": "OBSS003756213754GIRO000000000000" + }, + { + "记录标识号": "3", + "交易类型": "往账", + "业务类型": "网上支付", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "6212263602092911221", + "收款人名称": "吴小敏", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -419.3, + "交易后余额": 169800.75, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282058995688", + "摘要": "282058995999986688999986688", + "用途": "OBSS003756219241GIRO000000000000" + }, + { + "记录标识号": "4", + "交易类型": "往账", + "业务类型": "收费", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": null, + "收款人名称": null, + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -4.5, + "交易后余额": 169796.25, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282058995686", + "摘要": "282058995999986686999986686", + "用途": "对公跨行转账汇款手续费" + }, + { + "记录标识号": "5", + "交易类型": "往账", + "业务类型": "网上支付", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "7559016582106213872732048", + "收款人名称": "深圳市腾讯计算机系统有限公司", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -0.14, + "交易后余额": 169796.11, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282056095685", + "摘要": "282056095999986685999986685", + "用途": "OBSS003756206850GIRO000000000000" + }, + { + "记录标识号": "6", + "交易类型": "往账", + "业务类型": "收费", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": null, + "收款人名称": null, + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -4.5, + "交易后余额": 169791.61, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282056095683", + "摘要": "282056095999986683999986683", + "用途": "对公跨行转账汇款手续费" + }, + { + "记录标识号": "7", + "交易类型": "往账", + "业务类型": "转账支出", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "6217852000006363041", + "收款人名称": "范红霞", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:36", + "交易货币": "CNY", + "交易金额": -67.97, + "交易后余额": 169723.64, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282098067682", + "摘要": "282098067999986682999986682", + "用途": "OBSS003756216834GIRO000000000000" + } + ], + "databaseName": "fund_daily_report_db_37", + "businessDescription": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细", + "originalTemplate": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date", + "convertedTemplate": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = #{payerAccountNo} AND transaction_date::date = #{transactionDate}::date", + "executionStatus": "success", + "businessName": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44", + "testParams": { + "payerAccountNo": "667866722135", + "transactionDate": "2026-01-26", + "targetDatabaseName": "" + }, + "errorMessage": null, + "executionTime": 7, + "datasourceId": "37", + "logId": "1497", + "executableSql": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = '667866722135' AND transaction_date::date = '2026-01-26'::date", + "datasourceName": "fund_daily_report_db_37" + } +} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应code: 200 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:175] - 响应msg: 操作成功 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应data: {'resultCount': 6, 'data': [{'记录标识号': '2', '交易类型': '往账', '业务类型': '转账支出', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '6217852000006363041', '收款人名称': '范红霞', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:25', '交易货币': 'CNY', '交易金额': -787.5, '交易后余额': 170220.05, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282055238690', '摘要': '282055238999986690999986690', '用途': 'OBSS003756213754GIRO000000000000'}, {'记录标识号': '3', '交易类型': '往账', '业务类型': '网上支付', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '6212263602092911221', '收款人名称': '吴小敏', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -419.3, '交易后余额': 169800.75, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282058995688', '摘要': '282058995999986688999986688', '用途': 'OBSS003756219241GIRO000000000000'}, {'记录标识号': '4', '交易类型': '往账', '业务类型': '收费', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': None, '收款人名称': None, '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -4.5, '交易后余额': 169796.25, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282058995686', '摘要': '282058995999986686999986686', '用途': '对公跨行转账汇款手续费'}, {'记录标识号': '5', '交易类型': '往账', '业务类型': '网上支付', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '7559016582106213872732048', '收款人名称': '深圳市腾讯计算机系统有限公司', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -0.14, '交易后余额': 169796.11, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282056095685', '摘要': '282056095999986685999986685', '用途': 'OBSS003756206850GIRO000000000000'}, {'记录标识号': '6', '交易类型': '往账', '业务类型': '收费', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': None, '收款人名称': None, '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -4.5, '交易后余额': 169791.61, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282056095683', '摘要': '282056095999986683999986683', '用途': '对公跨行转账汇款手续费'}, {'记录标识号': '7', '交易类型': '往账', '业务类型': '转账支出', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '6217852000006363041', '收款人名称': '范红霞', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:36', '交易货币': 'CNY', '交易金额': -67.97, '交易后余额': 169723.64, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282098067682', '摘要': '282098067999986682999986682', '用途': 'OBSS003756216834GIRO000000000000'}], 'databaseName': 'fund_daily_report_db_37', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'originalTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'convertedTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = #{payerAccountNo} AND transaction_date::date = #{transactionDate}::date', 'executionStatus': 'success', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'testParams': {'payerAccountNo': '667866722135', 'transactionDate': '2026-01-26', 'targetDatabaseName': ''}, 'errorMessage': None, 'executionTime': 7, 'datasourceId': '37', 'logId': '1497', 'executableSql': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = \'667866722135\' AND transaction_date::date = \'2026-01-26\'::date', 'datasourceName': 'fund_daily_report_db_37'} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - 测试SQL API调用成功 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-28 21:50:18 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98 +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: { + "datasourceId": "37", + "businessName": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98", + "businessDescription": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息", + "sqlTemplate": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "parameters": { + "type": "object", + "required": [ + "transactionDate" + ], + "properties": { + "transactionDate": { + "type": "string", + "description": "交易日期,格式为YYYY-MM-DD", + "examples": [ + "2026-01-26" + ] + }, + "targetDatabaseName": { + "type": "string", + "description": "目标数据库名称", + "default": "" + } + } + }, + "testParams": { + "transactionDate": "2026-01-02", + "targetDatabaseName": "" + } +} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 37 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}, 'targetDatabaseName': {'type': 'string', 'description': '目标数据库名称', 'default': ''}}} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {'transactionDate': '2026-01-02', 'targetDatabaseName': ''} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:142] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:151] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-28 21:50:18 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:168] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:169] - test_sql_with_schema 接口返回的数据: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - HTTP状态码: 200 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - 响应数据类型: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - 响应数据内容: { + "msg": "操作成功", + "code": 200, + "data": { + "resultCount": 0, + "data": [], + "databaseName": "fund_daily_report_db_37", + "businessDescription": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息", + "originalTemplate": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "convertedTemplate": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "executionStatus": "success", + "businessName": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98", + "testParams": { + "transactionDate": "2026-01-02", + "targetDatabaseName": "" + }, + "errorMessage": null, + "executionTime": 7, + "datasourceId": "37", + "logId": "1498", + "executableSql": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = '2026-01-02'::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = '2026-01-02'::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "datasourceName": "fund_daily_report_db_37" + } +} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应code: 200 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:175] - 响应msg: 操作成功 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应data: {'resultCount': 0, 'data': [], 'databaseName': 'fund_daily_report_db_37', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'originalTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'convertedTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'executionStatus': 'success', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'testParams': {'transactionDate': '2026-01-02', 'targetDatabaseName': ''}, 'errorMessage': None, 'executionTime': 7, 'datasourceId': '37', 'logId': '1498', 'executableSql': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = \'2026-01-02\'::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = \'2026-01-02\'::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'datasourceName': 'fund_daily_report_db_37'} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - 测试SQL API调用成功 +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log index bb8c9e3..fa790b0 100644 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log +++ b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -1,223 +1,358 @@ -2025-12-31 12:57:27 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2025-12-31 12:57:27 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:299] - ============================================================ -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:302] - ============================================================ -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16 -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:309] - ============================================================ -2025-12-31 12:57:27 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... -2025-12-31 12:57:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-31 12:57:28 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2025-12-31 12:57:28 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... -2025-12-31 12:57:28 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: -2025-12-31 12:57:28 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/ -2025-12-31 12:57:29 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/ "HTTP/1.1 404 " -2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:97] - API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ -2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:98] - 错误响应: {"timestamp":"2025-12-31T12:57:30.248+08:00","status":404,"error":"Not Found","path":"/datasource/skill/getBySkillId/"} -2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 80, in get_skill_by_id - response.raise_for_status() - File "D:\anaconda3\Lib\site-packages\httpx\_models.py", line 829, in raise_for_status - raise HTTPStatusError(message, request=request, response=self) -httpx.HTTPStatusError: Client error '404 ' for url 'http://192.168.11.24:8088/datasource/skill/getBySkillId/' -For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api - raw_result = get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id - return default_client.get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 99, in get_skill_by_id - raise Exception(error_msg) -Exception: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ -2025-12-31 12:57:29 - mcp_services - WARNING - [main.py:131] - API获取失败,降级使用本地配置: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ -2025-12-31 12:57:29 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置 -2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools - tool = generate_tool_schema_from_query(query) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 12:57:29 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-31 12:57:29 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools - tool = generate_tool_schema_from_query(query) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 12:57:54 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2025-12-31 12:57:54 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:299] - ============================================================ -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:302] - ============================================================ -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:309] - ============================================================ -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... -2025-12-31 12:57:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置 -2025-12-31 12:57:54 - mcp_services - INFO - [main.py:123] - 本地配置: 3 条 -2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools - tool = generate_tool_schema_from_query(query) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 15:00:31 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-31 15:00:31 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools - tool = generate_tool_schema_from_query(query) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query - tool_name = query['businessName'] - ~~~~~^^^^^^^^^^^^^^^^ -KeyError: 'businessName' -2025-12-31 15:00:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭 -2025-12-31 15:00:53 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2025-12-31 15:00:53 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:299] - ============================================================ -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:302] - ============================================================ -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 29 -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086 -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:309] - ============================================================ -2025-12-31 15:00:53 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... -2025-12-31 15:00:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-31 15:00:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2025-12-31 15:00:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... -2025-12-31 15:00:54 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: -2025-12-31 15:00:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/ -2025-12-31 15:00:56 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:103] - API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2025-12-31 15:00:56 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -Traceback (most recent call last): - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions - yield - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request - resp = self._pool.handle_request(req) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 216, in handle_request - raise exc from None - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 196, in handle_request - response = connection.handle_request( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 99, in handle_request - raise exc - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 76, in handle_request - stream = self._connect(request) - ^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 122, in _connect - stream = self._network_backend.connect_tcp(**kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 205, in connect_tcp - with map_exceptions(exc_map): - ^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions - raise to_exc(exc) from exc -httpcore.ConnectError: [Errno 11001] getaddrinfo failed - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 74, in get_skill_by_id - response = self.client.get( - ^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get - return self.request( - ^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request - return self.send(request, auth=auth, follow_redirects=follow_redirects) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send - response = self._send_handling_auth( - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth - response = self._send_handling_redirects( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects - response = self._send_single_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request - response = transport.handle_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request - with map_httpcore_exceptions(): - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions - raise mapped_exc(message) from exc -httpx.ConnectError: [Errno 11001] getaddrinfo failed - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api - raw_result = get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id - return default_client.get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 104, in get_skill_by_id - raise Exception(error_msg) -Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2025-12-31 15:00:56 - mcp_services - WARNING - [main.py:131] - API获取失败,降级使用本地配置: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2025-12-31 15:00:56 - mcp_services - INFO - [main.py:55] - 成功加载 0 个业务查询配置 -2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 -2025-12-31 15:00:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-31 15:00:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 +2026-01-28 16:37:23 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2026-01-28 16:37:23 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 16:37:24 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 16:37:24 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 16:37:24 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 "HTTP/1.1 200 " +2026-01-28 16:37:24 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': '根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': '根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': '根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}]} +2026-01-28 16:37:24 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:322] - 成功处理 3 条技能数据 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': '根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': '根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': '根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:35:19 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2026-01-28 21:35:19 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 21:35:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 21:35:20 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:35:20 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 "HTTP/1.1 200 " +2026-01-28 21:35:20 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} +2026-01-28 21:35:20 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:322] - 成功处理 3 条技能数据 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:36:46 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭 +2026-01-28 21:44:42 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2026-01-28 21:44:42 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 21:49:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:49:54 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 "HTTP/1.1 200 " +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.11.24:8088/datasource/skill/getBySkillId/2016416951958351874 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:322] - 成功处理 3 条技能数据 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: { + "datasourceId": "37", + "businessName": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44", + "businessDescription": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细", + "sqlTemplate": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date", + "parameters": { + "type": "object", + "required": [ + "payerAccountNo", + "transactionDate" + ], + "properties": { + "payerAccountNo": { + "type": "string", + "description": "付款人账号", + "examples": [ + "667866722135" + ] + }, + "transactionDate": { + "type": "string", + "description": "交易日期,格式为YYYY-MM-DD", + "examples": [ + "2026-01-26" + ] + }, + "targetDatabaseName": { + "type": "string", + "description": "目标数据库名称", + "default": "" + } + } + }, + "testParams": { + "payerAccountNo": "667866722135", + "transactionDate": "2026-01-26", + "targetDatabaseName": "" + } +} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 37 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}, 'targetDatabaseName': {'type': 'string', 'description': '目标数据库名称', 'default': ''}}} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {'payerAccountNo': '667866722135', 'transactionDate': '2026-01-26', 'targetDatabaseName': ''} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:142] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:151] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-28 21:49:54 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:168] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:169] - test_sql_with_schema 接口返回的数据: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - HTTP状态码: 200 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - 响应数据类型: +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - 响应数据内容: { + "msg": "操作成功", + "code": 200, + "data": { + "resultCount": 6, + "data": [ + { + "记录标识号": "2", + "交易类型": "往账", + "业务类型": "转账支出", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "6217852000006363041", + "收款人名称": "范红霞", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:25", + "交易货币": "CNY", + "交易金额": -787.5, + "交易后余额": 170220.05, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282055238690", + "摘要": "282055238999986690999986690", + "用途": "OBSS003756213754GIRO000000000000" + }, + { + "记录标识号": "3", + "交易类型": "往账", + "业务类型": "网上支付", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "6212263602092911221", + "收款人名称": "吴小敏", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -419.3, + "交易后余额": 169800.75, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282058995688", + "摘要": "282058995999986688999986688", + "用途": "OBSS003756219241GIRO000000000000" + }, + { + "记录标识号": "4", + "交易类型": "往账", + "业务类型": "收费", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": null, + "收款人名称": null, + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -4.5, + "交易后余额": 169796.25, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282058995686", + "摘要": "282058995999986686999986686", + "用途": "对公跨行转账汇款手续费" + }, + { + "记录标识号": "5", + "交易类型": "往账", + "业务类型": "网上支付", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "7559016582106213872732048", + "收款人名称": "深圳市腾讯计算机系统有限公司", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -0.14, + "交易后余额": 169796.11, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282056095685", + "摘要": "282056095999986685999986685", + "用途": "OBSS003756206850GIRO000000000000" + }, + { + "记录标识号": "6", + "交易类型": "往账", + "业务类型": "收费", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": null, + "收款人名称": null, + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:27", + "交易货币": "CNY", + "交易金额": -4.5, + "交易后余额": 169791.61, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282056095683", + "摘要": "282056095999986683999986683", + "用途": "对公跨行转账汇款手续费" + }, + { + "记录标识号": "7", + "交易类型": "往账", + "业务类型": "转账支出", + "付款人账号": "667866722135", + "付款人名称": "广东海圆圆农业科技有限公司", + "收款人账号": "6217852000006363041", + "收款人名称": "范红霞", + "交易日期": "2026-01-26T00:00:00.000+08:00", + "交易时间": "17:40:36", + "交易货币": "CNY", + "交易金额": -67.97, + "交易后余额": 169723.64, + "起息日期": "2026-01-26T00:00:00.000+08:00", + "汇率": 1, + "交易流水号": "282098067682", + "摘要": "282098067999986682999986682", + "用途": "OBSS003756216834GIRO000000000000" + } + ], + "databaseName": "fund_daily_report_db_37", + "businessDescription": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细", + "originalTemplate": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date", + "convertedTemplate": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = #{payerAccountNo} AND transaction_date::date = #{transactionDate}::date", + "executionStatus": "success", + "businessName": "anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44", + "testParams": { + "payerAccountNo": "667866722135", + "transactionDate": "2026-01-26", + "targetDatabaseName": "" + }, + "errorMessage": null, + "executionTime": 7, + "datasourceId": "37", + "logId": "1497", + "executableSql": "SELECT record_id AS \"记录标识号\", transaction_type AS \"交易类型\", business_type AS \"业务类型\", payer_account_no AS \"付款人账号\", payer_name AS \"付款人名称\", payee_account_no AS \"收款人账号\", payee_name AS \"收款人名称\", transaction_date AS \"交易日期\", transaction_time AS \"交易时间\", trade_currency AS \"交易货币\", trade_amount AS \"交易金额\", after_transaction_balance AS \"交易后余额\", value_date AS \"起息日期\", exchange_rate AS \"汇率\", transaction_ref_no AS \"交易流水号\", reference AS \"摘要\", purpose AS \"用途\" FROM funds_daily_report WHERE payer_account_no = '667866722135' AND transaction_date::date = '2026-01-26'::date", + "datasourceName": "fund_daily_report_db_37" + } +} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应code: 200 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:175] - 响应msg: 操作成功 +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应data: {'resultCount': 6, 'data': [{'记录标识号': '2', '交易类型': '往账', '业务类型': '转账支出', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '6217852000006363041', '收款人名称': '范红霞', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:25', '交易货币': 'CNY', '交易金额': -787.5, '交易后余额': 170220.05, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282055238690', '摘要': '282055238999986690999986690', '用途': 'OBSS003756213754GIRO000000000000'}, {'记录标识号': '3', '交易类型': '往账', '业务类型': '网上支付', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '6212263602092911221', '收款人名称': '吴小敏', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -419.3, '交易后余额': 169800.75, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282058995688', '摘要': '282058995999986688999986688', '用途': 'OBSS003756219241GIRO000000000000'}, {'记录标识号': '4', '交易类型': '往账', '业务类型': '收费', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': None, '收款人名称': None, '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -4.5, '交易后余额': 169796.25, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282058995686', '摘要': '282058995999986686999986686', '用途': '对公跨行转账汇款手续费'}, {'记录标识号': '5', '交易类型': '往账', '业务类型': '网上支付', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '7559016582106213872732048', '收款人名称': '深圳市腾讯计算机系统有限公司', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -0.14, '交易后余额': 169796.11, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282056095685', '摘要': '282056095999986685999986685', '用途': 'OBSS003756206850GIRO000000000000'}, {'记录标识号': '6', '交易类型': '往账', '业务类型': '收费', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': None, '收款人名称': None, '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:27', '交易货币': 'CNY', '交易金额': -4.5, '交易后余额': 169791.61, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282056095683', '摘要': '282056095999986683999986683', '用途': '对公跨行转账汇款手续费'}, {'记录标识号': '7', '交易类型': '往账', '业务类型': '转账支出', '付款人账号': '667866722135', '付款人名称': '广东海圆圆农业科技有限公司', '收款人账号': '6217852000006363041', '收款人名称': '范红霞', '交易日期': '2026-01-26T00:00:00.000+08:00', '交易时间': '17:40:36', '交易货币': 'CNY', '交易金额': -67.97, '交易后余额': 169723.64, '起息日期': '2026-01-26T00:00:00.000+08:00', '汇率': 1, '交易流水号': '282098067682', '摘要': '282098067999986682999986682', '用途': 'OBSS003756216834GIRO000000000000'}], 'databaseName': 'fund_daily_report_db_37', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'originalTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'convertedTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = #{payerAccountNo} AND transaction_date::date = #{transactionDate}::date', 'executionStatus': 'success', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'testParams': {'payerAccountNo': '667866722135', 'transactionDate': '2026-01-26', 'targetDatabaseName': ''}, 'errorMessage': None, 'executionTime': 7, 'datasourceId': '37', 'logId': '1497', 'executableSql': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = \'667866722135\' AND transaction_date::date = \'2026-01-26\'::date', 'datasourceName': 'fund_daily_report_db_37'} +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - ================================================================================ +2026-01-28 21:49:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - 测试SQL API调用成功 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-28 21:50:18 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98 +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: { + "datasourceId": "37", + "businessName": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98", + "businessDescription": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息", + "sqlTemplate": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "parameters": { + "type": "object", + "required": [ + "transactionDate" + ], + "properties": { + "transactionDate": { + "type": "string", + "description": "交易日期,格式为YYYY-MM-DD", + "examples": [ + "2026-01-26" + ] + }, + "targetDatabaseName": { + "type": "string", + "description": "目标数据库名称", + "default": "" + } + } + }, + "testParams": { + "transactionDate": "2026-01-02", + "targetDatabaseName": "" + } +} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 37 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}, 'targetDatabaseName': {'type': 'string', 'description': '目标数据库名称', 'default': ''}}} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {'transactionDate': '2026-01-02', 'targetDatabaseName': ''} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:142] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:151] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-28 21:50:18 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:168] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:169] - test_sql_with_schema 接口返回的数据: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - HTTP状态码: 200 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - 响应数据类型: +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - 响应数据内容: { + "msg": "操作成功", + "code": 200, + "data": { + "resultCount": 0, + "data": [], + "databaseName": "fund_daily_report_db_37", + "businessDescription": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息", + "originalTemplate": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "convertedTemplate": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "executionStatus": "success", + "businessName": "anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98", + "testParams": { + "transactionDate": "2026-01-02", + "targetDatabaseName": "" + }, + "errorMessage": null, + "executionTime": 7, + "datasourceId": "37", + "logId": "1498", + "executableSql": "SELECT DISTINCT payer_account_no AS \"账号\", payer_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = '2026-01-02'::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS \"账号\", payee_name AS \"账户名\" FROM funds_daily_report WHERE transaction_date::date = '2026-01-02'::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL", + "datasourceName": "fund_daily_report_db_37" + } +} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应code: 200 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:175] - 响应msg: 操作成功 +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应data: {'resultCount': 0, 'data': [], 'databaseName': 'fund_daily_report_db_37', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'originalTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'convertedTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = #{transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'executionStatus': 'success', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'testParams': {'transactionDate': '2026-01-02', 'targetDatabaseName': ''}, 'errorMessage': None, 'executionTime': 7, 'datasourceId': '37', 'logId': '1498', 'executableSql': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = \'2026-01-02\'::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = \'2026-01-02\'::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'datasourceName': 'fund_daily_report_db_37'} +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - ================================================================================ +2026-01-28 21:50:18 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - 测试SQL API调用成功 +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 new file mode 100644 index 0000000..bb8c9e3 --- /dev/null +++ b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 @@ -0,0 +1,223 @@ +2025-12-31 12:57:27 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2025-12-31 12:57:27 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:299] - ============================================================ +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:302] - ============================================================ +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16 +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:309] - ============================================================ +2025-12-31 12:57:27 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2025-12-31 12:57:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2025-12-31 12:57:28 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2025-12-31 12:57:28 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2025-12-31 12:57:28 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: +2025-12-31 12:57:28 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/ +2025-12-31 12:57:29 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/ "HTTP/1.1 404 " +2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:97] - API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ +2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:98] - 错误响应: {"timestamp":"2025-12-31T12:57:30.248+08:00","status":404,"error":"Not Found","path":"/datasource/skill/getBySkillId/"} +2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 80, in get_skill_by_id + response.raise_for_status() + File "D:\anaconda3\Lib\site-packages\httpx\_models.py", line 829, in raise_for_status + raise HTTPStatusError(message, request=request, response=self) +httpx.HTTPStatusError: Client error '404 ' for url 'http://192.168.11.24:8088/datasource/skill/getBySkillId/' +For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api + raw_result = get_skill_by_id(skill_id) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id + return default_client.get_skill_by_id(skill_id) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 99, in get_skill_by_id + raise Exception(error_msg) +Exception: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ +2025-12-31 12:57:29 - mcp_services - WARNING - [main.py:131] - API获取失败,降级使用本地配置: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ +2025-12-31 12:57:29 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置 +2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools + tool = generate_tool_schema_from_query(query) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 12:57:29 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2025-12-31 12:57:29 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools + tool = generate_tool_schema_from_query(query) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 12:57:54 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2025-12-31 12:57:54 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:299] - ============================================================ +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:302] - ============================================================ +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:309] - ============================================================ +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2025-12-31 12:57:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置 +2025-12-31 12:57:54 - mcp_services - INFO - [main.py:123] - 本地配置: 3 条 +2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools + tool = generate_tool_schema_from_query(query) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 15:00:31 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2025-12-31 15:00:31 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName' +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools + tool = generate_tool_schema_from_query(query) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query + tool_name = query['businessName'] + ~~~~~^^^^^^^^^^^^^^^^ +KeyError: 'businessName' +2025-12-31 15:00:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭 +2025-12-31 15:00:53 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs +2025-12-31 15:00:53 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:299] - ============================================================ +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:302] - ============================================================ +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 29 +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086 +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:309] - ============================================================ +2025-12-31 15:00:53 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2025-12-31 15:00:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2025-12-31 15:00:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2025-12-31 15:00:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2025-12-31 15:00:54 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: +2025-12-31 15:00:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/ +2025-12-31 15:00:56 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:103] - API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed +2025-12-31 15:00:56 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed +Traceback (most recent call last): + File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions + yield + File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request + resp = self._pool.handle_request(req) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 216, in handle_request + raise exc from None + File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 196, in handle_request + response = connection.handle_request( + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 99, in handle_request + raise exc + File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 76, in handle_request + stream = self._connect(request) + ^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 122, in _connect + stream = self._network_backend.connect_tcp(**kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 205, in connect_tcp + with map_exceptions(exc_map): + ^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ + self.gen.throw(value) + File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions + raise to_exc(exc) from exc +httpcore.ConnectError: [Errno 11001] getaddrinfo failed + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 74, in get_skill_by_id + response = self.client.get( + ^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get + return self.request( + ^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request + return self.send(request, auth=auth, follow_redirects=follow_redirects) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send + response = self._send_handling_auth( + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth + response = self._send_handling_redirects( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects + response = self._send_single_request(request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request + response = transport.handle_request(request) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request + with map_httpcore_exceptions(): + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ + self.gen.throw(value) + File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions + raise mapped_exc(message) from exc +httpx.ConnectError: [Errno 11001] getaddrinfo failed + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api + raw_result = get_skill_by_id(skill_id) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id + return default_client.get_skill_by_id(skill_id) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 104, in get_skill_by_id + raise Exception(error_msg) +Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed +2025-12-31 15:00:56 - mcp_services - WARNING - [main.py:131] - API获取失败,降级使用本地配置: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed +2025-12-31 15:00:56 - mcp_services - INFO - [main.py:55] - 成功加载 0 个业务查询配置 +2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 +2025-12-31 15:00:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2025-12-31 15:00:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log index c945346..bc39f7b 100644 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log +++ b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log @@ -239,3 +239,64 @@ Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skil 2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 2025-12-31 15:00:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 16:37:23 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': '根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': '根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': None, 'updateTime': None, 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': '根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}]} +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': '根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': '根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': '根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 16:37:24 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 21:35:19 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 21:35:20 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:36:46 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:299] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:302] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 37 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID: 2016416951958351874 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:309] - ============================================================ +2026-01-28 21:44:42 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接... +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id: 2016416951958351874 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:283] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2016419268577308673', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:53:12', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按付款人账号和交易日期查询资金日报', 'name': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'description': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 7ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payerAccountNo","transactionDate"],"properties":{"payerAccountNo":{"type":"string","description":"付款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016419112255598594', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:52:34', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按收款人账号和交易日期查询资金日报', 'name': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'description': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'visualizable': 1, 'toolPrompt': '查询成功,返回 1 行数据,执行时间: 5ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'sqlParams': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["payeeAccountNo","transactionDate"],"properties":{"payeeAccountNo":{"type":"string","description":"收款人账号","examples":["667866722135"]},"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}, {'id': '2016418808822870017', 'createBy': 'heshuangfeng', 'createTime': '2026-01-28 15:51:22', 'updateBy': 'yy8z7', 'updateTime': '2026-01-28 20:53:14', 'serviceId': '2016416951970934785', 'uniqueName': '按交易日期查询所有账号和账户名', 'name': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'description': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 5 行数据,执行时间: 6ms', 'toolType': 'sql', 'datasourceId': '37', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'sqlParams': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]}}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["transactionDate"],"properties":{"transactionDate":{"type":"string","description":"交易日期,格式为YYYY-MM-DD","examples":["2026-01-26"]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:288] - 成功获取并处理 3 条数据 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:128] - API配置: 3 条 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2016419268577308673', 'businessName': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44', 'businessDescription': 'anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44: 根据指定的付款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payer_account_no = {payerAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payerAccountNo', 'transactionDate'], 'properties': {'payerAccountNo': {'type': 'string', 'description': '付款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016419112255598594', 'businessName': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7', 'businessDescription': 'anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: anshoukuanrenzhanghaohejiaoyiriqichaxunzijinribao_1af995f7: 根据指定的收款人账号和交易日期查询资金日报表中的详细交易记录,包括交易类型、金额、双方信息等完整交易明细', 'sqlTemplate': 'SELECT record_id AS "记录标识号", transaction_type AS "交易类型", business_type AS "业务类型", payer_account_no AS "付款人账号", payer_name AS "付款人名称", payee_account_no AS "收款人账号", payee_name AS "收款人名称", transaction_date AS "交易日期", transaction_time AS "交易时间", trade_currency AS "交易货币", trade_amount AS "交易金额", after_transaction_balance AS "交易后余额", value_date AS "起息日期", exchange_rate AS "汇率", transaction_ref_no AS "交易流水号", reference AS "摘要", purpose AS "用途" FROM funds_daily_report WHERE payee_account_no = {payeeAccountNo} AND transaction_date::date = {transactionDate}::date', 'parameters': {'type': 'object', 'required': ['payeeAccountNo', 'transactionDate'], 'properties': {'payeeAccountNo': {'type': 'string', 'description': '收款人账号', 'examples': ['667866722135']}, 'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}, {'id': '2016418808822870017', 'businessName': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98', 'businessDescription': 'anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98: 根据指定的交易日期,从资金日报表中去重查询所有付款人和收款人的账号及对应的账户名,用于了解特定日期涉及的银行账户信息', 'sqlTemplate': 'SELECT DISTINCT payer_account_no AS "账号", payer_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payer_account_no IS NOT NULL AND payer_name IS NOT NULL UNION SELECT DISTINCT payee_account_no AS "账号", payee_name AS "账户名" FROM funds_daily_report WHERE transaction_date::date = {transactionDate}::date AND payee_account_no IS NOT NULL AND payee_name IS NOT NULL', 'parameters': {'type': 'object', 'required': ['transactionDate'], 'properties': {'transactionDate': {'type': 'string', 'description': '交易日期,格式为YYYY-MM-DD', 'examples': ['2026-01-26']}}}, 'datasourceId': '37'}] +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:165] - 成功生成 3 个 MCP 工具 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: anfukuanrenzhanghaohejiaoyiriqichaxunzijinribao_b38c6d44 +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-28 21:49:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: anjiaoyiriqichaxunsuoyouzhanghaohezhanghuming_0bf10c98 +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-28 21:50:18 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml index 33d9c30..583cab2 100644 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml +++ b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml @@ -7,7 +7,7 @@ name = "lzwcai-mcp-sqlexecutor" version = "0.1.6" description = "MCP server for executing business SQL queries with dynamic tool generation" readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.10" license = {text = "MIT"} authors = [ {name = "lzwcai", email = "your-email@example.com"}, @@ -17,7 +17,7 @@ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.10", ] dependencies = [ "httpx>=0.28.1", diff --git a/lzwcai_mcp_sqlexecutor/main.py b/lzwcai_mcp_sqlexecutor/main.py index e87912b..596993f 100644 --- a/lzwcai_mcp_sqlexecutor/main.py +++ b/lzwcai_mcp_sqlexecutor/main.py @@ -4,9 +4,9 @@ Runs the MCP server for SQL query execution """ import os -os.environ["databaseId"] = "12" -os.environ["skillId"] = "2013848312313335809" -os.environ["backendBaseUrl"] = "http://192.168.11.24:8088" +os.environ["databaseId"] = "162" +os.environ["skillId"] = "2008360664955854850" +os.environ["backendBaseUrl"] = "http://192.168.2.236:8088" if __name__ == "__main__": # Import and run the actual MCP server from lzwcai_mcp_sqlexecutor.main import main diff --git a/lzwcai_mcp_sqlexecutor/pyproject.toml b/lzwcai_mcp_sqlexecutor/pyproject.toml index 42f1a78..355651f 100644 --- a/lzwcai_mcp_sqlexecutor/pyproject.toml +++ b/lzwcai_mcp_sqlexecutor/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-mcp-sqlexecutor" -version = "0.1.10" +version = "0.1.11" description = "MCP server for executing business SQL queries with dynamic tool generation" readme = "README.md" requires-python = ">=3.10" diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json index bcf2a5f..2498d8b 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json @@ -4,7 +4,7 @@ "businessName": "OrderDelayWarningAnalysis", "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", "datasourceId": "19", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 50", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", "parameters": {} }, { @@ -12,7 +12,7 @@ "businessName": "WorkOrderProgressAndAnomalyNodes", "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", "datasourceId": "19", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 30 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 30 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC LIMIT 15 ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END LIMIT 15 ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC LIMIT 15 ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", "parameters": {} }, { @@ -20,7 +20,7 @@ "businessName": "SupplyChainRiskWarning", "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", "datasourceId": "19", - "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 30 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 30 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 30 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 30 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 20", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 15 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 15 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 15 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 15 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs LIMIT 15 ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 15", "parameters": {} }, { @@ -28,7 +28,7 @@ "businessName": "EfficiencyOutputLossDashboard", "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", "datasourceId": "19", - "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 30 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 30 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 30 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 30 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 30", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 15 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 15 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 15 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 15", "parameters": {} }, { @@ -36,7 +36,7 @@ "businessName": "OnePageDecisionBrief", "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", "datasourceId": "19", - "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 88 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 65 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 12 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 80 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 40 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 15 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr ; WITH current_month AS (SELECT 'current_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE)), last_month AS (SELECT 'last_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')) SELECT period, order_count, ROUND(sales_amount, 2) AS sales_amount FROM current_month UNION ALL SELECT * FROM last_month ; SELECT c.customer_name, COUNT(so.sales_order_id) AS order_count, ROUND(SUM(so.deal_amount), 2) AS total_order_amount, ROUND(SUM(so.deal_amount) * 100.0 / (SELECT SUM(deal_amount) FROM fact_sales_order), 2) AS contribution_rate, SUM(CASE WHEN so.payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_order_count, SUM(CASE WHEN so.payment_status = 'UNPAID' THEN so.deal_amount ELSE 0 END) AS receivable_amount FROM fact_sales_order so INNER JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' GROUP BY c.customer_name ORDER BY total_order_amount DESC LIMIT 10 ; SELECT p.product_category, COUNT(DISTINCT wo.work_order_id) AS work_order_count, ROUND(SUM(wo.planned_qty), 0) AS planned_qty, ROUND(SUM(wo.completed_qty), 0) AS completed_qty, ROUND(SUM(wo.completed_qty) * 100.0 / NULLIF(SUM(wo.planned_qty), 0), 1) AS completion_rate, COUNT(DISTINCT lr.worker_name) AS worker_count, ROUND(SUM(lr.duration_minutes) / 60.0, 1) AS total_work_hours FROM dim_product p LEFT JOIN fact_work_order wo ON p.product_id = wo.product_id LEFT JOIN fact_labor_report lr ON p.product_id = lr.product_id WHERE p.is_current = 't' GROUP BY p.product_category ORDER BY completed_qty DESC LIMIT 30 ; SELECT 'warning_metrics' AS category, 'unpaid_order_amount' AS metric_name, ROUND(SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END), 2) AS current_value, 200000 AS threshold, CASE WHEN SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) > 200000 THEN 'EXCEEDED' ELSE 'NORMAL' END AS status FROM fact_sales_order UNION ALL SELECT 'warning_metrics', 'defect_rate_pct', ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0), 2), 12, CASE WHEN SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0) > 12 THEN 'EXCEEDED' ELSE 'NORMAL' END FROM fact_quality_inspection UNION ALL SELECT 'warning_metrics', 'production_completion_rate', ROUND(SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0), 2), 40, CASE WHEN SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0) < 40 THEN 'LOW' ELSE 'NORMAL' END FROM fact_work_order UNION ALL SELECT 'warning_metrics', 'return_rate_pct', ROUND((SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0), 2), 12, CASE WHEN (SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0) > 12 THEN 'EXCEEDED' ELSE 'NORMAL' END ; SELECT TO_CHAR(DATE_TRUNC('month', order_date_utc::timestamp), 'YYYY-MM') AS month, COUNT(*) AS order_count, ROUND(SUM(deal_amount), 2) AS sales_amount, ROUND(AVG(deal_amount), 2) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_count FROM fact_sales_order GROUP BY DATE_TRUNC('month', order_date_utc::timestamp) ORDER BY month DESC LIMIT 12", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 88 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 65 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 12 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 80 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 40 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 15 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr LIMIT 15 ; WITH current_month AS (SELECT 'current_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE)), last_month AS (SELECT 'last_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')) SELECT period, order_count, ROUND(sales_amount, 2) AS sales_amount FROM current_month UNION ALL SELECT * FROM last_month LIMIT 15 ; SELECT c.customer_name, COUNT(so.sales_order_id) AS order_count, ROUND(SUM(so.deal_amount), 2) AS total_order_amount, ROUND(SUM(so.deal_amount) * 100.0 / (SELECT SUM(deal_amount) FROM fact_sales_order), 2) AS contribution_rate, SUM(CASE WHEN so.payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_order_count, SUM(CASE WHEN so.payment_status = 'UNPAID' THEN so.deal_amount ELSE 0 END) AS receivable_amount FROM fact_sales_order so INNER JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' GROUP BY c.customer_name ORDER BY total_order_amount DESC LIMIT 15 ; SELECT p.product_category, COUNT(DISTINCT wo.work_order_id) AS work_order_count, ROUND(SUM(wo.planned_qty), 0) AS planned_qty, ROUND(SUM(wo.completed_qty), 0) AS completed_qty, ROUND(SUM(wo.completed_qty) * 100.0 / NULLIF(SUM(wo.planned_qty), 0), 1) AS completion_rate, COUNT(DISTINCT lr.worker_name) AS worker_count, ROUND(SUM(lr.duration_minutes) / 60.0, 1) AS total_work_hours FROM dim_product p LEFT JOIN fact_work_order wo ON p.product_id = wo.product_id LEFT JOIN fact_labor_report lr ON p.product_id = lr.product_id WHERE p.is_current = 't' GROUP BY p.product_category ORDER BY completed_qty DESC LIMIT 15 ; SELECT 'warning_metrics' AS category, 'unpaid_order_amount' AS metric_name, ROUND(SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END), 2) AS current_value, 200000 AS threshold, CASE WHEN SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) > 200000 THEN 'EXCEEDED' ELSE 'NORMAL' END AS status FROM fact_sales_order UNION ALL SELECT 'warning_metrics', 'defect_rate_pct', ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0), 2), 12, CASE WHEN SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0) > 12 THEN 'EXCEEDED' ELSE 'NORMAL' END FROM fact_quality_inspection UNION ALL SELECT 'warning_metrics', 'production_completion_rate', ROUND(SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0), 2), 40, CASE WHEN SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0) < 40 THEN 'LOW' ELSE 'NORMAL' END FROM fact_work_order UNION ALL SELECT 'warning_metrics', 'return_rate_pct', ROUND((SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0), 2), 12, CASE WHEN (SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0) > 12 THEN 'EXCEEDED' ELSE 'NORMAL' END LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', order_date_utc::timestamp), 'YYYY-MM') AS month, COUNT(*) AS order_count, ROUND(SUM(deal_amount), 2) AS sales_amount, ROUND(AVG(deal_amount), 2) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_count FROM fact_sales_order GROUP BY DATE_TRUNC('month', order_date_utc::timestamp) ORDER BY month DESC LIMIT 15", "parameters": {} }, { @@ -44,7 +44,7 @@ "businessName": "MetricTrendAndTurningPointWarning", "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", "datasourceId": "19", - "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 30 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 30 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 10", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 15 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 15 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs LIMIT 15 ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 15", "parameters": {} } ] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log index c653d64..75e57ee 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log @@ -777,3 +777,146 @@ 2026-01-16 12:39:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " 2026-01-16 12:39:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 2026-01-16 12:39:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:30:19 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-29 13:30:19 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-29 13:30:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-29 13:30:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:30:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:30:25 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:30:25 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:26 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-29 13:36:26 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-29 13:36:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-29 13:36:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC LIMIT 15 ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END LIMIT 15 ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC LIMIT 15 ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "SupplyChainRiskWarning", + "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 15 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 15 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 15 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 15 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs LIMIT 15 ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "EfficiencyOutputLossDashboard", + "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 15 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 15 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 15 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:41 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 15 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 15 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs LIMIT 15 ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:41 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log index ae6a095..be7799b 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -1,109 +1,143 @@ -2026-01-16 12:39:13 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs -2026-01-16 12:39:13 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-16 12:39:13 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-16 12:39:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-16 12:39:14 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-16 12:39:14 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-16 12:39:14 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-16 12:39:14 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-16 12:39:14 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-16 12:39:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-16 12:39:17 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-16 12:39:17 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-16 12:39:17 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-16 12:39:17 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", +2026-01-29 13:30:19 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-29 13:30:19 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-29 13:30:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-29 13:30:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", "businessName": "OrderDelayWarningAnalysis", "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id WHERE EXTRACT(YEAR FROM so.order_date_utc) = 2025 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 30", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", "parameters": {}, "testParams": {} } -2026-01-16 12:39:17 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-16 12:39:17 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-16 12:39:17 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-16 12:39:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-16 12:39:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-16 12:39:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-16 12:39:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-16 12:39:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", +2026-01-29 13:30:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:30:25 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", "businessName": "WorkOrderProgressAndAnomalyNodes", "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 24 AND completion_rate < 50 OR (last_report_time IS NULL AND elapsed_hours > 24) OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 30 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 30 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 24 AND wo.completed_qty * 100.0 / NULLIF(wo.planned_qty, 0) < 30), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 20 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 80 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 50 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 5 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", "parameters": {}, "testParams": {} } -2026-01-16 12:39:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-16 12:39:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-16 12:39:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-16 12:39:26 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-16 12:39:26 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-16 12:39:26 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-16 12:39:26 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-16 12:39:26 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", +2026-01-29 13:30:25 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:26 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-29 13:36:26 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-29 13:36:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-29 13:36:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC LIMIT 15 ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END LIMIT 15 ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC LIMIT 15 ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", + "parameters": {}, + "testParams": {} +} +2026-01-29 13:36:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", "businessName": "SupplyChainRiskWarning", "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", - "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 30 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 45 AND sri.return_count > 0 THEN 'HIGH' WHEN sri.avg_delivery_days > 45 OR sri.return_count > 0 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 30 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 5 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 30 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 30 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 5 AND 10 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 5 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 2 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 20 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 5 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 3 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 10 THEN 'HIGH_RISK' WHEN return_rate > 5 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 20", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 15 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 15 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 15 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 15 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs LIMIT 15 ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 15", "parameters": {}, "testParams": {} } -2026-01-16 12:39:26 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-16 12:39:26 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-16 12:39:26 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-16 12:39:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-16 12:39:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-16 12:39:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-16 12:39:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-16 12:39:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "OnePageDecisionBrief", - "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", - "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr ; WITH current_month AS (SELECT 'current_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE)), last_month AS (SELECT 'last_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')) SELECT period, order_count, ROUND(sales_amount, 2) AS sales_amount FROM current_month UNION ALL SELECT * FROM last_month ; SELECT c.customer_name, COUNT(so.sales_order_id) AS order_count, ROUND(SUM(so.deal_amount), 2) AS total_order_amount, ROUND(SUM(so.deal_amount) * 100.0 / (SELECT SUM(deal_amount) FROM fact_sales_order), 2) AS contribution_rate, SUM(CASE WHEN so.payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_order_count, SUM(CASE WHEN so.payment_status = 'UNPAID' THEN so.deal_amount ELSE 0 END) AS receivable_amount FROM fact_sales_order so INNER JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' GROUP BY c.customer_name ORDER BY total_order_amount DESC LIMIT 10 ; SELECT p.product_category, COUNT(DISTINCT wo.work_order_id) AS work_order_count, ROUND(SUM(wo.planned_qty), 0) AS planned_qty, ROUND(SUM(wo.completed_qty), 0) AS completed_qty, ROUND(SUM(wo.completed_qty) * 100.0 / NULLIF(SUM(wo.planned_qty), 0), 1) AS completion_rate, COUNT(DISTINCT lr.worker_name) AS worker_count, ROUND(SUM(lr.duration_minutes) / 60.0, 1) AS total_work_hours FROM dim_product p LEFT JOIN fact_work_order wo ON p.product_id = wo.product_id LEFT JOIN fact_labor_report lr ON p.product_id = lr.product_id WHERE p.is_current = 't' GROUP BY p.product_category ORDER BY completed_qty DESC LIMIT 30 ; SELECT 'warning_metrics' AS category, 'unpaid_order_amount' AS metric_name, ROUND(SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END), 2) AS current_value, 50000 AS threshold, CASE WHEN SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) > 50000 THEN 'EXCEEDED' ELSE 'NORMAL' END AS status FROM fact_sales_order UNION ALL SELECT 'warning_metrics', 'defect_rate_pct', ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0), 2), 5, CASE WHEN SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0) > 5 THEN 'EXCEEDED' ELSE 'NORMAL' END FROM fact_quality_inspection UNION ALL SELECT 'warning_metrics', 'production_completion_rate', ROUND(SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0), 2), 70, CASE WHEN SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0) < 70 THEN 'LOW' ELSE 'NORMAL' END FROM fact_work_order UNION ALL SELECT 'warning_metrics', 'return_rate_pct', ROUND((SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0), 2), 5, CASE WHEN (SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0) > 5 THEN 'EXCEEDED' ELSE 'NORMAL' END ; SELECT TO_CHAR(DATE_TRUNC('month', order_date_utc::timestamp), 'YYYY-MM') AS month, COUNT(*) AS order_count, ROUND(SUM(deal_amount), 2) AS sales_amount, ROUND(AVG(deal_amount), 2) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_count FROM fact_sales_order GROUP BY DATE_TRUNC('month', order_date_utc::timestamp) ORDER BY month DESC LIMIT 12", - "parameters": {}, - "testParams": {} -} -2026-01-16 12:39:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-16 12:39:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-16 12:39:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-16 12:39:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-16 12:39:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-16 12:39:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-16 12:39:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-16 12:39:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", +2026-01-29 13:36:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", "businessName": "EfficiencyOutputLossDashboard", "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", - "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 30 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 30 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 30 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 10 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 5 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 30 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 30", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 15 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 15 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 15 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 15", "parameters": {}, "testParams": {} } -2026-01-16 12:39:33 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-16 12:39:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-16 12:39:33 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-16 12:39:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-16 12:39:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-16 12:39:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-16 12:39:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-16 12:39:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", +2026-01-29 13:36:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:41 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "57", "businessName": "MetricTrendAndTurningPointWarning", "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", - "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 30 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.1 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.9 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.1 THEN 'RISING' WHEN total_output < prev_output * 0.9 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.2 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.8 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 30 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.05 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.95 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.2 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.05 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.95 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.2 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.1 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.9 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 8 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.3 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 10", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 15 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 15 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs LIMIT 15 ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 15", "parameters": {}, "testParams": {} } -2026-01-16 12:39:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-16 12:39:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-16 12:39:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:41 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 new file mode 100644 index 0000000..ae6a095 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 @@ -0,0 +1,109 @@ +2026-01-16 12:39:13 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-16 12:39:13 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-16 12:39:13 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-16 12:39:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-16 12:39:14 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-16 12:39:14 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-16 12:39:14 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-16 12:39:14 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-16 12:39:14 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-16 12:39:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:39:17 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-16 12:39:17 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:39:17 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:39:17 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id WHERE EXTRACT(YEAR FROM so.order_date_utc) = 2025 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 30", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:39:17 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-16 12:39:17 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:39:17 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:39:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:39:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-16 12:39:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:39:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:39:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 24 AND completion_rate < 50 OR (last_report_time IS NULL AND elapsed_hours > 24) OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 30 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 30 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 24 AND wo.completed_qty * 100.0 / NULLIF(wo.planned_qty, 0) < 30), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 20 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 80 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 50 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 5 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:39:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-16 12:39:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:39:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:39:26 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:39:26 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-16 12:39:26 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:39:26 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:39:26 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplyChainRiskWarning", + "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 30 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 45 AND sri.return_count > 0 THEN 'HIGH' WHEN sri.avg_delivery_days > 45 OR sri.return_count > 0 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 30 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 5 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 30 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 30 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 5 AND 10 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 5 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 2 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 20 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 5 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 3 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 10 THEN 'HIGH_RISK' WHEN return_rate > 5 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 20", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:39:26 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-16 12:39:26 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:39:26 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:39:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:39:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-16 12:39:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:39:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:39:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr ; WITH current_month AS (SELECT 'current_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE)), last_month AS (SELECT 'last_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')) SELECT period, order_count, ROUND(sales_amount, 2) AS sales_amount FROM current_month UNION ALL SELECT * FROM last_month ; SELECT c.customer_name, COUNT(so.sales_order_id) AS order_count, ROUND(SUM(so.deal_amount), 2) AS total_order_amount, ROUND(SUM(so.deal_amount) * 100.0 / (SELECT SUM(deal_amount) FROM fact_sales_order), 2) AS contribution_rate, SUM(CASE WHEN so.payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_order_count, SUM(CASE WHEN so.payment_status = 'UNPAID' THEN so.deal_amount ELSE 0 END) AS receivable_amount FROM fact_sales_order so INNER JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' GROUP BY c.customer_name ORDER BY total_order_amount DESC LIMIT 10 ; SELECT p.product_category, COUNT(DISTINCT wo.work_order_id) AS work_order_count, ROUND(SUM(wo.planned_qty), 0) AS planned_qty, ROUND(SUM(wo.completed_qty), 0) AS completed_qty, ROUND(SUM(wo.completed_qty) * 100.0 / NULLIF(SUM(wo.planned_qty), 0), 1) AS completion_rate, COUNT(DISTINCT lr.worker_name) AS worker_count, ROUND(SUM(lr.duration_minutes) / 60.0, 1) AS total_work_hours FROM dim_product p LEFT JOIN fact_work_order wo ON p.product_id = wo.product_id LEFT JOIN fact_labor_report lr ON p.product_id = lr.product_id WHERE p.is_current = 't' GROUP BY p.product_category ORDER BY completed_qty DESC LIMIT 30 ; SELECT 'warning_metrics' AS category, 'unpaid_order_amount' AS metric_name, ROUND(SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END), 2) AS current_value, 50000 AS threshold, CASE WHEN SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) > 50000 THEN 'EXCEEDED' ELSE 'NORMAL' END AS status FROM fact_sales_order UNION ALL SELECT 'warning_metrics', 'defect_rate_pct', ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0), 2), 5, CASE WHEN SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0) > 5 THEN 'EXCEEDED' ELSE 'NORMAL' END FROM fact_quality_inspection UNION ALL SELECT 'warning_metrics', 'production_completion_rate', ROUND(SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0), 2), 70, CASE WHEN SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0) < 70 THEN 'LOW' ELSE 'NORMAL' END FROM fact_work_order UNION ALL SELECT 'warning_metrics', 'return_rate_pct', ROUND((SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0), 2), 5, CASE WHEN (SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0) > 5 THEN 'EXCEEDED' ELSE 'NORMAL' END ; SELECT TO_CHAR(DATE_TRUNC('month', order_date_utc::timestamp), 'YYYY-MM') AS month, COUNT(*) AS order_count, ROUND(SUM(deal_amount), 2) AS sales_amount, ROUND(AVG(deal_amount), 2) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_count FROM fact_sales_order GROUP BY DATE_TRUNC('month', order_date_utc::timestamp) ORDER BY month DESC LIMIT 12", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:39:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-16 12:39:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:39:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:39:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:39:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-16 12:39:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:39:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:39:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "EfficiencyOutputLossDashboard", + "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 30 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 30 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 30 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 10 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 5 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 30 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 30", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:39:33 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-16 12:39:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:39:33 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:39:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:39:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-16 12:39:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:39:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:39:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 30 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.1 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.9 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.1 THEN 'RISING' WHEN total_output < prev_output * 0.9 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.2 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.8 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 30 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.05 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.95 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.2 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.05 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.95 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.2 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.1 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.9 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 8 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.3 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 10", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:39:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-16 12:39:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:39:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log index 116f876..0e5d22a 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log @@ -240,3 +240,56 @@ 2026-01-16 12:39:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning 2026-01-16 12:39:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... 2026-01-16 12:39:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml b/lzwcai_mcpskills_mfg_data_agent/pyproject.toml index a29b100..2b6d240 100644 --- a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml +++ b/lzwcai_mcpskills_mfg_data_agent/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-mcpskills-mfg-data-agent" -version = "0.1.4" +version = "0.1.6" description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" readme = "README.md" requires-python = ">=3.10" diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json index fc1def5..b1c6f8e 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json @@ -4,7 +4,7 @@ "businessName": "SupplierEvaluationAndSmartReplenishment", "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", "datasourceId": "19", - "sqlTemplate": "WITH mat_out AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM mat_out GROUP BY product_id ) SELECT p.product_code, p.product_name, p.uom_code, s.avg_daily_consume, s.std_daily_consume FROM stats s JOIN dim_product p ON p.product_id = s.product_id AND p.is_current = TRUE WHERE p.product_type = '标准产品' ; WITH lead_time AS ( SELECT po.supplier_id, (DATE(pr.doc_date_utc) - DATE(po.doc_date_utc)) AS lead_days, CASE WHEN po.delivery_date_utc IS NOT NULL AND pr.doc_date_utc IS NOT NULL THEN CASE WHEN DATE(pr.doc_date_utc) <= DATE(po.delivery_date_utc) THEN 1 ELSE 0 END ELSE NULL END AS is_ontime FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL ), quality AS ( SELECT supplier_id, SUM(COALESCE(pass_qty, 0)) AS pass_qty, SUM(COALESCE(fail_qty, 0)) AS fail_qty FROM fact_quality_inspection WHERE qc_type = 'INCOMING' AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY supplier_id ), agg AS ( SELECT supplier_id, AVG(lead_days) AS avg_lead_days, STDDEV_POP(lead_days) AS std_lead_days, AVG(is_ontime) AS ontime_rate FROM lead_time WHERE lead_days IS NOT NULL AND lead_days >= 0 GROUP BY supplier_id ) SELECT s.supplier_id, s.supplier_name, a.avg_lead_days, a.std_lead_days, a.ontime_rate, CASE WHEN (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) > 0 THEN COALESCE(q.pass_qty, 0) / (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) ELSE NULL END AS incoming_pass_rate FROM agg a LEFT JOIN dim_supplier s ON s.supplier_id = a.supplier_id AND s.is_current = TRUE LEFT JOIN quality q ON q.supplier_id = a.supplier_id ORDER BY s.supplier_name LIMIT 30 ; WITH params AS ( SELECT 1.65::numeric AS z, 7::int AS review_days ), consume_daily AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), consume_stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM consume_daily GROUP BY product_id ), lead_time AS ( SELECT pr.supplier_id, AVG((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS avg_lead_days, STDDEV_POP((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS std_lead_days FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL GROUP BY pr.supplier_id ), latest_onhand_row AS ( SELECT m.product_id, m.warehouse_id, m.onhand_qty_after, ROW_NUMBER() OVER (PARTITION BY m.product_id, m.warehouse_id ORDER BY m.event_time_utc DESC, m.inventory_movement_id DESC) AS rn FROM fact_inventory_movement m WHERE m.product_id IS NOT NULL ), onhand AS ( SELECT product_id, SUM(COALESCE(onhand_qty_after, 0)) AS onhand_qty FROM latest_onhand_row WHERE rn = 1 GROUP BY product_id ), product_supplier AS ( SELECT p.product_id, p.product_code, p.product_name, b.supplier_name FROM dim_product p LEFT JOIN dim_bom b ON b.product_code = p.product_code AND b.is_current = TRUE WHERE p.is_current = TRUE AND p.product_type = '标准产品' ), supplier_id_map AS ( SELECT supplier_id, supplier_name FROM dim_supplier WHERE is_current = TRUE ), merged AS ( SELECT ps.product_id, ps.product_code, ps.product_name, sm.supplier_id, COALESCE(cs.avg_daily_consume, 0) AS avg_daily_consume, COALESCE(cs.std_daily_consume, 0) AS std_daily_consume, COALESCE(lt.avg_lead_days, 0) AS avg_lead_days, COALESCE(lt.std_lead_days, 0) AS std_lead_days, COALESCE(oh.onhand_qty, 0) AS onhand_qty FROM product_supplier ps LEFT JOIN supplier_id_map sm ON sm.supplier_name IS NOT DISTINCT FROM ps.supplier_name LEFT JOIN consume_stats cs ON cs.product_id = ps.product_id LEFT JOIN lead_time lt ON lt.supplier_id = sm.supplier_id LEFT JOIN onhand oh ON oh.product_id = ps.product_id ) SELECT product_code, product_name, onhand_qty, avg_daily_consume, std_daily_consume, avg_lead_days, (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS safety_stock_qty, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS reorder_point_qty, GREATEST( 0, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) - onhand_qty ) AS suggest_order_qty, supplier_id FROM merged CROSS JOIN params ORDER BY suggest_order_qty DESC, product_code LIMIT 30 ;", + "sqlTemplate": "WITH mat_out AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM mat_out GROUP BY product_id ) SELECT p.product_code, p.product_name, p.uom_code, s.avg_daily_consume, s.std_daily_consume FROM stats s JOIN dim_product p ON p.product_id = s.product_id AND p.is_current = TRUE WHERE p.product_type = '标准产品' LIMIT 15 ; WITH lead_time AS ( SELECT po.supplier_id, (DATE(pr.doc_date_utc) - DATE(po.doc_date_utc)) AS lead_days, CASE WHEN po.delivery_date_utc IS NOT NULL AND pr.doc_date_utc IS NOT NULL THEN CASE WHEN DATE(pr.doc_date_utc) <= DATE(po.delivery_date_utc) THEN 1 ELSE 0 END ELSE NULL END AS is_ontime FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL ), quality AS ( SELECT supplier_id, SUM(COALESCE(pass_qty, 0)) AS pass_qty, SUM(COALESCE(fail_qty, 0)) AS fail_qty FROM fact_quality_inspection WHERE qc_type = 'INCOMING' AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY supplier_id ), agg AS ( SELECT supplier_id, AVG(lead_days) AS avg_lead_days, STDDEV_POP(lead_days) AS std_lead_days, AVG(is_ontime) AS ontime_rate FROM lead_time WHERE lead_days IS NOT NULL AND lead_days >= 0 GROUP BY supplier_id ) SELECT s.supplier_id, s.supplier_name, a.avg_lead_days, a.std_lead_days, a.ontime_rate, CASE WHEN (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) > 0 THEN COALESCE(q.pass_qty, 0) / (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) ELSE NULL END AS incoming_pass_rate FROM agg a LEFT JOIN dim_supplier s ON s.supplier_id = a.supplier_id AND s.is_current = TRUE LEFT JOIN quality q ON q.supplier_id = a.supplier_id ORDER BY s.supplier_name LIMIT 15 ; WITH params AS ( SELECT 1.65::numeric AS z, 7::int AS review_days ), consume_daily AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), consume_stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM consume_daily GROUP BY product_id ), lead_time AS ( SELECT pr.supplier_id, AVG((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS avg_lead_days, STDDEV_POP((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS std_lead_days FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL GROUP BY pr.supplier_id ), latest_onhand_row AS ( SELECT m.product_id, m.warehouse_id, m.onhand_qty_after, ROW_NUMBER() OVER (PARTITION BY m.product_id, m.warehouse_id ORDER BY m.event_time_utc DESC, m.inventory_movement_id DESC) AS rn FROM fact_inventory_movement m WHERE m.product_id IS NOT NULL ), onhand AS ( SELECT product_id, SUM(COALESCE(onhand_qty_after, 0)) AS onhand_qty FROM latest_onhand_row WHERE rn = 1 GROUP BY product_id ), product_supplier AS ( SELECT p.product_id, p.product_code, p.product_name, b.supplier_name FROM dim_product p LEFT JOIN dim_bom b ON b.product_code = p.product_code AND b.is_current = TRUE WHERE p.is_current = TRUE AND p.product_type = '标准产品' ), supplier_id_map AS ( SELECT supplier_id, supplier_name FROM dim_supplier WHERE is_current = TRUE ), merged AS ( SELECT ps.product_id, ps.product_code, ps.product_name, sm.supplier_id, COALESCE(cs.avg_daily_consume, 0) AS avg_daily_consume, COALESCE(cs.std_daily_consume, 0) AS std_daily_consume, COALESCE(lt.avg_lead_days, 0) AS avg_lead_days, COALESCE(lt.std_lead_days, 0) AS std_lead_days, COALESCE(oh.onhand_qty, 0) AS onhand_qty FROM product_supplier ps LEFT JOIN supplier_id_map sm ON sm.supplier_name IS NOT DISTINCT FROM ps.supplier_name LEFT JOIN consume_stats cs ON cs.product_id = ps.product_id LEFT JOIN lead_time lt ON lt.supplier_id = sm.supplier_id LEFT JOIN onhand oh ON oh.product_id = ps.product_id ) SELECT product_code, product_name, onhand_qty, avg_daily_consume, std_daily_consume, avg_lead_days, (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS safety_stock_qty, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS reorder_point_qty, GREATEST( 0, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) - onhand_qty ) AS suggest_order_qty, supplier_id FROM merged CROSS JOIN params ORDER BY suggest_order_qty DESC, product_code LIMIT 15", "parameters": {} }, { @@ -12,7 +12,7 @@ "businessName": "SalesBIIntelligentAnalyticsPlatform", "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", "datasourceId": "19", - "sqlTemplate": "SELECT DATE(so.order_date_utc) AS order_date, so.customer_id, c.customer_name, c.customer_level, c.industry, c.address, so.sales_person_id, p.person_name AS sales_person_name, COUNT(DISTINCT so.sales_order_id) AS order_count, SUM(COALESCE(so.deal_amount, 0)) AS order_amount FROM fact_sales_order so LEFT JOIN dim_customer c ON c.customer_id = so.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = so.sales_person_id AND p.is_current = TRUE GROUP BY DATE(so.order_date_utc), so.customer_id, c.customer_name, c.customer_level, c.industry, c.address, so.sales_person_id, p.person_name LIMIT 30 ; WITH ship AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, customer_id, sales_person_id, SUM(COALESCE(amount, 0)) AS ship_amount FROM fact_sales_shipment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM'), customer_id, sales_person_id ), ret AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, customer_id, sales_person_id, SUM(COALESCE(amount, 0)) AS return_amount FROM fact_sales_return WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM'), customer_id, sales_person_id ), keys AS ( SELECT ym, customer_id, sales_person_id FROM ship UNION SELECT ym, customer_id, sales_person_id FROM ret ) SELECT k.ym, k.customer_id, c.customer_name, k.sales_person_id, p.person_name AS sales_person_name, COALESCE(s.ship_amount, 0) AS ship_amount, COALESCE(r.return_amount, 0) AS return_amount, (COALESCE(s.ship_amount, 0) - COALESCE(r.return_amount, 0)) AS net_amount FROM keys k LEFT JOIN ship s ON s.ym = k.ym AND s.customer_id IS NOT DISTINCT FROM k.customer_id AND s.sales_person_id IS NOT DISTINCT FROM k.sales_person_id LEFT JOIN ret r ON r.ym = k.ym AND r.customer_id IS NOT DISTINCT FROM k.customer_id AND r.sales_person_id IS NOT DISTINCT FROM k.sales_person_id LEFT JOIN dim_customer c ON c.customer_id = k.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = k.sales_person_id AND p.is_current = TRUE LIMIT 30 ; WITH orders AS ( SELECT so.sales_person_id, so.customer_id, COUNT(DISTINCT so.sales_order_id) AS order_cnt FROM fact_sales_order so WHERE so.order_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.sales_person_id, so.customer_id ), rate AS ( SELECT sales_person_id, COUNT(DISTINCT customer_id) AS customer_cnt, SUM(CASE WHEN order_cnt >= 2 THEN 1 ELSE 0 END) AS repeat_customer_cnt, CASE WHEN COUNT(DISTINCT customer_id) > 0 THEN SUM(CASE WHEN order_cnt >= 2 THEN 1 ELSE 0 END) / COUNT(DISTINCT customer_id) ELSE NULL END AS repurchase_rate FROM orders GROUP BY sales_person_id ), avg_rate AS ( SELECT AVG(repurchase_rate) AS avg_repurchase_rate FROM rate WHERE repurchase_rate IS NOT NULL ) SELECT r.sales_person_id, p.person_name AS sales_person_name, r.customer_cnt, r.repeat_customer_cnt, r.repurchase_rate, a.avg_repurchase_rate FROM rate r CROSS JOIN avg_rate a LEFT JOIN dim_person p ON p.person_id = r.sales_person_id AND p.is_current = TRUE WHERE r.repurchase_rate IS NOT NULL AND r.repurchase_rate < a.avg_repurchase_rate ORDER BY (a.avg_repurchase_rate - r.repurchase_rate) DESC LIMIT 30 ; WITH revenue AS ( SELECT so.customer_id, SUM(COALESCE(so.deal_amount, 0)) AS revenue_amount FROM fact_sales_order so WHERE so.order_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.customer_id ), cost AS ( SELECT so.customer_id, SUM(COALESCE(ABS(m.movement_qty) * m.unit_price, 0)) AS cost_amount FROM fact_inventory_movement m JOIN fact_sales_order so ON so.sales_order_id = m.sales_order_id WHERE m.sales_order_id IS NOT NULL AND COALESCE(m.movement_qty, 0) < 0 AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.customer_id ) SELECT r.customer_id, c.customer_name, r.revenue_amount, COALESCE(k.cost_amount, 0) AS cost_amount, (r.revenue_amount - COALESCE(k.cost_amount, 0)) AS gross_profit_amount, CASE WHEN r.revenue_amount > 0 THEN (r.revenue_amount - COALESCE(k.cost_amount, 0)) / r.revenue_amount ELSE NULL END AS gross_margin_rate FROM revenue r LEFT JOIN cost k ON k.customer_id = r.customer_id LEFT JOIN dim_customer c ON c.customer_id = r.customer_id AND c.is_current = TRUE ORDER BY gross_profit_amount DESC LIMIT 10 ;", + "sqlTemplate": "SELECT DATE(so.order_date_utc) AS order_date, so.customer_id, c.customer_name, c.customer_level, c.industry, c.address, so.sales_person_id, p.person_name AS sales_person_name, COUNT(DISTINCT so.sales_order_id) AS order_count, SUM(COALESCE(so.deal_amount, 0)) AS order_amount FROM fact_sales_order so LEFT JOIN dim_customer c ON c.customer_id = so.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = so.sales_person_id AND p.is_current = TRUE GROUP BY DATE(so.order_date_utc), so.customer_id, c.customer_name, c.customer_level, c.industry, c.address, so.sales_person_id, p.person_name LIMIT 15 ; WITH ship AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, customer_id, sales_person_id, SUM(COALESCE(amount, 0)) AS ship_amount FROM fact_sales_shipment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM'), customer_id, sales_person_id ), ret AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, customer_id, sales_person_id, SUM(COALESCE(amount, 0)) AS return_amount FROM fact_sales_return WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM'), customer_id, sales_person_id ), keys AS ( SELECT ym, customer_id, sales_person_id FROM ship UNION SELECT ym, customer_id, sales_person_id FROM ret ) SELECT k.ym, k.customer_id, c.customer_name, k.sales_person_id, p.person_name AS sales_person_name, COALESCE(s.ship_amount, 0) AS ship_amount, COALESCE(r.return_amount, 0) AS return_amount, (COALESCE(s.ship_amount, 0) - COALESCE(r.return_amount, 0)) AS net_amount FROM keys k LEFT JOIN ship s ON s.ym = k.ym AND s.customer_id IS NOT DISTINCT FROM k.customer_id AND s.sales_person_id IS NOT DISTINCT FROM k.sales_person_id LEFT JOIN ret r ON r.ym = k.ym AND r.customer_id IS NOT DISTINCT FROM k.customer_id AND r.sales_person_id IS NOT DISTINCT FROM k.sales_person_id LEFT JOIN dim_customer c ON c.customer_id = k.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = k.sales_person_id AND p.is_current = TRUE LIMIT 15 ; WITH orders AS ( SELECT so.sales_person_id, so.customer_id, COUNT(DISTINCT so.sales_order_id) AS order_cnt FROM fact_sales_order so WHERE so.order_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.sales_person_id, so.customer_id ), rate AS ( SELECT sales_person_id, COUNT(DISTINCT customer_id) AS customer_cnt, SUM(CASE WHEN order_cnt >= 2 THEN 1 ELSE 0 END) AS repeat_customer_cnt, CASE WHEN COUNT(DISTINCT customer_id) > 0 THEN SUM(CASE WHEN order_cnt >= 2 THEN 1 ELSE 0 END) / COUNT(DISTINCT customer_id) ELSE NULL END AS repurchase_rate FROM orders GROUP BY sales_person_id ), avg_rate AS ( SELECT AVG(repurchase_rate) AS avg_repurchase_rate FROM rate WHERE repurchase_rate IS NOT NULL ) SELECT r.sales_person_id, p.person_name AS sales_person_name, r.customer_cnt, r.repeat_customer_cnt, r.repurchase_rate, a.avg_repurchase_rate FROM rate r CROSS JOIN avg_rate a LEFT JOIN dim_person p ON p.person_id = r.sales_person_id AND p.is_current = TRUE WHERE r.repurchase_rate IS NOT NULL AND r.repurchase_rate < a.avg_repurchase_rate ORDER BY (a.avg_repurchase_rate - r.repurchase_rate) DESC LIMIT 15 ; WITH revenue AS ( SELECT so.customer_id, SUM(COALESCE(so.deal_amount, 0)) AS revenue_amount FROM fact_sales_order so WHERE so.order_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.customer_id ), cost AS ( SELECT so.customer_id, SUM(COALESCE(ABS(m.movement_qty) * m.unit_price, 0)) AS cost_amount FROM fact_inventory_movement m JOIN fact_sales_order so ON so.sales_order_id = m.sales_order_id WHERE m.sales_order_id IS NOT NULL AND COALESCE(m.movement_qty, 0) < 0 AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.customer_id ) SELECT r.customer_id, c.customer_name, r.revenue_amount, COALESCE(k.cost_amount, 0) AS cost_amount, (r.revenue_amount - COALESCE(k.cost_amount, 0)) AS gross_profit_amount, CASE WHEN r.revenue_amount > 0 THEN (r.revenue_amount - COALESCE(k.cost_amount, 0)) / r.revenue_amount ELSE NULL END AS gross_margin_rate FROM revenue r LEFT JOIN cost k ON k.customer_id = r.customer_id LEFT JOIN dim_customer c ON c.customer_id = r.customer_id AND c.is_current = TRUE ORDER BY gross_profit_amount DESC LIMIT 15", "parameters": {} }, { @@ -20,7 +20,7 @@ "businessName": "SalesPerformanceIntelligentStatistics", "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", "datasourceId": "19", - "sqlTemplate": "WITH deal AS ( SELECT so.sales_order_id AS deal_id, so.contract_id, so.customer_id, so.sales_person_id, so.deal_amount, so.order_date_utc AS deal_time_utc FROM fact_sales_order so ), role_first AS ( SELECT d.deal_id, COALESCE(dc.owner_id, d.sales_person_id) AS person_id, 'FIRST_TOUCH' AS role, 0.30 AS role_weight FROM deal d LEFT JOIN dim_contract dc ON dc.contract_id = d.contract_id AND dc.is_current = TRUE ), role_follow AS ( SELECT d.deal_id, d.sales_person_id AS person_id, 'FOLLOW_UP' AS role, 0.50 AS role_weight FROM deal d ), collab_people AS ( SELECT DISTINCT s.contract_id, s.operator_id AS person_id FROM fact_sales_shipment s WHERE s.operator_id IS NOT NULL UNION DISTINCT SELECT DISTINCT i.contract_id, i.handler_id AS person_id FROM fact_invoice i WHERE i.handler_id IS NOT NULL UNION DISTINCT SELECT DISTINCT w.contract_id, w.handler_id AS person_id FROM fact_write_off w WHERE w.handler_id IS NOT NULL ), role_collab AS ( SELECT d.deal_id, cp.person_id, 'COLLAB' AS role, 0.20 AS role_weight FROM deal d JOIN collab_people cp ON cp.contract_id = d.contract_id ), roles_union AS ( SELECT * FROM role_first UNION ALL SELECT * FROM role_follow UNION ALL SELECT * FROM role_collab ), collab_cnt AS ( SELECT deal_id, COUNT(DISTINCT person_id) AS collab_person_cnt FROM role_collab GROUP BY deal_id ), normalized AS ( SELECT ru.deal_id, ru.person_id, ru.role, CASE WHEN ru.role = 'COLLAB' THEN ru.role_weight / NULLIF(cc.collab_person_cnt, 0) ELSE ru.role_weight END AS contribution_pct FROM roles_union ru LEFT JOIN collab_cnt cc ON cc.deal_id = ru.deal_id ) SELECT n.deal_id, n.person_id, n.role, n.contribution_pct FROM normalized n LIMIT 30 ; WITH deal AS ( SELECT so.sales_order_id AS deal_id, so.contract_id, so.customer_id, so.deal_amount, so.order_date_utc AS deal_time_utc FROM fact_sales_order so ), contrib AS ( WITH role_first AS ( SELECT d.deal_id, COALESCE(dc.owner_id, so.sales_person_id) AS person_id, 'FIRST_TOUCH' AS role, 0.30 AS role_weight FROM deal d JOIN fact_sales_order so ON so.sales_order_id = d.deal_id LEFT JOIN dim_contract dc ON dc.contract_id = d.contract_id AND dc.is_current = TRUE ), role_follow AS ( SELECT d.deal_id, so.sales_person_id AS person_id, 'FOLLOW_UP' AS role, 0.50 AS role_weight FROM deal d JOIN fact_sales_order so ON so.sales_order_id = d.deal_id ), collab_people AS ( SELECT DISTINCT s.contract_id, s.operator_id AS person_id FROM fact_sales_shipment s WHERE s.operator_id IS NOT NULL UNION DISTINCT SELECT DISTINCT i.contract_id, i.handler_id AS person_id FROM fact_invoice i WHERE i.handler_id IS NOT NULL UNION DISTINCT SELECT DISTINCT w.contract_id, w.handler_id AS person_id FROM fact_write_off w WHERE w.handler_id IS NOT NULL ), role_collab AS ( SELECT d.deal_id, cp.person_id, 'COLLAB' AS role, 0.20 AS role_weight FROM deal d JOIN collab_people cp ON cp.contract_id = d.contract_id ), roles_union AS ( SELECT * FROM role_first UNION ALL SELECT * FROM role_follow UNION ALL SELECT * FROM role_collab ), collab_cnt AS ( SELECT deal_id, COUNT(DISTINCT person_id) AS collab_person_cnt FROM role_collab GROUP BY deal_id ) SELECT ru.deal_id, ru.person_id, ru.role, CASE WHEN ru.role = 'COLLAB' THEN ru.role_weight / NULLIF(cc.collab_person_cnt, 0) ELSE ru.role_weight END AS contribution_pct FROM roles_union ru LEFT JOIN collab_cnt cc ON cc.deal_id = ru.deal_id ) SELECT d.deal_id, d.deal_time_utc, d.contract_id, d.customer_id, c.customer_name, co.person_id, p.person_name, co.role, co.contribution_pct, d.deal_amount, (d.deal_amount * 0.05 * co.contribution_pct) AS recommend_commission_amount FROM deal d JOIN contrib co ON co.deal_id = d.deal_id LEFT JOIN dim_customer c ON c.customer_id = d.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = co.person_id AND p.is_current = TRUE ORDER BY d.deal_time_utc DESC, d.deal_id, co.contribution_pct DESC LIMIT 30 ;", + "sqlTemplate": "WITH deal AS ( SELECT so.sales_order_id AS deal_id, so.contract_id, so.customer_id, so.sales_person_id, so.deal_amount, so.order_date_utc AS deal_time_utc FROM fact_sales_order so ), role_first AS ( SELECT d.deal_id, COALESCE(dc.owner_id, d.sales_person_id) AS person_id, 'FIRST_TOUCH' AS role, 0.30 AS role_weight FROM deal d LEFT JOIN dim_contract dc ON dc.contract_id = d.contract_id AND dc.is_current = TRUE ), role_follow AS ( SELECT d.deal_id, d.sales_person_id AS person_id, 'FOLLOW_UP' AS role, 0.50 AS role_weight FROM deal d ), collab_people AS ( SELECT DISTINCT s.contract_id, s.operator_id AS person_id FROM fact_sales_shipment s WHERE s.operator_id IS NOT NULL UNION DISTINCT SELECT DISTINCT i.contract_id, i.handler_id AS person_id FROM fact_invoice i WHERE i.handler_id IS NOT NULL UNION DISTINCT SELECT DISTINCT w.contract_id, w.handler_id AS person_id FROM fact_write_off w WHERE w.handler_id IS NOT NULL ), role_collab AS ( SELECT d.deal_id, cp.person_id, 'COLLAB' AS role, 0.20 AS role_weight FROM deal d JOIN collab_people cp ON cp.contract_id = d.contract_id ), roles_union AS ( SELECT * FROM role_first UNION ALL SELECT * FROM role_follow UNION ALL SELECT * FROM role_collab ), collab_cnt AS ( SELECT deal_id, COUNT(DISTINCT person_id) AS collab_person_cnt FROM role_collab GROUP BY deal_id ), normalized AS ( SELECT ru.deal_id, ru.person_id, ru.role, CASE WHEN ru.role = 'COLLAB' THEN ru.role_weight / NULLIF(cc.collab_person_cnt, 0) ELSE ru.role_weight END AS contribution_pct FROM roles_union ru LEFT JOIN collab_cnt cc ON cc.deal_id = ru.deal_id ) SELECT n.deal_id, n.person_id, n.role, n.contribution_pct FROM normalized n LIMIT 15 ; WITH deal AS ( SELECT so.sales_order_id AS deal_id, so.contract_id, so.customer_id, so.deal_amount, so.order_date_utc AS deal_time_utc FROM fact_sales_order so ), contrib AS ( WITH role_first AS ( SELECT d.deal_id, COALESCE(dc.owner_id, so.sales_person_id) AS person_id, 'FIRST_TOUCH' AS role, 0.30 AS role_weight FROM deal d JOIN fact_sales_order so ON so.sales_order_id = d.deal_id LEFT JOIN dim_contract dc ON dc.contract_id = d.contract_id AND dc.is_current = TRUE ), role_follow AS ( SELECT d.deal_id, so.sales_person_id AS person_id, 'FOLLOW_UP' AS role, 0.50 AS role_weight FROM deal d JOIN fact_sales_order so ON so.sales_order_id = d.deal_id ), collab_people AS ( SELECT DISTINCT s.contract_id, s.operator_id AS person_id FROM fact_sales_shipment s WHERE s.operator_id IS NOT NULL UNION DISTINCT SELECT DISTINCT i.contract_id, i.handler_id AS person_id FROM fact_invoice i WHERE i.handler_id IS NOT NULL UNION DISTINCT SELECT DISTINCT w.contract_id, w.handler_id AS person_id FROM fact_write_off w WHERE w.handler_id IS NOT NULL ), role_collab AS ( SELECT d.deal_id, cp.person_id, 'COLLAB' AS role, 0.20 AS role_weight FROM deal d JOIN collab_people cp ON cp.contract_id = d.contract_id ), roles_union AS ( SELECT * FROM role_first UNION ALL SELECT * FROM role_follow UNION ALL SELECT * FROM role_collab ), collab_cnt AS ( SELECT deal_id, COUNT(DISTINCT person_id) AS collab_person_cnt FROM role_collab GROUP BY deal_id ) SELECT ru.deal_id, ru.person_id, ru.role, CASE WHEN ru.role = 'COLLAB' THEN ru.role_weight / NULLIF(cc.collab_person_cnt, 0) ELSE ru.role_weight END AS contribution_pct FROM roles_union ru LEFT JOIN collab_cnt cc ON cc.deal_id = ru.deal_id ) SELECT d.deal_id, d.deal_time_utc, d.contract_id, d.customer_id, c.customer_name, co.person_id, p.person_name, co.role, co.contribution_pct, d.deal_amount, (d.deal_amount * 0.05 * co.contribution_pct) AS recommend_commission_amount FROM deal d JOIN contrib co ON co.deal_id = d.deal_id LEFT JOIN dim_customer c ON c.customer_id = d.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = co.person_id AND p.is_current = TRUE ORDER BY d.deal_time_utc DESC, d.deal_id, co.contribution_pct DESC LIMIT 15", "parameters": {} }, { @@ -28,7 +28,7 @@ "businessName": "FinancialAnalyticsDashboard", "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", "datasourceId": "19", - "sqlTemplate": "SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ar_amount, SUM(COALESCE(written_off_amount, 0)) AS ar_written_off_amount, SUM(COALESCE(unwritten_off_amount, 0)) AS ar_unwritten_off_amount FROM fact_ar_receipt WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ORDER BY ym ; SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ap_amount, SUM(COALESCE(written_off_amount, 0)) AS ap_written_off_amount, SUM(COALESCE(unwritten_off_amount, 0)) AS ap_unwritten_off_amount FROM fact_ap_payment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ORDER BY ym ; SELECT DATE(doc_date_utc) AS d, SUM(COALESCE(income_amount, 0)) AS cash_in, SUM(COALESCE(expense_amount, 0)) AS cash_out, SUM(COALESCE(income_amount, 0)) - SUM(COALESCE(expense_amount, 0)) AS net_cash FROM fact_cash_flow WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '60 days' GROUP BY DATE(doc_date_utc) ORDER BY d LIMIT 30 ; SELECT r.customer_id, c.customer_name, SUM(COALESCE(r.unwritten_off_amount, 0)) AS ar_overdue_balance, MIN(DATE(r.doc_date_utc)) AS oldest_doc_date FROM fact_ar_receipt r LEFT JOIN dim_customer c ON c.customer_id = r.customer_id AND c.is_current = TRUE WHERE COALESCE(r.unwritten_off_amount, 0) > 0 AND (CURRENT_DATE - DATE(r.doc_date_utc)) > 60 GROUP BY r.customer_id, c.customer_name ORDER BY ar_overdue_balance DESC LIMIT 30 ; WITH revenue AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ship_amount FROM fact_sales_shipment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ), cost AS ( SELECT to_char(event_time_utc, 'YYYY-MM') AS ym, SUM(COALESCE(ABS(movement_qty) * unit_price, 0)) AS cost_amount FROM fact_inventory_movement WHERE sales_order_id IS NOT NULL AND COALESCE(movement_qty, 0) < 0 AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(event_time_utc, 'YYYY-MM') ) SELECT r.ym, r.ship_amount, c.cost_amount, (r.ship_amount - COALESCE(c.cost_amount, 0)) AS gross_profit_amount, CASE WHEN r.ship_amount > 0 THEN (r.ship_amount - COALESCE(c.cost_amount, 0)) / r.ship_amount ELSE NULL END AS gross_margin_rate FROM revenue r LEFT JOIN cost c ON c.ym = r.ym ORDER BY r.ym LIMIT 30 ;", + "sqlTemplate": "SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ar_amount, SUM(COALESCE(written_off_amount, 0)) AS ar_written_off_amount, SUM(COALESCE(unwritten_off_amount, 0)) AS ar_unwritten_off_amount FROM fact_ar_receipt WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ORDER BY ym LIMIT 15 ; SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ap_amount, SUM(COALESCE(written_off_amount, 0)) AS ap_written_off_amount, SUM(COALESCE(unwritten_off_amount, 0)) AS ap_unwritten_off_amount FROM fact_ap_payment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ORDER BY ym LIMIT 15 ; SELECT DATE(doc_date_utc) AS d, SUM(COALESCE(income_amount, 0)) AS cash_in, SUM(COALESCE(expense_amount, 0)) AS cash_out, SUM(COALESCE(income_amount, 0)) - SUM(COALESCE(expense_amount, 0)) AS net_cash FROM fact_cash_flow WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '60 days' GROUP BY DATE(doc_date_utc) ORDER BY d LIMIT 15 ; SELECT r.customer_id, c.customer_name, SUM(COALESCE(r.unwritten_off_amount, 0)) AS ar_overdue_balance, MIN(DATE(r.doc_date_utc)) AS oldest_doc_date FROM fact_ar_receipt r LEFT JOIN dim_customer c ON c.customer_id = r.customer_id AND c.is_current = TRUE WHERE COALESCE(r.unwritten_off_amount, 0) > 0 AND (CURRENT_DATE - DATE(r.doc_date_utc)) > 60 GROUP BY r.customer_id, c.customer_name ORDER BY ar_overdue_balance DESC LIMIT 15 ; WITH revenue AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ship_amount FROM fact_sales_shipment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ), cost AS ( SELECT to_char(event_time_utc, 'YYYY-MM') AS ym, SUM(COALESCE(ABS(movement_qty) * unit_price, 0)) AS cost_amount FROM fact_inventory_movement WHERE sales_order_id IS NOT NULL AND COALESCE(movement_qty, 0) < 0 AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(event_time_utc, 'YYYY-MM') ) SELECT r.ym, r.ship_amount, c.cost_amount, (r.ship_amount - COALESCE(c.cost_amount, 0)) AS gross_profit_amount, CASE WHEN r.ship_amount > 0 THEN (r.ship_amount - COALESCE(c.cost_amount, 0)) / r.ship_amount ELSE NULL END AS gross_margin_rate FROM revenue r LEFT JOIN cost c ON c.ym = r.ym ORDER BY r.ym LIMIT 15", "parameters": {} }, { @@ -36,7 +36,7 @@ "businessName": "SmartCostPredictionModel", "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", "datasourceId": "19", - "sqlTemplate": "WITH raw_in AS ( SELECT m.product_id, SUM(COALESCE(m.movement_qty, 0)) AS in_qty, SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) AS in_amount FROM fact_inventory_movement m WHERE m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 GROUP BY m.product_id ) SELECT p.product_code, p.product_name, CASE WHEN ri.in_qty > 0 THEN ri.in_amount / ri.in_qty ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN raw_in ri ON ri.product_id = p.product_id WHERE p.is_current = TRUE AND p.product_type = '标准产品' LIMIT 30 ; WITH bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ) SELECT p.bom_number, p.parent_product_code, c.component_product_code, c.unit_usage_qty FROM parent p JOIN component c ON c.bom_number = p.bom_number ORDER BY p.parent_product_code, c.component_product_code LIMIT 30 ; WITH raw_price AS ( SELECT p.product_code, CASE WHEN SUM(COALESCE(m.movement_qty, 0)) > 0 THEN SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) / SUM(COALESCE(m.movement_qty, 0)) ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN fact_inventory_movement m ON m.product_id = p.product_id AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 WHERE p.is_current = TRUE AND p.product_type = '标准产品' GROUP BY p.product_code ), bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ), line_cost AS ( SELECT p.parent_product_code, c.component_product_code, c.unit_usage_qty, rp.base_unit_cost, (c.unit_usage_qty * rp.base_unit_cost) AS component_cost FROM parent p JOIN component c ON c.bom_number = p.bom_number LEFT JOIN raw_price rp ON rp.product_code = c.component_product_code ), total_cost AS ( SELECT parent_product_code, SUM(COALESCE(component_cost, 0)) AS standard_cost FROM line_cost GROUP BY parent_product_code ) SELECT lc.parent_product_code, lc.component_product_code, lc.unit_usage_qty, lc.base_unit_cost, lc.component_cost, tc.standard_cost, CASE WHEN tc.standard_cost > 0 THEN lc.component_cost / tc.standard_cost ELSE NULL END AS cost_share FROM line_cost lc JOIN total_cost tc ON tc.parent_product_code = lc.parent_product_code ORDER BY lc.parent_product_code, cost_share DESC LIMIT 30 ; WITH scenario AS ( SELECT 'RAW-001' AS material_product_code, 0.10 AS delta_pct ), raw_price AS ( SELECT p.product_code, CASE WHEN SUM(COALESCE(m.movement_qty, 0)) > 0 THEN SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) / SUM(COALESCE(m.movement_qty, 0)) ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN fact_inventory_movement m ON m.product_id = p.product_id AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 WHERE p.is_current = TRUE AND p.product_type = '标准产品' GROUP BY p.product_code ), bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ), line_cost AS ( SELECT p.parent_product_code, c.component_product_code, c.unit_usage_qty, rp.base_unit_cost, (c.unit_usage_qty * rp.base_unit_cost) AS component_cost FROM parent p JOIN component c ON c.bom_number = p.bom_number LEFT JOIN raw_price rp ON rp.product_code = c.component_product_code ), total_cost AS ( SELECT parent_product_code, SUM(COALESCE(component_cost, 0)) AS standard_cost FROM line_cost GROUP BY parent_product_code ), delta_cost AS ( SELECT lc.parent_product_code, SUM( CASE WHEN lc.component_product_code = s.material_product_code THEN COALESCE(lc.component_cost, 0) * s.delta_pct ELSE 0 END ) AS scenario_cost_delta FROM line_cost lc CROSS JOIN scenario s GROUP BY lc.parent_product_code ) SELECT tc.parent_product_code, tc.standard_cost AS base_standard_cost, dc.scenario_cost_delta, (tc.standard_cost + COALESCE(dc.scenario_cost_delta, 0)) AS new_standard_cost FROM total_cost tc LEFT JOIN delta_cost dc ON dc.parent_product_code = tc.parent_product_code ORDER BY dc.scenario_cost_delta DESC LIMIT 30 ;", + "sqlTemplate": "WITH raw_in AS ( SELECT m.product_id, SUM(COALESCE(m.movement_qty, 0)) AS in_qty, SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) AS in_amount FROM fact_inventory_movement m WHERE m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 GROUP BY m.product_id ) SELECT p.product_code, p.product_name, CASE WHEN ri.in_qty > 0 THEN ri.in_amount / ri.in_qty ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN raw_in ri ON ri.product_id = p.product_id WHERE p.is_current = TRUE AND p.product_type = '标准产品' LIMIT 15 ; WITH bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ) SELECT p.bom_number, p.parent_product_code, c.component_product_code, c.unit_usage_qty FROM parent p JOIN component c ON c.bom_number = p.bom_number ORDER BY p.parent_product_code, c.component_product_code LIMIT 15 ; WITH raw_price AS ( SELECT p.product_code, CASE WHEN SUM(COALESCE(m.movement_qty, 0)) > 0 THEN SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) / SUM(COALESCE(m.movement_qty, 0)) ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN fact_inventory_movement m ON m.product_id = p.product_id AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 WHERE p.is_current = TRUE AND p.product_type = '标准产品' GROUP BY p.product_code ), bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ), line_cost AS ( SELECT p.parent_product_code, c.component_product_code, c.unit_usage_qty, rp.base_unit_cost, (c.unit_usage_qty * rp.base_unit_cost) AS component_cost FROM parent p JOIN component c ON c.bom_number = p.bom_number LEFT JOIN raw_price rp ON rp.product_code = c.component_product_code ), total_cost AS ( SELECT parent_product_code, SUM(COALESCE(component_cost, 0)) AS standard_cost FROM line_cost GROUP BY parent_product_code ) SELECT lc.parent_product_code, lc.component_product_code, lc.unit_usage_qty, lc.base_unit_cost, lc.component_cost, tc.standard_cost, CASE WHEN tc.standard_cost > 0 THEN lc.component_cost / tc.standard_cost ELSE NULL END AS cost_share FROM line_cost lc JOIN total_cost tc ON tc.parent_product_code = lc.parent_product_code ORDER BY lc.parent_product_code, cost_share DESC LIMIT 15 ; WITH scenario AS ( SELECT 'RAW-001' AS material_product_code, 0.10 AS delta_pct ), raw_price AS ( SELECT p.product_code, CASE WHEN SUM(COALESCE(m.movement_qty, 0)) > 0 THEN SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) / SUM(COALESCE(m.movement_qty, 0)) ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN fact_inventory_movement m ON m.product_id = p.product_id AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 WHERE p.is_current = TRUE AND p.product_type = '标准产品' GROUP BY p.product_code ), bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ), line_cost AS ( SELECT p.parent_product_code, c.component_product_code, c.unit_usage_qty, rp.base_unit_cost, (c.unit_usage_qty * rp.base_unit_cost) AS component_cost FROM parent p JOIN component c ON c.bom_number = p.bom_number LEFT JOIN raw_price rp ON rp.product_code = c.component_product_code ), total_cost AS ( SELECT parent_product_code, SUM(COALESCE(component_cost, 0)) AS standard_cost FROM line_cost GROUP BY parent_product_code ), delta_cost AS ( SELECT lc.parent_product_code, SUM( CASE WHEN lc.component_product_code = s.material_product_code THEN COALESCE(lc.component_cost, 0) * s.delta_pct ELSE 0 END ) AS scenario_cost_delta FROM line_cost lc CROSS JOIN scenario s GROUP BY lc.parent_product_code ) SELECT tc.parent_product_code, tc.standard_cost AS base_standard_cost, dc.scenario_cost_delta, (tc.standard_cost + COALESCE(dc.scenario_cost_delta, 0)) AS new_standard_cost FROM total_cost tc LEFT JOIN delta_cost dc ON dc.parent_product_code = tc.parent_product_code ORDER BY dc.scenario_cost_delta DESC LIMIT 15", "parameters": {} }, { @@ -44,7 +44,7 @@ "businessName": "HumanResourcesAnalytics", "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", "datasourceId": "19", - "sqlTemplate": "WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ) SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, p.position, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department, p.position ORDER BY ym, p.department, p.position LIMIT 30 ; SELECT to_char(employment_day, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS hire_count FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL AND employment_day >= (CURRENT_DATE - INTERVAL '12 months') GROUP BY to_char(employment_day, 'YYYY-MM'), department, position ORDER BY ym, department, position LIMIT 30 ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ) SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department, position ORDER BY ym, department, position LIMIT 30 ; WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ), headcount AS ( SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department ), ordered AS ( SELECT person_id, department, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), exit_cnt AS ( SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department ), rate AS ( SELECT h.ym, h.department, COALESCE(e.exit_count, 0) AS exit_count, h.headcount_active, CASE WHEN h.headcount_active > 0 THEN COALESCE(e.exit_count, 0)::numeric / h.headcount_active ELSE NULL END AS monthly_turnover_rate FROM headcount h LEFT JOIN exit_cnt e ON e.ym = h.ym AND e.department IS NOT DISTINCT FROM h.department ), flag AS ( SELECT r.*, LAG(monthly_turnover_rate, 1) OVER (PARTITION BY department ORDER BY ym) AS r_m1, LAG(monthly_turnover_rate, 2) OVER (PARTITION BY department ORDER BY ym) AS r_m2 FROM rate r ) SELECT ym, department, exit_count, headcount_active, monthly_turnover_rate FROM flag WHERE monthly_turnover_rate IS NOT NULL AND r_m1 IS NOT NULL AND r_m2 IS NOT NULL AND monthly_turnover_rate > r_m1 AND r_m1 > r_m2 ORDER BY department, ym LIMIT 30 ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), hire_event AS ( SELECT department, position, employment_day AS hire_date, person_id FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL ), matched AS ( SELECT e.department, e.position, e.exit_time, ( SELECT MIN(h.hire_date) FROM hire_event h WHERE h.department IS NOT DISTINCT FROM e.department AND h.position IS NOT DISTINCT FROM e.position AND h.hire_date > DATE(e.exit_time) ) AS next_hire_date FROM exit_event e ) SELECT department, position, DATE(exit_time) AS exit_date, next_hire_date, (next_hire_date - DATE(exit_time)) AS vacancy_days FROM matched WHERE next_hire_date IS NOT NULL ORDER BY vacancy_days DESC LIMIT 30 ;", + "sqlTemplate": "WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ) SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, p.position, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department, p.position ORDER BY ym, p.department, p.position LIMIT 15 ; SELECT to_char(employment_day, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS hire_count FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL AND employment_day >= (CURRENT_DATE - INTERVAL '12 months') GROUP BY to_char(employment_day, 'YYYY-MM'), department, position ORDER BY ym, department, position LIMIT 15 ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ) SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department, position ORDER BY ym, department, position LIMIT 15 ; WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ), headcount AS ( SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department ), ordered AS ( SELECT person_id, department, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), exit_cnt AS ( SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department ), rate AS ( SELECT h.ym, h.department, COALESCE(e.exit_count, 0) AS exit_count, h.headcount_active, CASE WHEN h.headcount_active > 0 THEN COALESCE(e.exit_count, 0)::numeric / h.headcount_active ELSE NULL END AS monthly_turnover_rate FROM headcount h LEFT JOIN exit_cnt e ON e.ym = h.ym AND e.department IS NOT DISTINCT FROM h.department ), flag AS ( SELECT r.*, LAG(monthly_turnover_rate, 1) OVER (PARTITION BY department ORDER BY ym) AS r_m1, LAG(monthly_turnover_rate, 2) OVER (PARTITION BY department ORDER BY ym) AS r_m2 FROM rate r ) SELECT ym, department, exit_count, headcount_active, monthly_turnover_rate FROM flag WHERE monthly_turnover_rate IS NOT NULL AND r_m1 IS NOT NULL AND r_m2 IS NOT NULL AND monthly_turnover_rate > r_m1 AND r_m1 > r_m2 ORDER BY department, ym LIMIT 15 ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), hire_event AS ( SELECT department, position, employment_day AS hire_date, person_id FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL ), matched AS ( SELECT e.department, e.position, e.exit_time, ( SELECT MIN(h.hire_date) FROM hire_event h WHERE h.department IS NOT DISTINCT FROM e.department AND h.position IS NOT DISTINCT FROM e.position AND h.hire_date > DATE(e.exit_time) ) AS next_hire_date FROM exit_event e ) SELECT department, position, DATE(exit_time) AS exit_date, next_hire_date, (next_hire_date - DATE(exit_time)) AS vacancy_days FROM matched WHERE next_hire_date IS NOT NULL ORDER BY vacancy_days DESC LIMIT 15", "parameters": {} } ] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml index 5f2e98c..25ea00e 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml +++ b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-mcpskills-mfg-data-agentv2" -version = "0.1.5" +version = "0.1.6" description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" readme = "README.md" requires-python = ">=3.10" diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/main.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1c8a03a6d62eb52ba019d65281f7425c22794a8 GIT binary patch literal 14593 zcmcJ0Yjjg(y71n~&i&f^y`)f(AT76Y712@(lwt(|l`1C_vo|Frm%5Vxt?Af798yIo z$4iUiSkBpwa%YiS{Io;b;=541Hx^gOP-^%<#mdVD&OfnGLF+xl#Jos`Bk!x*K97`bh86^ym(-j;Gb zTL!Br=PFo5hX!^gjr>pdwukwCac?R1ftnXG*O1ky*%@i8S)7~?PWb=BY+AMg&Tk1j z6K<%6odxkyu9U6ZJC~w(E!^}{dEYeEu4q@xqPXHpinkFMOhFx;26Z$$9qMRy8r1Ei zdgvo-_EQIxJE{G&eRk+9oQ3|0x-GVC9KVm_ZIc5hCqKBLpM3kw!!y-yWEJ z_p{g+M_1{qY#zVM=~-pFdz^OD_123@2t`KvY z{6Y6_x62uH`+ZNgVH^+u05RB39YoSBaZ{t3DX-|xF9ASd%1Seh?y6h0nQQP zJy4k&;DVtxN1#0r3Nsup7I`}MyPR%^*VPu->-KnHI?mhXaRxc) zu64GxIebnpC+eWN!^7?4JYvzkO%t0CO|Vu!3;~%80OyvKvErP(JnXZzq4#6Fc_h#Q9TGCy&IA9-Mq?XTD;f)vnot^~1w9(Fq;GQsvXPSm*e_}wlpAZl1{w=?7kip(C))7D|A zs%j1ReN}DF;GU+?LpWzTmJ#fjI(H`a)gjwlvN*&_Scl!NDVamH|svI#@3X7f?Gc|Rsk23nJOu-0K5Y<}8P1a~uVc%@QYKvM6qNXiz z1EngwO{rDtxS3KJ8PPxV9Ia7eAF~#K}5lD zYbHd!6i0z1L7@}@5`=mPYFnxz5wUBBosDrA27NRa{+*~1OEBH#tG}$$x)n032?JYLf8C^8P2nV$m%BzhbY0mRTl)L)qyafVjS>|y(A zjBemTry zSMVS`QzRY0-P>#u=s+_!uuF%dBRB0ZtL4&7&@`|JqAn_G$Q2BwGuLXF`#@tsFdZ@rBo*!OrA??k_}01F%Hx6d3GvNXWVK?wUARR6Ew-`aFXf0PqC`Z zD|w&#fRV=rhNW;RSkO3FO1?Fk+D6Ty7Eu9uzk;TA0(yh|mXNt-Q9(p<5WoNh50Hwl@FLt6IR0h?irh6L!*9g?&=)%~0ChP~M)+245k_|%y*t88|?$oShhAAwpSM_cMWYA%UT_$l%~>)^msu@Z~L+K z{;apcJ$Ymr%5L%(w)az0vUZxC(~46R3r?A1th0O1)RBsD(upg;<($s?0XkTayH zaq5^^1*;rnWSWeev^4|wWmR%tR=pP(ByUgS;29JVR&$?mwDLI9FQo;j@_l;fT9OUm{Y(LzHjxq`}g4*H-)ZjjE$(y4h^fxIb*#F+%(33Gra$XTj2 z)1D0yPhgFMCV53N&$%yOFgFoUjD67s00?~GKFWNDegQz;0wU3f$_@3~Q8Dpy!99Lf zWRN)D_y*P%LX~Ig&2zD%@1|QEVyDhd{pq7sHc{yfwD67C5W)5SvrR^NVy}G&xB#;G zjl-bXg^*1WJU?@8^6Wu*!0jMhYqJa|*AJw{Y0(0z}ALvUT|rK=&KF z8-?OkW5(6v1vC1MLp#O_9*JzcW+^xt?hf~@AGOTt4_vX#Lk?p+W)x_HeMa ze65|jR8gye&t92EC5I5Pi8F|P*z&@H_4jv=49|lkfK=x5~ z6WB)s9oOfi7zZRJ!h@|v2F(EcJ@P5amWY$L!VvX>A%mrfU9KcW<^iuWQKO{n1k!4P z`~wZ3Scdf1rzq5E7#Rb|ZAr?6GG{skIY`^pM4OC7-jALC{4Sl6&xh`kTu+k^kMdL~ z7a|a(BrD$a_YTQ+41h+$WS_%cWb^u`ot{ua08;P{C>Pax_oIPensQ)r@f_Htz;cD! zE$@PMFTpSH7(`vvHH{&%|KQ8xmg4@WKW;wRJY*Q1y?V^@sGxl`sx$TEy;e4EDSZ7U zL0ddwE)+@@3^4=ygra4m=H-HRIk^cG-Eib->@~>++F%yEfz~85g@Qzo&(KMy$-c?c zlv^Q>2qr08FsWV0_XD!N;)CREfwZo)Gl^4A@XvIz6GCnoJNHLfs6}FGn;8CmdYdM@ zN)mFAz@*U+qz`tQXW^5^Re_G6?nS5|zZ>da#d@S=fca`FucWu}SmRqybUiW7=npsk zsK4wnogB6 z(WOo)os+asom8sLI%-!Z1v(kvMDm=IQ^D!7N||$XD(=%JsZ$-G1KFTgWgbbDHj?r_ zZQxBNIUAG4S(7t#qGp%W`BLjl@z2&IhfLLm6=7vqHK>(!Q2AVF>d4E?gHBeL!uv2K z_XN1pr^8(sXu%&qyRsWXXz7bZdSiz_pX~0MJkcFHdIoIk5{QMWp>p!=Ph*iG+h#Dz zY)8|tZHllkU~aK5-->m=ka!Lh1ASIjNjXc?K_0ha%!^JTW`cZe|r_k$8X2p|Ba2HL<$F{u}lPco}dAtI|!7b z8qC|Aj}=vDDFwp??f?XE6~O?opF%cm9r~0>UNoi~RDYHr+ zVWO6tqX)VV^r`z5z*-@nU);Or*q*-5(fq0_`He%l=L*jj4$uEaF}kQRVu*%KLP0%u0`cSlV?rjZYGXEc?TBna^ z!HlSP;lV45`xv5?^Y zm46ubk@K*CoqRzGRx?{x(j{vj&8mQ7C2r{r78C4sNj0<53`D1`S zQW#ctg5AVNGhkcknv+JcGGHvLoz5@8o|2S-S>5!ygEUx~k~WqwP#B;zFq0eRk#YJJ zSp7>%ih5VstNgV}MRhW1^F#UFfz?l+1LzjBjap6}DF-PVfu01AvMV}OX(MIYi6M;V zsF*HghgAn@I8W6tRVkYbp%7+bOPLyaB}Em=V6FsJsDoE3#Hj>PSY<=(f3(9m-x3#8GDjZn%vvC7CD;9MaPLkM|5 zF%J}3LajnrZNTEhyBGf=mRLaQhIbb%3xeT1Aj#c#(+q2ZW{{UXGW0K@^S{C`0ImQD zl3`5ZB@thSo*(fa_;cl7=3Sci%}#-9{dVo%I7Pio*DK(&mR^satPP6W6s6jz01lp0 z(A7AOR7I)E1}b8(99_}9qG$c6p`$ZkaZ+in9taNX z7V?&kX_t+g3;Wg#R1V||MGHsGiv;bWxLT{SPT1xTY#&fwRffh9uTg5k=DaZLNz zHM1q6`8%%4`rm=Ia3Tk5bL$?WFBMcld|8#db`^EGh+eybxm;X^>E$$*ub?r#N>^8@ z{90LBH$(aL3>Bs;l~60Hr6)>PIx-5FDoKI68c9+n(T`QKN%bC$wI3)! zkxh=;3M1Vez?iCp*U5zV*FqDqTua3Ab14!0KC{6)rQA~{SwI|;qUL>i88Y-z-KVV) z96J(g>r{u;tck2l9cIF+4%kYk#y1a2&1t0|h(hTMD7B=Og6aaLxln2)rKYfo)f3}} zii`zn4G`@tXro}WJ2hcVn1S=l39CtrL`cQvo=|jZA9R{2k-8N{;=4jv%jON{%R9Q~ z+zZIs3Li8FTX;{;BGR*jE$-B%^-da1Qp;dV!aBBeuuLZIi5o8`{j1aZB0;+=urt!? zGYEsMjjdp34$hLt9;}pinwVuanZ*!9OCkyE92ss7f<2FYh}3VmYZn=zmYthAgS1us z13OM@oq1$?YUCC$@58$0k#+664~#zSP8qHA_h~af&9G$%qQFT)D=MpZ1-42)6JWg6 zgz+jQ#tUM^f(O8tJ{`UQb{B>XD2Q|#AGGVRF>Tj`3WRBP5xe+=veWdSz7JTFiOpk| z4Avx}Vj8spvA|21_tFp=H1sI9O6)ShV7tyweRVAM*{2Dq8oe1}zkW6L$!DMmOr0FC z&8-cz`&@R?QOw~EIPp!`0bT_jzhl3j_plwBjhLBllVn5HGgx?st^kpmI}n7&KjJ`e z2qqV^C@em~BjV>z!M z99|9tL--ajb>aAA-NQ<`wzzs{~F8|*FQf4k3#U2JN4CDpa)$)_4&kCum5M) zK~YaSyS?Cl(D4iR(`%o6YX8E<4K4m!2u<5|?5W?;g2{S}*4EYT#P9C)u5Ftzv2KT_ ze(Tdu)hzaIS<$g#?bdbsYwP!H-B7!y(fdThR&23$(^ET^*7JKewY0Q6`lzUqCeo45 zf;L}^?1#~a8_{~*UT!=3dzi?oV4MCB@8U!>`H@sdF2296TCiXNyiWzd+}Q@N@+}fdoZNx*!21Z04p7!<6JyZKs(=OX)|9+bnj)-sX(BmmB0LLJ=&#n}eA>Pg7 z1@dn}B;V*exD@GF$n#ccJ`^NCD`6zP{^haj=TA(1`s!rwr=TRFw)oqw$qzr8y6~IH zH$O^r_PP&nEYUqDhF`z&!M{kC68oY*_WoDbyAS{H#e0%jgydpp-+?=ceR5>-%sY53 zMsffy&?-IL0Z|tMYbPO(A>{f;lBwHFoNa$qx=n4m_*$=sy-Y z2XZ6oFM0rl>wowPa+Q#~SG@e;i-_ch6Z_;h*WVoa;froKARQ3{QQqZ=Sg7#=-~puE zK*)ykIshM0?e?)C5!#JB(Mf$+X4nqrTqlW&qG~r-q=Gz(q&zZT(b$0CCRbbQy)SSq z%dt<%*PV;K`G$;Iw98zHbxf+s~RFEFp&~(RR^l$JB zT!3>SYB9>4M!`~fMNIw$oE`MOT{ER;Y1}Sy{ot30h8SPp|b5{PSVe-{oP16?nC+3{~tbTZn@a!&Ocgxtq zJ!3|tGE+D8SCJNM;jKDop*A9kk8e|R^{0iWwL#Gtd24q3Wz_FZCfypJ! zO@)?RgNc-)4cTBrz|ujVx1~7=&n51&vR7gg5gB+?gm^Trbf^|qFREUMF0j5d_gsfR z;h$RsvH-^e)AR+MpsqXHoG-XN;HS$4fOs1u??TD*)ttzHS1w8pqH$VdHIItCsFoUu zNUQJ+)xPlRo3difHNb=!`kzWwWl9)r^$YjN4a_+Sf#@7lNi; zz7$*+%Cm240H<$h3>N)QlmYMzqo0&8C>j1o9RBCH0t$bQW9iSNI92!GfoEV*2Q6!@ z^f9`2srsLlkb0+9U;7AnY|ypK)t77xrYj9lerW++yOO!IP>t#3G?uTVG5v^otx0p4 zrq?pY%L)yqbs8w2h9>|U(&PI$9L0kl3}pEo3~$g;3K^dklFwofY?<*nU&au?$CS>U zG^9HpWTs1VdqN}IKfG!N9ttw(ws(z@H0Nb*4W$`0%t4imx8TZ_GE1iKUOv#w&;9DO zT_m;4RHtH)>-L?W{fyr6kJ6=_5S zap{G0H&&~hzIL~t|6f>&Xb;qi!Bz^F*WiF9af-$tz^_t+O=-noZVCNdTZAPxo!5WQycnM3f8JIrW%?_?-4!K z{kb0(oh<4%2-f*yOx3ulIBLo|y0d#{G&{ey`dIaCmC~&4T94CPBKyXe{0ZqjP_V8T zV^&Tuy2A~>YBFOz(+>R|c&N{B=aI?I8d9$$(@pJDV7mg~UB zyPCYp@IK7zF)z_rLPvR|4`NoTv>M;fJ%I%6O)Do{LL$;u>HjCt5R3m}0V)bw{6uRg z==KDv(e23PaRWn;+$$2roNu3O%|#59T$XaAE|RC8T#beh9&u1KfS0tN4-$_)QMImq z?T!tSKLG)cKVm!b615mpOR$H<;c)tZQ4v))0A8{LW)OxXpXOzlYQhM$7V`8>UT8&q z+8dJ}tC0SQx|9{U9}E8lqu*kLMr9&(6M!QSb(aU+9qnq#2YU_wAJ7=UjSBn#aa>8$ z^i4)XGq@H0tca+M$skYKHewxG3Gk>HYx^2#(^Lysqrr>jXehH-Yw<-9npbZ%UcPiOV#_H!S%o@^bdSag$u{4FJ= z$RjoJ#iVi82%R-b=fo8yv^Am#$T>PtrSC4Jo0@`WSt zr(Za<^y-p^5%|+LjHou<)Id`l8)n5$t2vt9OZRPyQzS94J&_o$NhB_=`zG&dL-R;O zv#{%htGoOo@YmoM`29b@jnEB>ACsR=Qm&rfq_`!Gc5{o;OzUsXETD_yH56lvbX-vu z{gu(ax<0O?nBt$~FX_O%e-n-bFRJc4#{NCC>U(D06=q#rhlO_nNG;Em)>l#gR#mY+ LTlw!kVi3j;do9o>DH*`i2gE|pIF$y2mNx@s` zQ}~sgN(wPFOEW4~#j07F)$}N90Nq0~>X+zFEu(>1-Jk{`K+rI&g;_nLBNTY7hp~Y* zGKM275#l=-BU8pKVobe=F~3DI<>yqLWeUV<*|JNNEX_eBLY<2`O`T?#FJ~<5qF)Iv0GPPnmK@shoL@6)^OIc_puub=tVm6=m`_0|VhGjG1^);HM&k>>)vZu`t@Kc6{w zdG@ncGAG`K=@4w!%X>qIVdecl&c;4%&Rl$B_QEL{hUr)z+bz27)8jACynOO@^yKXe zAH#GY>_5cvK=kTZ<_{w|cDJ!1b~`Caxg{_lsW5N9q~=*M%m=!2R>u%T!w=i-ZX>iK zD1!P*S1|$}d_pcMb9nkp_w7^iREF?xcfcblwtuD*5}A zwwH1P#&Q(#ZT~fu^(cZ}ZA8KqAWKC~i;BD~_*`%lj4BDH0x(qsV_>QZm}=hfzvI|THi;lWvuLDoYRq4WI+eBX$=N4Xc( zA<>wp5f|lqJ&Jn7YbmgCp5g#HwfGq7R~|seDCgp^60U^a4i7kt%;{IB$A6zW^CDcs zqL(Q32q8WgVtH|ZP>^N|-z&ID4>KWm)NPkp+&*#o)~E0P>)I*Vk7V9?u?WQA;{`H2 z!jD572(b>g-66XdN8^KtfY(nDWG4?9&4Xe%A>{K8?9PE@d1m6D-MaWmCU$y0zJ722 zE;i6B9)|7Ltywc4PqxP!_K83C`octl`MB`x^I?u>8DJvvVRjxa!H~!W0|Ee^b;#~- zt)tPN>#lQj+3j`q>8mGlj?ylB+&N|#A|ulLi1@-1`-|a_k9FHu=D!m$P@}yt6iAq8 zv~z+dFRGJpl4xvtNi7>*qOoBmBpFH!DH)j(3rMSDU2C=?9do(9Ls1TS-@l z3zE7g$ost_>_j4gM6=-gCAAy?B+a2<(8qcMP8tLP14^nMuTNklYOO@AaVq#KIM+Om z+kM2_%jQ-*up%7cd_KVg#)1nXe&33GRy^Io z0B{mnesyR-vgbygoaRAK4wV<1Ujy=DO$0qSmMF?LAWPLq&Domxqvsz_l{ZG))8&=9 z@w!xb1B|WJBTt-tBHlIHp0Yj`-Icc0jO;wSGaeW{l(MajK9$FBOqH(yG;njCb;jFA zRVmBrXh(XH^$d5Ci|fv>OD$@MZqKTbt}+(MDiu}g=+jv((pqBABsELY4%e_Lu1;u| zeQl^nui7yDbi5;JTzOMxjU7+w9L42yeKP>H3GMO+fc;6`vLf(ngC*V21bo_(#yZTW zWI3r_hGzznI(yo&D!1`XL-nMg{<3wnE9KmH-MJ;<+>&y(Om07va2~qhY8v&YT$`@D zwkBL#Q?Ax8%O+j>hduEF$+8ug)v=^*Y1-k;u>vh0UNyR7vTg%RXp&m{efxu&H`hHn zY?`cIozymi73%Ur0HwAf-k#7jqzx7E>ZD49r6U!Q8r`m<1GT1zaU?flyeKs{}$tnz^Y!@x!`H3K7Wep)klq zuR&UnIdf(D>f1B%D{|2ROSdk+Gd=#{%LJp06$O1DY%(0KNfuP`e@bEGKr~;Hkn@!gk01*; zP6V8z9oEv352-j&0EjGoUVk|S;FrRebt-wBUP3G?L^&R3fBbsf;GR<_={bQ52%M2W^$A^u!adqW{Mz&JJhT1;&q-e2$# zn30@?TpS>Ak;gF$0zAnz_$C+#Sj3$0d^28k-#V}} zcb)5=(m23r>2^>zYM1`j^}6ePXR3D1$qw*MRm+DB)8LklTzq88urgh3A5%;y5~h|( zZ3{TQ@|tvIb=p>WM?>4x(Op?1vMl)q=~Rynt8SQT;tyZ6O_>_fM%#3K>%{(4eFwyV zVS2bHcH|!hQ@VM>@UHkn;P%do31g#Vs!f|8igzZ=E3ccI6XxbI`YZE>tQDALE0M8! zlCJvh+x5t7yKbsWnCieKg1G62>c_TDY)x3VCym>|EyC120blT6R%|UtUzFEuZBl;G zNI@&9!8|=&@BA)t97z7ZD)&;(8TM0x1!{x&>t(v6US6;kX4H9y2M)1V(G}d?5FMeQ zMu8fb(VV0ALgp2I1H>oiy;yO`|3Q#)VXnv72kD?)t|N-qcdnn2Ed@1436so~k7qx) zP^9>t3xEF(5|1d+9^QKlvQ;}U z;PEMTs`$;gk1qycOYRYm{k?$W7P%Vo#aNcV7{Pcm;j@Ho(7v2^nZ0 z!LlKv<&68JJJu6Flrk)jwx%uBBd)Wqcvs4@DrxB$J$hyEy}|LuFBPfg4oH~Ft436eoeOYl_(#pOG$Hg!n%7qz} zWN!PQR*olbd)sXs;hhS}$OeQk&w9KF^t1o=JOMo3zyt>r&ZE;JWQ|~ zfyNI@xTXX1yN2(WU+sM zLD-SPUr_L;7t{t!*dG$!gMTCN3U+7rw43}vChTLMA(7+|9s5=TBMc=g{ z%J_FwnN|Ogq8=G-%_96B+dsa3f}UW;Hzzi9+$B@l!-^)VdTjl;?E`lfL3g75E`DX7 zqbevhCWP16zPtF9U59An@L*C|BYQXVV*L4R)sl6^9fR7Uj<(-1qoq%d_obHXOwhH_ z*5QWO%CBkNa7Ubu9i7xKouZfF;f`2mY~M)d+0OIovHcTkrz*Bh+S(@d+o$Lq^71pW S_DTJ+uV{OAF`{dU-v0qEIt +2026-01-28 09:58:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest +2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:142] - 收到 ListTools 请求,当前配置数量: 7 +2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:161] - ListTools 响应: 返回 7 个工具 +2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:58:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_mixed, arguments=None +2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_mixed +2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:58:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_error, arguments=None +2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - ERROR - [main.py:190] - 工具执行失败: 这是一个演示用的模拟错误!执行被中断。 +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 186, in handle_call_tool + result_contents = execute_tool(name, arguments or {}, tool_config) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 259, in execute_tool + raise ValueError("这是一个演示用的模拟错误!执行被中断。") +ValueError: 这是一个演示用的模拟错误!执行被中断。 +2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:59:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_resource, arguments=None +2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_resource +2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:59:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None +2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image +2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:59:09 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None +2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image +2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 10:02:13 - root - INFO - [logger_config.py:112] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\logs +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'mcpskills_template_server' +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:322] - ================================================== +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:323] - MCP Skills Template Server 启动 +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:324] - ================================================== +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:329] - 命令行参数: {'mode': 'local', 'json_path': None} +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:332] - 使用模式: local +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:83] - 从本地加载 8 条配置: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\tools_config.json +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:137] - 已加载 8 个工具配置 +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:339] - 开始运行 MCP Server (stdio 模式) +2026-01-28 10:02:13 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') +2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 10:02:40 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest +2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:143] - 收到 ListTools 请求,当前配置数量: 8 +2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:162] - ListTools 响应: 返回 8 个工具 +2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 10:02:44 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_complex, arguments={"user_info": {}} +2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_complex +2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 10:02:50 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_mixed, arguments=None +2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_mixed +2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log new file mode 100644 index 0000000..14e452d --- /dev/null +++ b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log @@ -0,0 +1,89 @@ +2026-01-28 09:58:33 - root - INFO - [logger_config.py:112] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\logs +2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'mcpskills_template_server' +2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest +2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:304] - ================================================== +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:305] - MCP Skills Template Server 启动 +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:306] - ================================================== +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:311] - 命令行参数: {'mode': 'local', 'json_path': None} +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:314] - 使用模式: local +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:82] - 从本地加载 7 条配置: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\tools_config.json +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:136] - 已加载 7 个工具配置 +2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:321] - 开始运行 MCP Server (stdio 模式) +2026-01-28 09:58:33 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor +2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') +2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:58:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest +2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:142] - 收到 ListTools 请求,当前配置数量: 7 +2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:161] - ListTools 响应: 返回 7 个工具 +2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:58:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_mixed, arguments=None +2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_mixed +2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:58:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_error, arguments=None +2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - ERROR - [main.py:190] - 工具执行失败: 这是一个演示用的模拟错误!执行被中断。 +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 186, in handle_call_tool + result_contents = execute_tool(name, arguments or {}, tool_config) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 259, in execute_tool + raise ValueError("这是一个演示用的模拟错误!执行被中断。") +ValueError: 这是一个演示用的模拟错误!执行被中断。 +2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:59:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_resource, arguments=None +2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_resource +2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:59:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None +2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image +2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 09:59:09 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None +2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image +2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 10:02:13 - root - INFO - [logger_config.py:112] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\logs +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'mcpskills_template_server' +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:322] - ================================================== +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:323] - MCP Skills Template Server 启动 +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:324] - ================================================== +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:329] - 命令行参数: {'mode': 'local', 'json_path': None} +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:332] - 使用模式: local +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:83] - 从本地加载 8 条配置: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\tools_config.json +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:137] - 已加载 8 个工具配置 +2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:339] - 开始运行 MCP Server (stdio 模式) +2026-01-28 10:02:13 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor +2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') +2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 10:02:40 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest +2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:143] - 收到 ListTools 请求,当前配置数量: 8 +2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:162] - ListTools 响应: 返回 8 个工具 +2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 10:02:44 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_complex, arguments={"user_info": {}} +2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_complex +2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent +2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: +2026-01-28 10:02:50 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest +2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_mixed, arguments=None +2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_mixed +2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log new file mode 100644 index 0000000..d73ec05 --- /dev/null +++ b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log @@ -0,0 +1,8 @@ +2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - ERROR - [main.py:190] - 工具执行失败: 这是一个演示用的模拟错误!执行被中断。 +Traceback (most recent call last): + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 186, in handle_call_tool + result_contents = execute_tool(name, arguments or {}, tool_config) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 259, in execute_tool + raise ValueError("这是一个演示用的模拟错误!执行被中断。") +ValueError: 这是一个演示用的模拟错误!执行被中断。 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py index 41c33ce..2cf6e94 100644 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py +++ b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py @@ -12,6 +12,7 @@ import os import logging import argparse import anyio +import asyncio import mcp.types as types from mcp.server import NotificationOptions, Server @@ -182,32 +183,27 @@ async def handle_call_tool( raise ValueError(f"未知工具: {name}") # TODO: 在这里实现你的工具逻辑 - # 示例:简单返回参数 try: - result = execute_tool(name, arguments or {}, tool_config) + # execute_tool 现在是 async 的 + result_contents = await execute_tool(name, arguments or {}, tool_config) logger.info(f"工具执行成功: {name}") + return result_contents except Exception as e: logger.error(f"工具执行失败: {e}", exc_info=True) - result = {"error": str(e), "tool_name": name} - - return [ - types.TextContent( - type="text", - text=json.dumps(result, ensure_ascii=False, indent=2) - ) - ] + # 重新抛出异常,以便 MCP SDK 能够捕获并将其格式化为错误响应 + raise e -def execute_tool(name: str, arguments: dict, config: dict) -> dict: +async def execute_tool(name: str, arguments: dict, config: dict) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: """ - 执行工具逻辑 - - TODO: 根据实际需求修改此函数 + 执行工具逻辑并返回 MCP 内容列表 (Async) """ - # 示例实现 + # 1. 示例:Hello World (返回纯文本) if name == "example_hello_world": - return {"message": f"Hello, {arguments.get('name', 'World')}!"} + msg = f"Hello, {arguments.get('name', 'World')}!" + return [types.TextContent(type="text", text=msg)] + # 2. 示例:计算器 (返回 JSON 格式的文本) elif name == "example_calculator": a = float(arguments.get("a", 0)) b = float(arguments.get("b", 0)) @@ -224,14 +220,82 @@ def execute_tool(name: str, arguments: dict, config: dict) -> dict: else: result = "未知运算符" - return {"result": result, "expression": f"{a} {op} {b}"} + output = {"result": result, "expression": f"{a} {op} {b}"} + return [types.TextContent(type="text", text=json.dumps(output, ensure_ascii=False))] + + # 3. 演示:纯文本 + elif name == "demo_text": + return [types.TextContent(type="text", text="这是一个标准的纯文本输出示例。")] + + # 4. 演示:图片输出 + elif name == "demo_image": + # 这是一个 1x1 的红色像素点的 Base64 + red_dot_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" + return [ + types.ImageContent( + type="image", + data=red_dot_base64, + mimeType="image/png" + ) + ] + + # 5. 演示:资源输出 + elif name == "demo_resource": + # 模拟返回一个嵌入式资源 + return [ + types.EmbeddedResource( + type="resource", + resource=types.TextResourceContents( + uri="file:///logs/app.log", + text="[INFO] System started\n[WARN] Low memory", + mimeType="text/plain" + ) + ) + ] + + # 6. 演示:错误状态 (带 observation) + elif name == "demo_error": + # 尝试模拟用户要求的带 observation 的错误结构 + # 注意:标准 MCP 协议中 TextContent 可能不支持 observation 字段 + # 这里演示如何抛出异常,这是最标准的错误反馈方式 + raise ValueError("执行失败: 网络超时,请检查连接") + + # 如果客户端支持非标准字段,可以尝试返回如下结构(需 SDK 支持): + # return [ + # types.TextContent(type="text", text="执行失败") # 无法直接添加 observation + # ] + + # 7. 演示:混合输出 (文本 + 图片) + elif name == "demo_mixed": + red_dot_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" + return [ + types.TextContent(type="text", text="下面是一张图片:"), + types.ImageContent( + type="image", + data=red_dot_base64, + mimeType="image/png" + ), + types.TextContent(type="text", text="图片展示完毕。") + ] + + # 8. 演示:复杂参数 + elif name == "demo_complex": + user_info = arguments.get("user_info", {}) + tags = arguments.get("tags", []) + + # 模拟异步处理 + await asyncio.sleep(0.1) + + result_text = f"接收到复杂参数:\n用户: {json.dumps(user_info, ensure_ascii=False)}\n标签: {tags}" + return [types.TextContent(type="text", text=result_text)] # 默认返回参数 - return { + default_result = { "tool_name": name, "arguments": arguments, "message": "工具执行成功(默认实现)" } + return [types.TextContent(type="text", text=json.dumps(default_result, ensure_ascii=False, indent=2))] async def run_server(): diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py index 5f9eb7e..64423d4 100644 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py +++ b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py @@ -61,6 +61,23 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]: elif param_type == "number": property_schema["type"] = "number" + + elif param_type == "boolean": + property_schema["type"] = "boolean" + # Boolean default handling + if default_value is not None and str(default_value).lower() in ("true", "false", "1", "0"): + property_schema["default"] = str(default_value).lower() in ("true", "1") + + elif param_type == "array": + property_schema["type"] = "array" + # Simple array of strings by default if no item type specified + # For more complex arrays, we might need extended config in sqlParams + property_schema["items"] = {"type": "string"} + + elif param_type == "object": + property_schema["type"] = "object" + # Allow any object structure by default + property_schema["additionalProperties"] = True else: # 默认当作 string 处理 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json index f8ae9a4..2074e10 100644 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json +++ b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json @@ -12,5 +12,47 @@ "description": "示例工具 - 简单计算器", "toolPrompt": "执行简单的数学计算", "sqlParams": "[{\"type\":\"number\",\"name\":\"a\",\"displayName\":\"数字A\",\"required\":true},{\"type\":\"number\",\"name\":\"b\",\"displayName\":\"数字B\",\"required\":true},{\"type\":\"select\",\"name\":\"operation\",\"displayName\":\"运算符\",\"required\":true,\"options\":[\"+\",\"-\",\"*\",\"/\"]}]" + }, + { + "id": "demo_001", + "name": "demo_text", + "description": "演示纯文本输出", + "toolPrompt": "返回一段简单的文本信息", + "sqlParams": "[]" + }, + { + "id": "demo_002", + "name": "demo_image", + "description": "演示图片输出", + "toolPrompt": "返回一张 Base64 编码的图片", + "sqlParams": "[]" + }, + { + "id": "demo_003", + "name": "demo_resource", + "description": "演示资源输出", + "toolPrompt": "返回一个嵌入式资源", + "sqlParams": "[]" + }, + { + "id": "demo_004", + "name": "demo_error", + "description": "演示错误状态", + "toolPrompt": "触发一个执行错误", + "sqlParams": "[]" + }, + { + "id": "demo_005", + "name": "demo_mixed", + "description": "演示混合输出", + "toolPrompt": "同时返回文本和图片", + "sqlParams": "[]" + }, + { + "id": "demo_006", + "name": "demo_complex", + "description": "演示复杂参数输入", + "toolPrompt": "接受对象和数组类型的参数", + "sqlParams": "[{\"type\":\"object\",\"name\":\"user_info\",\"displayName\":\"用户信息\",\"required\":true},{\"type\":\"array\",\"name\":\"tags\",\"displayName\":\"标签列表\",\"required\":false}]" } ] \ No newline at end of file diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfce60552753f32e6e88c62a294405bee77b894a GIT binary patch literal 707 zcmaJ;zl#$=7@hr*WV4&ZLk{a`w@3tA6%P>$8xevaL*Qd>cd|E^ncZb(6WL(nUtw=| ze~Xn^IZX<&5G>@byXxjkavaFvSG=9~W_G^!&HFkW#>Ca1v#AJtLVhB-F2}cZ@!owt zlbleJd$}(?r^Dxt|%@5q4~(xDpBkw*%W zcfWY_PTNbP^}~0KkY-%7Y{3dXo>zJ-*B>$_KxMUA2q_ITT-A~_{I}=4q@L}|isf)> zsxl31lrbrR)x!2)ynX#r3SKt$ZowPK`J63f1KCzfLz|%LPQrq>c7WuV84gP=?Fbn! zSCCcZTom@e?D>jm!Hy+QeS7G%Weuh^&Jf6|C`4J<82?1VbsFoyIn*IS2ce75Lx>Ri z2>9kYK^QoAlOEU}Kwf14Ho=+Tgv*-i?Nv5LySoAYxz@%vc5iE8hdWdIiEcV#JA5)Y zBNxs9x%~L*$*et@Ffprjb;`45wo71Q)@^aUc4ggmx8(Gnoh7C{0}!QX z03OxtW%Nu{`BL)d`kpg`37h*4{sx}s{UDEjkp~-c^o<;C$n-mzZphQ~FiHGRJ$>j- FT>;iY(zgHr literal 0 HcmV?d00001 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/api_client.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/api_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d2ddaf81c96f6322cfabb1d08789fb51c678081 GIT binary patch literal 7295 zcmb_heQ*;;mhX{9qpz`KS^mN{w!nZbjQI#jE^ABzi_J&jHC!;M#0ychnz4~1OWK)H zf_)<8uw)~4A;w8I7RhblO-KcbY)ooX3%f~n@9vK~uI}ne0e{HM`}OPBuU~h+{=McON=j@5(tn)a7u-}y$iHJnOU`^@ZUsfi zIN47)(n~msGlZyKO8FXk4e&LFjDvJ9J!tAR4Klq9MXZFQIaA0yXz8^KT6?V&)|rrP z(B5mOh=K5SzT^VP#MJBH%)L&|(pzdEI|yezMmXCAL%z+$8T@5D!@HIMfp>HEcKC8E z@9HyfCCBLAax095e_T1|;43&M{7QkJ<|}wpp8?TL%EeoD_h9l$8zptrrxR{ zLnGnY6-01>_EgbqsQKAEM@|G^CWnpBk(ViNMeGR}&bD>$F3-%xAKiZYT=tXqrxTZM zUzoJbj=T%?+wUIp?Cj~;>p>R1F?;dVnGasG%|c(&?KR4DM=&7C#;xHY*|H}p1tVd9 zXcq0|HOOo~FZq7p7kOVy2+4M=`J+MKi~Nw6l5JQGMf&@Bp&x}2^5hn^@K?f?fJ#ww zeQj?+eVp(FR+Qjs_8K_@XW;2RBWL7IJfm2M)=MVd+{bVXSjfU#IWun?fL&9ZV0A3V z=nHvrFAIDnU~NWS5ooo-+Bx*Kb8|MH;q35ZVI1Ui7OY*#A~Xlk%!+2Jr2O_KL^Hzxaup?x(vCr#`0|^ckhmc)!>qAu zGJs1TJ8p!UN#o!QP-C>3z;|Mi3<-7!_Ek2o}srFvAs*&Ax!L z|71ozq%s{0_eH$4OpAP|Pwk>}i?~E+ynC;7wjCHc)anl&hzgMbJ|GKrtyzmk)v_}1b7ybQwzQqSE;)}shNc2hkU^L{H9@G+(f+6t$T3S_gZ4{i4 z&ld~_C7*A&Og}ImLUEX%BdP{;nQIe^u zzgo5KY|96&P-8OE&My%elI7aQq?WxK~FZNtI`02rmds5{c|7_`;1F2|G zUWy6ffPSKYh-XPTb#W-bsUrjMElwjVCH>4Tw%?(beq!ePeq4Aab@JjP>;sP+s|Kd& z*Wb+kWMbyaFIDfZZxry!=DgSN(n1+<%BDk-6g@06swYzR#d%7ofF==V=n>GN%8Y9L z;nI17tCaP~xE3-nH$&Lc(e~r*DSN|>TF;sG)9n}Osr4P{^&Qu1I};rzx-w4pX#99Q zWvLZV%f(H&9a_fmj)R;1FAYbi#iuGb7pKSJ{>NK6PJuD#7~U&0Z(M@IJ2QD{`qQJ? zv!4x@`lHcMFyP0FY~6qu340C0?6yc)0#Dr9GZf`zW@{k8N2Ouwv#l`S*4=oSDaXcZ zP%I;x5AlAE7eo=Aq$h79)`!4nWzK{|IOI+qUI%K*UMXs!eYG71!U@qh@M9^XEAv%~JsDry;n)%}YL5DpL z(ZTY-l|Y|Rlpd->@k%(PDoq4u#oaQ0#UG^}vb$_?^8mVyqEsjY_L_MBk3mt=c81cv zw5SoPTKhiINF=;ll{DbV7YaUrz`cOL?aCfE9I+JW+*rVtFLG?$vQR&1rN$MvXeR__ zK0oFvt$qFrV|E;|awT!=0GyQKw75+;hk6SCzcl2WN35?{U#6rL+W4T=6QEV8)S^-1 zR&7Q&=Rb91&h>Gb7GVKUUDQ6>=z3)1j;&e%1xLqbM_yGgJJr$aKus^)j{-G!sscEO zif$kM&CI3A?C*~0oe=M+zP3%3KK;&brhoOb1?}jA+dS&9x4wL9`ezrXPkyq1L-o&X zh%i0VXFi;nd_OyS6!;b}`LaOjn%6$Da*Y2m){WZycO{q5UD8pII#eIJ@t z6%Tr3$|D$G`d=reIhRiT5b?P3TxP)$`4b)LYi={NiK>6yvNgPs`rh;ikL5N~as zv=!LJop8|u5?M{kD#w?OEj{I&uq205j@1ccrfkX8vJJ_esihmzOE&;Kp&z$4-C)Z` zcOT!KTr=^~#G!Nki^g-YRP|%m*~f1>D-hZJ^p%NtqbA(X@IOYXBcGcDVfWZs9!}#}gf8wgEMIXO4UE5kbzOMb+y!PKXNY%1b zQ7fsJ+d?X$j;P;U02*!_FVx%Pd`l1 zA8$FUwXr|mchPm>nR7c~ZSU`PnM-uRObT@PW6lgZ-xG1OzTdfiyPN#2duz$AM&sXk zp4!=9ywboz`AW;iUG>JluQx$?0q{qtsesfMpzv)V_0{D7&Y-3&5r-FC^+f{bgza)flvfOSGPR#Yw4|GQ1yB@I=P|m*fZuuZp}D%ZL+FDuhg$(H zr}|e3qa-tW+L!zxnZbhe1O0(Obv+QJ^M~(`(pB2KAWB5f47ys_QQL7FMB&bIMdkCf zuo_yrySw3$45WnpgS^it+kC#k2p0=snf3X881sknEoPsOiv)n|?C9LSb$?fnuV>fy zI`{1F5lkRQ*bJF$>I+5ul7KghY=LJBxC5ji#b*nM2bKFkz*SK00|BijpodiEE^Nor zZp=C{Llmk^Qm|tQGbD-kAUj6>g|3HKuCXbzY-MKI`a4#)X*+eZtR}f}tTkuC3Ixhf zZ5&&gGb>dKscD!~*uW7pj+}uu)g?`5*wgG?0>wF_M$EAmQ&p0i*m%x3@xYBeJkSI!8nohh7sHRE6IgC)nCw#ha-mjR}Jgc z7c>2V1D?zDw-0zUgA?5Ytm-l?TspZ7qxB*+p8@6%;3wif86n>=#8Q;9=KyXwE$Q+1lR$w<>ZeVc*kbN04)J*00MDs3JpJUQDGxjU4MG$M zcAdvfDfcsEz`(R+JsCyrvg~{9x zX3v|x6%@fjZ|{X?s(~ zwIt)L$dtG+FRjYi3EK=nfn{=pF)`}WcR^=wwXhdHsslmzCo|v^gW-PFhtSxD8pt+2 z{1U*%a9^-rd5;zF305vq-&?f?d>xVPy9T2XK~f;0@I4fufYv|9HnwX|_YU2w;Sp2z zHJme_KOBw#mISCN`h2Pp(BTRA1ggxn$YtHLw5x9PN#O-x!7VBNCuBJzMNxMRMv9)Z z5vt^0iTx|GDos}9XcOiAihk_hXzv{xv6Y|NdCj_XgaOaut{QQI&mLj_i|H^>6?X{c zxxF-LUNho6Rh{&vT`R7UmKCH5?WEvXp*lQ>z<8bAS@V&`8 zb0+wLV7vR&qe)|OU(z!6WZJeoXGXGxlvJGRNj4_MWb@ebX?8`KpA&z{SNu-@3_C~H2#f) Hg8Y8~L^Jz@ literal 0 HcmV?d00001 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/env_config.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/env_config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9feae7e034dc129c8f70e862afbdb20398149909 GIT binary patch literal 1946 zcmcIl-EZ4e6uqCPNChgJb1s%AXfN!>ahx^{|E zRVYBq8bO)0Q_%cMPF!2{adG!oKMbx(WZ+-oOI*_gz`uH<`< z&xg-BzjN;KZ_#KN!TNh`#HjEH-6k)sCHTbthd@-3hLT8QG}dI244j+%6;gcb)3$(+;LSs{c)(yO_A{*-|JO^&-3!Cnb zpShJ<<5HzDJ72%Pv9o&F{l3~Du?)u;Cv+}v8@a4vIzdfODFxGZ1gzTyoK+d3<1fes zaq#R9!d6AL&qipRZIf+cJkTcFgvLyxbzgS`#n{3P5QRlpy9>X#m2bp>cit4;&CeTO z{oENd)u7t|PFoVCh>;(wBsQ+qb~k=sE5>LZ z=f-u_9yO;vQWYa^O%>BEI-};}mX0TN9M3E2gp$^Mi3}2J!Z1xsw)ITjRP0Ad3btWd zqY#U%=CUax-Ip&q-4Kzi1g~D^+)Pi<-q)C+N4I6KdreV`(eWl-CeV$wQ=vAac;anBegWJQf*g(6GJe z$csy_|8YcI8r}!2^>=)u=(`S4BpvkV^x>1#yoN!UuL z*^7>zm>Ilu_LH-7)~Dwva_}!eF=pzSG26~diG-O`6>}_S+0yA#r%%PgPEeM$oGQ!I zza!Yiypc^~0;|Kpa6PD`!;_YvHKTsdfsrl=7@($v0JX!rUj@4X6T zWh?}g#Z6eoMs+$#jByh;_6bTrH^v7wey{|S=}V0!>+4uz3=xwh@Wp#y`)G&8)AZhR z&pE$){@io#`ynqc2O#?6R+syA5`af|p%7ZjwB3rP89;*`Kto!^!XAibf>n8m9s*(+ z$&y~Ghw`d>)M!mHYDU8lF4ZA~qty)MB5BPS*`p1qtlD4^628*a*R};46cJuR0?(!W3iWgl!etPDq6K4 zE9ezn7?qADzoO>Sal8nIRYIPk^%xW`Gl%zL7$#^n{>`BrF+mU}xA^z)|nYe4`x;h zr{p^dY4tF9jvRo3(qI6^fubJ;La}0vBqb;^+(9zYTH%Pohe_p3o;@FB-+^@YR$Z_h z9ej=jQL4=>S&b(xEo|PKmagBB#uub_$E7<vrXCK_*=9w&XtD9vy{6ecg=%ZUW z&d=3bq_3}}u1+F)NxB`8KE9cpS=ieJ2U~-EHZx8?&15j?*k@OymC5AHZ1U=a0t@2P zdM3>DzV(*iVWdi>iM%Bv^NHyBMD)v)|9XoExgjbjxNNoP(rOtW^62*Ubp%a&8_MpU zOn2TZlp-`W~dz}Fr&v5TB zoGsvRo^kXsX^kCe_%m*nPr?FvRe)HZQI(#4Jj~P!v0Se8qXceAyL!D^}dPVK^Dn zp4{Ccuy%)ULMe|nnwvp+vyGzLIKp#9Db=d+yfl`e93jc1-223 zo+&i!XdAqKI><6Fak$0t*M@%{4JcL!e*vYBfc{rd{2M6UQkTOv7%@lq+2%-S{g8DF OV8Ko2-}1~=AkaS%JSZ^$ literal 0 HcmV?d00001 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/logger_config.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/logger_config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a26a0fb3d041b758054a4f241a4202597748d8d GIT binary patch literal 5886 zcmbVQZ*UXG72lIi(n-3L?D(H-$wnBmUH=?76W)0uqg z+dbV$PDDt0WADD*xBK?(?)&pw{fXmT2)=(`8;stlM(AI-VSJ7vvGRK$rjdY#kw6KS zI5kX>+A?e*wRPAEwKYyBY{RyMeb`Rnadg~~V1^kASx_GmY^RZ6zi!bb4m&MKbY3rt zA9e{2(Hg5SHp{9}i@=Di;1oH*1tS7wtRgvT7u=_9!(M@faSt0q3*2dXxN5=@_N04Z zqHFo1%XfZt>hAQffJ@Q`sIjn zY^1mWQgOw`%1?oqMk0a(M)+{yU~2@cNW($UuuYp_6YW6TVLJ{uG$!^~xfq=m7{MvH zMs0#yq~TcX0tf#d`1gt~n9o!^XjZ5K`K(Y4a~?K^79N9jIXK2zjqOj3jfv8(RB|*r zmac=9%TGx^qUBp34QNa}H74^yR2nJXJyFsMCH`NjqVR?@lvJ$%rGFXfdXdf`-t0|w zl}0GEG1eFvvl~67x>DbiB?HGg=|-qrb_T`DwJVBH#vn2B%aTHV-2iqgSv)%Uo;15oXANE@dw*UH!q*nID&*tVZMW90Dt1#FJ53;Zw&a zUk-Id zt3!vnLW!=B5De|@3BA-48r(Awc1d`r5>{JdcpeMldCfi^k;J5;*%HU$k~J5o$B(9@ zL`2ctLPQabMgu=4Dm>;AmH1I9mEgtX3C(HtX)GU2MwMtJ9-S10uvMdFF+Qp<$c{^? zm^h;FQYxj$*mQ!y;Ae03_8gu#))k2!F0v2DC*K%}M7!k4i7|stjEr~5qI5!(y2c|T z$6<#>4i6mpcr+fDc|}Z&$3fdK;iQ#lTs{oSg4y#U#O}MtC-6yNrFnj`uD{GBx{Yp# zsYp&haSHv@U9%Vn&iF14WOu*)(jEVrpD`D==lmO0|Hhnui|XH!?av1~W}j9Ao3gvl zymXsk-{HQ`)W)ag8@E8eCm&dw zr9WjFS71S;*(Fg)OUV(_hoBQ*AMw1S&^wJvK2^yBaCA3fcFeSMEL;B!Yo4MM)9z!Y zT^pkiD&vxDQ{rH%jLWX(d@Zi$OXpU7U9Mn#&w-;wtmca|Gv)iOup{Hl22q$E(477I z`}qFeS9<$3df&jF{b7e@+ughKTYVBvXqw~TjspYx2KqD_6Pm5}z=8b-G-lUPLZ0YfFNHY#>kC1w#W{^b0NiCQP;$kfnniVF5tx_$f zIn&j6S&^WLg9D$4jE{o|!>J^khb3&blWnUly0QZYVWd0`#VNGtYr5>b;GEeodt%=A zbk4U;^=+H?ZO?Ld>H^b8&K+5-^-uSo>tAeWnSS%!o4B?A-2TNP&$lM$>rj0i(9YF` z)Vk1pFVpDCa)rlGRddc8RK3BO<_{bnvbWfI?{jm^bCM4h)f#n=ui(p3gl5n^Y_&rg z1AF9g*pqO=)VM_Cq#nRz2sIje6yid99K1RWk<0_mEvF#3rP9iHS{X4d8)jleM7-w= zWFW{}Co%v44XLWb82qOUe?%AT>u3@la>gEE#LTF8OBS4sr<@7~ z5#-A_Gu8}EGHvnnrd%1I_rb9qhlc#4)Iht8B!>^`wh>y36!*e?d<+ z!&cH$G0{b=$ym2km#xqKpL<}-WITKiw&ET*1&0grWjMhuH~=zQ-gjOvM))aD##3n* ztL)A5C`|=d#>1Nk=pkqCDbw2~ohb8%U{S%cN@+D3u{MJ$)rO=gZ^rv5?aS7wVB>=O z5%ydKD^#+zDnkbfJ!HS$(g=YC)Mu&>g;zdbHNo1Cb7b4O41g2qPR7gh0jw$yr zaJYvPN|@H@H;#!(jfo}|z*3R8^bP2iHbXJV7-(_w$*+t=LmejC97;b9Qgo23zr0N3 zyL98s-RxNar$z3?8%rPms3#b%m2gs)+M&>#ke2j3r7<#)IHzgMG5sATYn&t|QYXxJ znR5@prmzPI+opuW7D1R24%r$D63h{rD}SHjyPc}MEPR5vc3z>?e;%wkLokWzCLRo0w3d9p>^f1_1!GSAPT_XdE0AWK66( zHl-&026pMNcoh%tSzZSL@DwVrh^@-G1AlP`7HgVvHEn85+k8#?9NT`UzByOFUaen0 zUmwo8^Gr>S38+lq{GkPA{q2UMT*p?`w>8Tx zHgx9M#(Z6C-q)6|3p{Yze6B1DiaP6ZOtZ=~pN}pu9eJiU$E;PEwKL%b=5eB4i^{Z| z&p-@DzR*0ocedx|@Lc!>wef|lXVJGI&o-3{CSV2x*XI~K`MozTPhFT=V7l@Q zEY+$qtutFm3-(TgW8OoBZ^Xi@{rS53ybo3hJaF5Ie2*#r{KNti{+#SJ;Vien1WFsg zYuvXZwrQ~?lxx|nwru`j*M~3OdU3wx`Rx8Y>(8-mD%&=5Xn}oVF}OY#+@uCK<*WAO zt7>yqLA5HlVzaLG%(2Y{262HL+o7@@bK#x^_E~a2tt#6(^ZWw4`F0?PpDT@IVV}zQ z=KOtsp#RMMk^9}7bBzZVm{)Imo937%9n!fIDIpE`3*)uQzZzhVdeqyc^ee(3x<1qUG+)V99h zB%}+qcNWN$f(t$U+=s8-dTm8-4p=B#_v~N+L3Q&WW<0>8t}juw6YE|YSUS4 Td(@`$2QR;N0oxw6iKz8I3vn{( literal 0 HcmV?d00001 diff --git a/lzwcai_mcpskills_template/pyproject.toml b/lzwcai_mcpskills_template/pyproject.toml index 78d035c..411eb74 100644 --- a/lzwcai_mcpskills_template/pyproject.toml +++ b/lzwcai_mcpskills_template/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai_mcpskills_template" -version = "0.1.0" +version = "0.1.1" description = "MCP Server 模板项目 - 用于快速创建新的 MCP 服务" readme = "README.md" requires-python = ">=3.10" diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py index 4b8f395..ef76b10 100644 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py +++ b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py @@ -19,12 +19,12 @@ from mcp.server.models import InitializationOptions from mcp.server.stdio import stdio_server try: - from .schema_converter import convert_sql_params_to_input_schema + from .schema_converter import convert_sql_params_to_input_schema, sanitize_json_schema from .utils.api_client import execute_workflow, get_workflow_by_id, extract_final_output from .utils.env_config import get_workflow_id from .utils.logger_config import setup_system_logging, get_logger except ImportError: - from schema_converter import convert_sql_params_to_input_schema + from schema_converter import convert_sql_params_to_input_schema, sanitize_json_schema from utils.api_client import execute_workflow, get_workflow_by_id, extract_final_output from utils.env_config import get_workflow_id from utils.logger_config import setup_system_logging, get_logger @@ -174,13 +174,30 @@ async def handle_list_tools() -> list[types.Tool]: for query in _tools_config: name = query.get("name", "") description = query.get("description") or query.get("toolPrompt") or query.get("uniqueName", "") - sql_params = query.get("sqlParams", "[]") logger.debug(f"处理工具配置: name={name}, description={description[:50] if description else 'None'}...") - # 转换参数为 inputSchema - input_schema = convert_sql_params_to_input_schema(sql_params) - logger.debug(f"工具 {name} 的 inputSchema: {json.dumps(input_schema, ensure_ascii=False)}") + # 优先使用 inputJsonSchema,如果不存在则从 sqlParams 转换 + input_json_schema = query.get("inputJsonSchema") + if input_json_schema: + try: + # inputJsonSchema 是 JSON 字符串,需要解析 + if isinstance(input_json_schema, str): + input_schema = json.loads(input_json_schema) + else: + input_schema = input_json_schema + # 清理 schema,确保 enum 只包含字符串 + input_schema = sanitize_json_schema(input_schema) + logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}") + except (json.JSONDecodeError, TypeError) as e: + logger.warning(f"解析 inputJsonSchema 失败: {e},回退到 sqlParams") + sql_params = query.get("sqlParams", "[]") + input_schema = convert_sql_params_to_input_schema(sql_params) + else: + # 从 sqlParams 转换 + sql_params = query.get("sqlParams", "[]") + input_schema = convert_sql_params_to_input_schema(sql_params) + logger.debug(f"从 sqlParams 转换的 inputSchema: {json.dumps(input_schema, ensure_ascii=False)}") tools.append( types.Tool( diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py index 405489d..f497d4d 100644 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py +++ b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py @@ -12,6 +12,41 @@ import json from typing import Any +def sanitize_json_schema(schema: dict) -> dict: + """ + 清理 JSON Schema,确保 enum 字段只包含字符串值 + + Args: + schema: 原始 JSON Schema + + Returns: + dict: 清理后的 JSON Schema + """ + if not isinstance(schema, dict): + return schema + + # 递归处理 properties + if "properties" in schema and isinstance(schema["properties"], dict): + for prop_name, prop_schema in schema["properties"].items(): + if isinstance(prop_schema, dict) and "enum" in prop_schema: + enum_values = prop_schema["enum"] + if isinstance(enum_values, list) and len(enum_values) > 0: + # 检查是否是对象数组 + if isinstance(enum_values[0], dict): + # 提取 value 字段 + cleaned_enum = [ + item.get("value", item.get("label", "")) + for item in enum_values + if isinstance(item, dict) + ] + # 过滤空值 + prop_schema["enum"] = [v for v in cleaned_enum if v] + # 保留原始数据 + prop_schema["x-options"] = enum_values + + return schema + + def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]: """ 将单个参数转换为 JSON Schema property @@ -57,7 +92,22 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]: elif param_type == "select": property_schema["type"] = "string" if options: - property_schema["enum"] = options + # 处理 options 可能是对象数组 [{label, value}] 或字符串数组的情况 + if isinstance(options, list) and len(options) > 0: + if isinstance(options[0], dict): + # 对象数组,提取 value 字段作为 enum,过滤空值 + enum_values = [ + opt.get("value", opt.get("label", "")) + for opt in options + if isinstance(opt, dict) + ] + # 过滤掉空字符串 + property_schema["enum"] = [v for v in enum_values if v] + # 保留原始 options 供前端使用(如果前端支持) + property_schema["x-options"] = options + else: + # 字符串数组,直接使用,过滤空值 + property_schema["enum"] = [v for v in options if v] elif param_type == "number": property_schema["type"] = "number" diff --git a/lzwcai_workflow_to_mcp/main.py b/lzwcai_workflow_to_mcp/main.py index 4077d21..d1197e0 100644 --- a/lzwcai_workflow_to_mcp/main.py +++ b/lzwcai_workflow_to_mcp/main.py @@ -7,8 +7,8 @@ import os if __name__ == "__main__": # 设置环境变量 - os.environ["workflowId"] = "2005892514011795457" - os.environ["workflowExecuteKey"] = "wf_ce270212b2ee45ab9c81714a7c243c56" + os.environ["workflowId"] = "2019984063311556609" + os.environ["workflowExecuteKey"] = "wf_45bd630402664485a2ef5cd3ede5aa53" os.environ["backendBaseUrl"] = "http://192.168.2.236:8088" # Import and run the actual MCP server