diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log new file mode 100644 index 0000000..b9251e7 --- /dev/null +++ b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log @@ -0,0 +1,156 @@ +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 16:05:23 +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 +2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ +2026-01-19 16:05:23 - __main__ - INFO - [main.py:50] - ================================================================================ +2026-01-19 16:05:23 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 +2026-01-19 16:05:23 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 16:05:23 - __main__ - INFO - [main.py:53] - ================================================================================ +2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.168.11.24:3001/v1/parameters +2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76'} +2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} +2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 +2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} +2026-01-19 16:05:25 - __main__ - INFO - [main.py:68] - 传输模式: stdio +2026-01-19 16:05:25 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.168.11.24:3001/v1', 'app_sks': ['app-RM7IGxvdq48ixu1JXFiUZh76'], 'mode_type': 'workflow', 'transport': 'stdio'} +2026-01-19 16:05:26 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-19 16:05:26 - src.create_mcp - INFO - [create_mcp.py:104] - 工具 tool_HeTongShenChaGongZuoLiu 的 parameters 数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} +2026-01-19 16:05:26 - src.create_mcp - INFO - [create_mcp.py:110] - 工具 tool_HeTongShenChaGongZuoLiu 提取的文件字段: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_extensions': [], 'is_list': False}] +2026-01-19 16:06:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:144] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段信息: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_extensions': [], 'is_list': False}] +2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:145] - 工具 tool_HeTongShenChaGongZuoLiu 调用前的 arguments: {'contact_file': [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]} +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:64] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段变量名: {'contact_file'} +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:73] - 工具 tool_HeTongShenChaGongZuoLiu: 发现文件字段 contact_file,值: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:168] - 工具 tool_HeTongShenChaGongZuoLiu: 处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:85] - 工具 tool_HeTongShenChaGongZuoLiu: 准备处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:322] - 从URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 提取的文件扩展名: docx +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:333] - URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 匹配文件类型: document +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:96] - 工具 tool_HeTongShenChaGongZuoLiu: 自动识别文件类型为 document +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:242] - 开始上传远程文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:400] - 开始处理文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:86] - 正在下载文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx (尝试 1/3) +2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:100] - 文件下载成功: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 20445 字节) +2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:189] - 准备上传文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 0.02 MB) +2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:220] - 文件上传成功: eb25b2b233a8719f680d4b0b9c5a80e7.docx (ID: e2ec6720-9cf9-4a64-aa31-102e787a343b) +2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:417] - 已清理临时文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:255] - 文件上传成功 - ID: e2ec6720-9cf9-4a64-aa31-102e787a343b, 名称: eb25b2b233a8719f680d4b0b9c5a80e7.docx, 大小: 20445 bytes +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:274] - 文件预处理完成,共处理 1 个文件 +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:111] - 工具 tool_HeTongShenChaGongZuoLiu: 文件预处理完成,成功上传 1 个文件 +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:219] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 是 file 类型,输出单个对象 +2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:221] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 最终值: {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'} +2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:149] - 工具 tool_HeTongShenChaGongZuoLiu 处理后的 arguments: {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}} +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:131] - Sending data to Dify API: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:132] - Sending headers to Dify API: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76', 'Content-Type': 'application/json'} +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:133] - Sending url to Dify API: http://192.168.11.24:3001/v1/workflows/run +2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:148] - Response1:{'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} 400 BAD REQUEST +2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:152] - API request failed with status 400 +2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:153] - Response content: {"code": "invalid_param", "message": "File validation failed for file: eb25b2b233a8719f680d4b0b9c5a80e7.docx", "status": 400} + +2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:154] - Request data: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} +2026-01-19 16:06:14 - src.create_mcp - ERROR - [create_mcp.py:178] - 工具 tool_HeTongShenChaGongZuoLiu 调用 Dify API 失败: [400] invalid_param: File validation failed for file: eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 16:21:31 +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 +2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ +2026-01-19 16:21:31 - __main__ - INFO - [main.py:50] - ================================================================================ +2026-01-19 16:21:31 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 +2026-01-19 16:21:31 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 16:21:31 - __main__ - INFO - [main.py:53] - ================================================================================ +2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.168.11.24:3001/v1/parameters +2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76'} +2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} +2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 +2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} +2026-01-19 16:21:33 - __main__ - INFO - [main.py:68] - 传输模式: stdio +2026-01-19 16:21:33 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.168.11.24:3001/v1', 'app_sks': ['app-RM7IGxvdq48ixu1JXFiUZh76'], 'mode_type': 'workflow', 'transport': 'stdio'} +2026-01-19 16:21:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-19 16:21:37 - src.create_mcp - INFO - [create_mcp.py:104] - 工具 tool_HeTongShenChaGongZuoLiu 的 parameters 数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} +2026-01-19 16:21:37 - src.create_mcp - INFO - [create_mcp.py:110] - 工具 tool_HeTongShenChaGongZuoLiu 提取的文件字段: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_extensions': [], 'is_list': False}] +2026-01-19 16:21:46 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:144] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段信息: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_extensions': [], 'is_list': False}] +2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:145] - 工具 tool_HeTongShenChaGongZuoLiu 调用前的 arguments: {'contact_file': [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]} +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:64] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段变量名: {'contact_file'} +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:73] - 工具 tool_HeTongShenChaGongZuoLiu: 发现文件字段 contact_file,值: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:168] - 工具 tool_HeTongShenChaGongZuoLiu: 处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:85] - 工具 tool_HeTongShenChaGongZuoLiu: 准备处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:322] - 从URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 提取的文件扩展名: docx +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:333] - URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 匹配文件类型: document +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:96] - 工具 tool_HeTongShenChaGongZuoLiu: 自动识别文件类型为 document +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:242] - 开始上传远程文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:400] - 开始处理文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:86] - 正在下载文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx (尝试 1/3) +2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:100] - 文件下载成功: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 20445 字节) +2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:189] - 准备上传文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 0.02 MB) +2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:220] - 文件上传成功: eb25b2b233a8719f680d4b0b9c5a80e7.docx (ID: c09a25e0-9d92-4b8d-b098-a59d6be10921) +2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:417] - 已清理临时文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:255] - 文件上传成功 - ID: c09a25e0-9d92-4b8d-b098-a59d6be10921, 名称: eb25b2b233a8719f680d4b0b9c5a80e7.docx, 大小: 20445 bytes +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:274] - 文件预处理完成,共处理 1 个文件 +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:111] - 工具 tool_HeTongShenChaGongZuoLiu: 文件预处理完成,成功上传 1 个文件 +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:219] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 是 file 类型,输出单个对象 +2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:221] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 最终值: {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'} +2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:149] - 工具 tool_HeTongShenChaGongZuoLiu 处理后的 arguments: {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}} +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:131] - Sending data to Dify API: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}, 'response_mode': 'streaming', 'user': 'pp666'} +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:132] - Sending headers to Dify API: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76', 'Content-Type': 'application/json'} +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:133] - Sending url to Dify API: http://192.168.11.24:3001/v1/workflows/run +2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:148] - Response1:{'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}, 'response_mode': 'streaming', 'user': 'pp666'} 200 OK +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:09:39 +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 +2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ +2026-01-19 22:09:39 - __main__ - INFO - [main.py:50] - ================================================================================ +2026-01-19 22:09:39 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 +2026-01-19 22:09:39 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 22:09:39 - __main__ - INFO - [main.py:53] - ================================================================================ +2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters +2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'} +2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:09:55 +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 +2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ +2026-01-19 22:09:55 - __main__ - INFO - [main.py:50] - ================================================================================ +2026-01-19 22:09:55 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 +2026-01-19 22:09:55 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 22:09:55 - __main__ - INFO - [main.py:53] - ================================================================================ +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:10:01 +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 +2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ +2026-01-19 22:10:01 - __main__ - INFO - [main.py:50] - ================================================================================ +2026-01-19 22:10:01 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 +2026-01-19 22:10:01 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log +2026-01-19 22:10:01 - __main__ - INFO - [main.py:53] - ================================================================================ +2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters +2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'} +2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'paragraph': {'variable': 'contentText', 'label': '订单内容', 'type': 'paragraph', 'max_length': 48000, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['image', 'document'], 'allowed_file_extensions': []}}, {'file': {'variable': 'contentFile', 'label': '文件内容', 'type': 'file', 'max_length': 5, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document', 'image'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'} +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'paragraph': {'variable': 'contentText', 'label': '订单内容', 'type': 'paragraph', 'max_length': 48000, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['image', 'document'], 'allowed_file_extensions': []}}, {'file': {'variable': 'contentFile', 'label': '文件内容', 'type': 'file', 'max_length': 5, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document', 'image'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} +2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} +2026-01-19 22:10:13 - __main__ - INFO - [main.py:68] - 传输模式: stdio +2026-01-19 22:10:13 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.167.30.8:3001/v1', 'app_sks': ['app-0dlxPuFY8HBXo6PpoeStQCjA'], 'mode_type': 'workflow', 'transport': 'stdio'} +2026-01-19 22:10:17 - __main__ - INFO - [main.py:68] - 传输模式: stdio +2026-01-19 22:10:17 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.167.30.8:3001/v1', 'app_sks': ['app-0dlxPuFY8HBXo6PpoeStQCjA'], 'mode_type': 'workflow', 'transport': 'stdio'} 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 5bf8a91..d9aed5a 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' 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "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": {} }, { @@ -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 > 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 ; 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 50 ; 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 > 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": {} }, { @@ -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) > 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 ; 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 ; 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 ; 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 ; 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) > 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": {} }, { @@ -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) >= 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 ; 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 ; 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 ; 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 ; 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", + "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": {} }, { @@ -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)) >= 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 ; 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", + "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": {} }, { @@ -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.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 ; 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 ; 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.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": {} } ] \ 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 e9ed7b4..c653d64 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 @@ -668,3 +668,112 @@ 2026-01-10 00:47:46 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " 2026-01-10 00:47:46 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 2026-01-10 00:47:46 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log index 4466214..ae6a095 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,45 +1,109 @@ -2026-01-10 00:10:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-10 00:10:04 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-10 00:10:04 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-10 00:10:04 - 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-10 00:10:04 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +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": "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 ; 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 ; 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 ; 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 ; 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", + "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-10 00:10:04 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-10 00:10:04 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-10 00:10:04 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-10 00:26:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-10 00:26:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-10 00:26:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-10 00:26:28 - 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-10 00:26:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +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 ; 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", + "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-10 00:26:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-10 00:26:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-10 00:26:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-10 00:47:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-10 00:47:45 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-10 00:47:45 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-10 00:47:45 - 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-10 00:47:45 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +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 ; 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 ; 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.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-10 00:47:46 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-10 00:47:46 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-10 00:47:46 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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/lzwcai_mcp_sqlexecutor_daily.log.2026-01-10 b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-10 new file mode 100644 index 0000000..4466214 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-10 @@ -0,0 +1,45 @@ +2026-01-10 00:10:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-10 00:10:04 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-10 00:10:04 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-10 00:10:04 - 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-10 00:10:04 - 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 ; 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 ; 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 ; 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 ; 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", + "parameters": {}, + "testParams": {} +} +2026-01-10 00:10:04 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-10 00:10:04 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-10 00:10:04 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-10 00:26:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-10 00:26:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-10 00:26:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-10 00:26:28 - 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-10 00:26:28 - 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 ; 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-10 00:26:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-10 00:26:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-10 00:26:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-10 00:47:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-10 00:47:45 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-10 00:47:45 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-10 00:47:45 - 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-10 00:47:45 - 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 ; 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 ; 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-10 00:47:46 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-10 00:47:46 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-10 00:47:46 - 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 1360088..116f876 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 @@ -206,3 +206,37 @@ 2026-01-10 00:47:45 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning 2026-01-10 00:47:45 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... 2026-01-10 00:47:46 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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_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_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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +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调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml b/lzwcai_mcpskills_mfg_data_agent/pyproject.toml index 038d392..ed5e3b1 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.2" +version = "0.1.4" description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" readme = "README.md" requires-python = ">=3.13" 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 7ea5f89..fc1def5 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 ; 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 ;", + "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 ;", "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 ; 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 ; 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 ; 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 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 ;", "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 ; 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 ;", + "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 ;", "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 ; 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 ; 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 ;", + "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 ;", "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 = '标准产品' ; 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 ; 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 ; 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 ;", + "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 ;", "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 ; 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 ; 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 ; 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 ; 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 ;", + "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 ;", "parameters": {} } ] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log index 2d05afb..04d9750 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log @@ -400,3 +400,97 @@ 2026-01-09 20:43:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " 2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 2026-01-09 20:43:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:03 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs +2026-01-16 12:40:03 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-16 12:40:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-16 12:40:05 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:05 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:05 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplierEvaluationAndSmartReplenishment", + "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", + "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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:40:05 - 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:40:05 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:06 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:06 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:06 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:40:06 - 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:40:06 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesPerformanceIntelligentStatistics", + "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", + "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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:40:07 - 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:40:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:08 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:08 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard +2026-01-16 12:40:08 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:08 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:08 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "FinancialAnalyticsDashboard", + "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", + "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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:40:09 - 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:40:09 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:09 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:11 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:11 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:11 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "HumanResourcesAnalytics", + "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", + "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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-16 12:40:11 - 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:40:11 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log index 2d05afb..b786195 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -1,402 +1,94 @@ -2026-01-09 18:44:45 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs -2026-01-09 18:44:45 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 18:44:45 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 18:44:46 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 18:44:46 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 18:44:46 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 18:44:46 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 18:44:46 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 18:44:46 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 18:44:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:44:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 18:44:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:44:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:44:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 18:44:47 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:44:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:44:47 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 18:49:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:49:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics -2026-01-09 18:49:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:49:19 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:49:19 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesPerformanceIntelligentStatistics", - "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", - "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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 18:49:19 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:49:19 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:49:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 18:49:24 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:49:24 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard -2026-01-09 18:49:24 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:49:24 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:49:24 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "FinancialAnalyticsDashboard", - "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", - "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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 18:49:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:49:24 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:49:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 18:49:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:49:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel -2026-01-09 18:49:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:49:27 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:49:27 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SmartCostPredictionModel", - "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", - "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 = 'RAW' ; 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 ; 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 = 'RAW' 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 ; 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 = 'RAW' 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 18:49:27 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:49:27 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:49:27 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 18:57:52 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:57:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 18:57:52 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:57:52 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:57:52 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 18:57:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:57:52 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:57:52 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 18:58:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:58:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment -2026-01-09 18:58:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:58:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:58:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +2026-01-16 12:40:03 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs +2026-01-16 12:40:03 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-16 12:40:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-16 12:40:05 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:05 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:05 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { "datasourceId": "19", "businessName": "SupplierEvaluationAndSmartReplenishment", "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", - "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 = 'RAW' ; 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 ; 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 = 'RAW' ), 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 ;", + "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 ;", "parameters": {}, "testParams": {} } -2026-01-09 18:58:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:58:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:58:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 18:58:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:58:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 18:58:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:58:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 18:58:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +2026-01-16 12:40:05 - 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:40:05 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:06 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:06 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:06 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { "datasourceId": "19", "businessName": "SalesBIIntelligentAnalyticsPlatform", "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 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 ;", "parameters": {}, "testParams": {} } -2026-01-09 18:58:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:58:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:58:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:02:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:02:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 19:02:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:02:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:02:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:02:47 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:02:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:02:47 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:31:56 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs -2026-01-09 19:31:56 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 19:31:56 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 19:31:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 19:31:58 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 19:31:58 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 19:31:58 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 19:31:58 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 19:31:58 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 19:32:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:32:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment -2026-01-09 19:32:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:32:01 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:32:01 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SupplierEvaluationAndSmartReplenishment", - "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", - "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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:32:01 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:32:01 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:32:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:32:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:32:04 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 19:32:04 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:32:04 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:32:04 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:32:04 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:32:04 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:32:04 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:32:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:32:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics -2026-01-09 19:32:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:32:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:32:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +2026-01-16 12:40:06 - 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:40:06 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { "datasourceId": "19", "businessName": "SalesPerformanceIntelligentStatistics", "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", - "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 ; 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 ;", + "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 ;", "parameters": {}, "testParams": {} } -2026-01-09 19:32:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:32:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:32:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:32:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:32:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard -2026-01-09 19:32:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:32:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:32:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +2026-01-16 12:40:07 - 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:40:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:08 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:08 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard +2026-01-16 12:40:08 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:08 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:08 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { "datasourceId": "19", "businessName": "FinancialAnalyticsDashboard", "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", - "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 ; 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 ; 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 ;", + "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 ;", "parameters": {}, "testParams": {} } -2026-01-09 19:32:12 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:32:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:32:12 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:32:16 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:32:16 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel -2026-01-09 19:32:16 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:32:16 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:32:16 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SmartCostPredictionModel", - "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", - "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 = '标准产品' ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:32:16 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:32:16 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:32:16 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:32:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:32:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 19:32:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:32:33 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:32:33 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:32:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:32:34 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:32:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:34:32 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:34:32 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 19:34:32 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:34:32 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:34:32 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "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 ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:34:32 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:34:32 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:34:32 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:48:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:48:17 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment -2026-01-09 19:48:17 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:48:17 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:48:17 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SupplierEvaluationAndSmartReplenishment", - "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", - "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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:48:17 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:48:17 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:48:17 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 19:57:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 19:57:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics -2026-01-09 19:57:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 19:57:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 19:57:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SalesPerformanceIntelligentStatistics", - "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", - "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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 19:57:14 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 19:57:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 19:57:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 20:02:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 20:02:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard -2026-01-09 20:02:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 20:02:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 20:02:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "FinancialAnalyticsDashboard", - "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", - "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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 20:02:14 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 20:02:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 20:02:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 20:06:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 20:06:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel -2026-01-09 20:06:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 20:06:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 20:06:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "SmartCostPredictionModel", - "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", - "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 = '标准产品' ; 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 ; 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 20:06:58 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 20:06:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 20:06:58 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 20:23:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 20:23:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics -2026-01-09 20:23:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 20:23:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 20:23:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +2026-01-16 12:40:09 - 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:40:09 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:09 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:11 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:11 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-16 12:40:11 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { "datasourceId": "19", "businessName": "HumanResourcesAnalytics", "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", - "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 ; 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 ; 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 ; 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' WHERE p.department LIKE '%产线%' 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 WHERE department LIKE '%产线%' ), 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) / 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 ; 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 ;", + "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 ;", "parameters": {}, "testParams": {} } -2026-01-09 20:23:12 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 20:23:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 20:23:12 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 20:26:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 20:26:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics -2026-01-09 20:26:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 20:26:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 20:26:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "HumanResourcesAnalytics", - "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", - "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 ; 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 ; 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 ; 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' WHERE p.department LIKE '%产线%' 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 WHERE department LIKE '%产线%' ), 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) / 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 20:26:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 20:26:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 20:26:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 20:38:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 20:38:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics -2026-01-09 20:38:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 20:38:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 20:38:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "HumanResourcesAnalytics", - "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", - "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 ; 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 ; 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 ; 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' WHERE p.department LIKE '%产线%' 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 WHERE department LIKE '%产线%' ), 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) / 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 20:38:58 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 20:38:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 20:38:58 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 20:43:32 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs -2026-01-09 20:43:32 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 20:43:32 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 20:43:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 20:43:33 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 20:43:33 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 20:43:33 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 20:43:33 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 20:43:33 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 20:43:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 20:43:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics -2026-01-09 20:43:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "HumanResourcesAnalytics", - "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", - "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 ; 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 ; 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 ; 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) / 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 ; 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 ;", - "parameters": {}, - "testParams": {} -} -2026-01-09 20:43:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 20:43:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:11 - 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:40:11 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 new file mode 100644 index 0000000..2d05afb --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 @@ -0,0 +1,402 @@ +2026-01-09 18:44:45 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs +2026-01-09 18:44:45 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-09 18:44:45 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-09 18:44:46 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-09 18:44:46 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-09 18:44:46 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-09 18:44:46 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-09 18:44:46 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-09 18:44:46 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-09 18:44:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:44:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 18:44:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:44:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:44:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:44:47 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:44:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:44:47 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 18:49:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:49:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics +2026-01-09 18:49:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:49:19 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:49:19 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesPerformanceIntelligentStatistics", + "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", + "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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:49:19 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:49:19 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:49:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 18:49:24 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:49:24 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard +2026-01-09 18:49:24 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:49:24 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:49:24 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "FinancialAnalyticsDashboard", + "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", + "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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:49:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:49:24 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:49:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 18:49:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:49:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel +2026-01-09 18:49:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:49:27 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:49:27 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SmartCostPredictionModel", + "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", + "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 = 'RAW' ; 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 ; 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 = 'RAW' 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 ; 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 = 'RAW' 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:49:27 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:49:27 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:49:27 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 18:57:52 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:57:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 18:57:52 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:57:52 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:57:52 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:57:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:57:52 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:57:52 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 18:58:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:58:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment +2026-01-09 18:58:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:58:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:58:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplierEvaluationAndSmartReplenishment", + "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", + "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 = 'RAW' ; 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 ; 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 = 'RAW' ), 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:58:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:58:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:58:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 18:58:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 18:58:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 18:58:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 18:58:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 18:58:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 18:58:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 18:58:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 18:58:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:02:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:02:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 19:02:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:02:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:02:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:02:47 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:02:47 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:02:47 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:31:56 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs +2026-01-09 19:31:56 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-09 19:31:56 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-09 19:31:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-09 19:31:58 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-09 19:31:58 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-09 19:31:58 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-09 19:31:58 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-09 19:31:58 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-09 19:32:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:32:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment +2026-01-09 19:32:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:32:01 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:32:01 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplierEvaluationAndSmartReplenishment", + "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", + "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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:32:01 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:32:01 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:32:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:32:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:32:04 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 19:32:04 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:32:04 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:32:04 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:32:04 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:32:04 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:32:04 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:32:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:32:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics +2026-01-09 19:32:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:32:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:32:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesPerformanceIntelligentStatistics", + "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", + "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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:32:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:32:07 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:32:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:32:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:32:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard +2026-01-09 19:32:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:32:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:32:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "FinancialAnalyticsDashboard", + "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", + "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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:32:12 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:32:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:32:12 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:32:16 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:32:16 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel +2026-01-09 19:32:16 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:32:16 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:32:16 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SmartCostPredictionModel", + "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", + "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 = '标准产品' ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:32:16 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:32:16 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:32:16 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:32:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:32:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 19:32:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:32:33 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:32:33 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:32:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:32:34 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:32:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:34:32 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:34:32 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-09 19:34:32 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:34:32 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:34:32 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesBIIntelligentAnalyticsPlatform", + "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", + "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 ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:34:32 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:34:32 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:34:32 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:48:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:48:17 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment +2026-01-09 19:48:17 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:48:17 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:48:17 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplierEvaluationAndSmartReplenishment", + "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", + "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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:48:17 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:48:17 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:48:17 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 19:57:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 19:57:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics +2026-01-09 19:57:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 19:57:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 19:57:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SalesPerformanceIntelligentStatistics", + "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", + "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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 19:57:14 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 19:57:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 19:57:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 20:02:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 20:02:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard +2026-01-09 20:02:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 20:02:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 20:02:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "FinancialAnalyticsDashboard", + "businessDescription": "财务数据分析看板:覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解。", + "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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 20:02:14 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 20:02:14 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 20:02:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 20:06:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 20:06:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel +2026-01-09 20:06:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 20:06:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 20:06:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SmartCostPredictionModel", + "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", + "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 = '标准产品' ; 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 ; 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 20:06:58 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 20:06:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 20:06:58 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 20:23:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 20:23:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-09 20:23:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 20:23:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 20:23:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "HumanResourcesAnalytics", + "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", + "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 ; 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 ; 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 ; 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' WHERE p.department LIKE '%产线%' 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 WHERE department LIKE '%产线%' ), 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) / 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 20:23:12 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 20:23:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 20:23:12 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 20:26:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 20:26:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-09 20:26:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 20:26:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 20:26:12 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "HumanResourcesAnalytics", + "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", + "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 ; 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 ; 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 ; 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' WHERE p.department LIKE '%产线%' 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 WHERE department LIKE '%产线%' ), 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) / 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 20:26:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 20:26:13 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 20:26:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 20:38:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 20:38:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-09 20:38:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 20:38:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 20:38:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "HumanResourcesAnalytics", + "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", + "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 ; 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 ; 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 ; 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' WHERE p.department LIKE '%产线%' 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 WHERE department LIKE '%产线%' ), 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) / 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 20:38:58 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 20:38:58 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 20:38:58 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-09 20:43:32 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\logs +2026-01-09 20:43:32 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-09 20:43:32 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-09 20:43:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-09 20:43:33 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-09 20:43:33 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-09 20:43:33 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-09 20:43:33 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-09 20:43:33 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-09 20:43:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-09 20:43:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-09 20:43:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema +2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "HumanResourcesAnalytics", + "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", + "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 ; 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 ; 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 ; 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) / 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 ; 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 ;", + "parameters": {}, + "testParams": {} +} +2026-01-09 20:43:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " +2026-01-09 20:43:35 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-09 20:43:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log index f967584..886fc14 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log @@ -115,3 +115,34 @@ 2026-01-09 20:43:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics 2026-01-09 20:43:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... 2026-01-09 20:43:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-16 12:40:03 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-16 12:40:04 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:05 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:08 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard +2026-01-16 12:40:08 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:09 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-16 12:40:11 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml index 85f9e02..c165f8d 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.3" +version = "0.1.5" description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" readme = "README.md" requires-python = ">=3.13" diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/.gitkeep b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp.log b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp.log deleted file mode 100644 index c08ee56..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp.log +++ /dev/null @@ -1,979 +0,0 @@ -2025-12-20 18:03:07 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:272] - ================================================== -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:273] - Workflow MCP Server 启动 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:274] - ================================================== -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:279] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 使用模式: api -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': ' wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-20 18:03:07 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-20 18:03:07 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Sat, 20 Dec 2025 10:03:06 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:03:07 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Sat, 20 Dec 2025 10:03:06 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:290] - 开始运行 MCP Server (stdio 模式) -2025-12-20 18:03:07 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-20 18:03:08 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:03:08 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-20 18:03:08 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-20 18:03:08 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-20 18:03:12 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:03:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-20 18:03:12 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "Bearer wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 401, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'40f82dcb-2055-46b4-a9ae-1bbee5591306'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Content-Length', b'61'), (b'Date', b'Sat, 20 Dec 2025 10:03:10 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:03:12 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 401 " -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 401 -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '40f82dcb-2055-46b4-a9ae-1bbee5591306', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'content-length': '61', 'date': 'Sat, 20 Dec 2025 10:03:10 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - ERROR - [api_client.py:182] - [API错误] 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - ERROR - [api_client.py:183] - [API错误] 响应内容: {"msg":"API Key无效","code":401,"errorCode":"UNAUTHORIZED"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - ERROR - [main.py:236] - 工作流执行失败: 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 167, in execute_workflow - 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 '401 ' for url 'http://192.168.2.236:8088/open/workflow/execute' -For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 - -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_workflow_to_mcp\lzwcai_workflow_to_mcp\main.py", line 232, in handle_call_tool - result = execute_workflow(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 268, in execute_workflow - return default_client.execute_workflow(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 184, in execute_workflow - raise Exception(error_msg) -Exception: 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-20 18:04:18 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:272] - ================================================== -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:273] - Workflow MCP Server 启动 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:274] - ================================================== -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:279] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 使用模式: api -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-20 18:04:18 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-20 18:04:18 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Sat, 20 Dec 2025 10:04:16 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:04:18 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Sat, 20 Dec 2025 10:04:16 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:290] - 开始运行 MCP Server (stdio 模式) -2025-12-20 18:04:18 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-20 18:04:19 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:04:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-20 18:04:19 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-20 18:04:19 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-20 18:04:22 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:04:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-20 18:04:22 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'62311551-e7ab-4d5d-adef-f59b1969a4cf'), (b'X-RateLimit-Limit', b'100'), (b'X-RateLimit-Remaining', b'99'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Sat, 20 Dec 2025 10:04:22 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:04:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 200 " -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 200 -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '62311551-e7ab-4d5d-adef-f59b1969a4cf', 'x-ratelimit-limit': '100', 'x-ratelimit-remaining': '99', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Sat, 20 Dec 2025 10:04:22 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:170] - [API响应] 执行工作流成功 -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:171] - [API响应] Body: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "e6dcd942-1592-45bb-8af7-d7d25df338ed", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 2.206175, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/e6dcd942-1592-45bb-8af7-d7d25df338ed.json" - } -} -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.main - INFO - [main.py:233] - 工作流执行成功: 2002300699510763521 -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:234] - 工作流执行结果: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "e6dcd942-1592-45bb-8af7-d7d25df338ed", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 2.206175, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/e6dcd942-1592-45bb-8af7-d7d25df338ed.json" - } -} -2025-12-20 18:04:24 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:19:55 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:272] - ================================================== -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:273] - Workflow MCP Server 启动 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:274] - ================================================== -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:279] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 使用模式: api -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-23 18:19:55 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-23 18:19:55 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:19:54 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:19:55 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:19:54 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:290] - 开始运行 MCP Server (stdio 模式) -2025-12-23 18:19:55 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-23 18:19:56 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:19:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-23 18:19:56 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-23 18:19:56 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:20:00 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:20:00 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-23 18:20:00 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'2c574f18-2d6b-4580-bd8a-eda8c94c26eb'), (b'X-RateLimit-Limit', b'100'), (b'X-RateLimit-Remaining', b'99'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:20:00 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:20:01 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 200 " -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 200 -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '2c574f18-2d6b-4580-bd8a-eda8c94c26eb', 'x-ratelimit-limit': '100', 'x-ratelimit-remaining': '99', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:20:00 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:170] - [API响应] 执行工作流成功 -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:171] - [API响应] Body: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "d8ff9fc7-530c-4b77-a8a8-07d51aa79870", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 0.926932, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/d8ff9fc7-530c-4b77-a8a8-07d51aa79870.json" - } -} -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.main - INFO - [main.py:233] - 工作流执行成功: 2002300699510763521 -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:234] - 工作流执行结果: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "d8ff9fc7-530c-4b77-a8a8-07d51aa79870", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 0.926932, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/d8ff9fc7-530c-4b77-a8a8-07d51aa79870.json" - } -} -2025-12-23 18:20:01 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:24:44 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:275] - ================================================== -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:276] - Workflow MCP Server 启动 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:277] - ================================================== -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:285] - 使用模式: api -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-23 18:24:44 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-23 18:24:44 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:24:43 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:24:44 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:24:43 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:293] - 开始运行 MCP Server (stdio 模式) -2025-12-23 18:24:44 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-23 18:24:45 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:24:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-23 18:24:45 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-23 18:24:45 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:24:48 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:24:48 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-23 18:24:48 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'282e2c5e-e68e-48f5-bfdb-1e04ffc18682'), (b'X-RateLimit-Limit', b'100'), (b'X-RateLimit-Remaining', b'99'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:24:48 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:24:49 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 200 " -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 200 -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '282e2c5e-e68e-48f5-bfdb-1e04ffc18682', 'x-ratelimit-limit': '100', 'x-ratelimit-remaining': '99', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:24:48 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:170] - [API响应] 执行工作流成功 -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:171] - [API响应] Body: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "1f154317-ca19-4073-b7b5-a2b2beb38a3f", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 1.146157, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/1f154317-ca19-4073-b7b5-a2b2beb38a3f.json" - } -} -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.main - INFO - [main.py:233] - 工作流执行成功: 2002300699510763521 -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:234] - 工作流执行结果: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "1f154317-ca19-4073-b7b5-a2b2beb38a3f", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 1.146157, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/1f154317-ca19-4073-b7b5-a2b2beb38a3f.json" - } -} -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:294] - 提取 final_output 最后节点成功: key=f54a21ba-e3a3-461e-9a6b-e952087f4a5b -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:295] - 提取结果: {"q": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?"} -2025-12-23 18:24:49 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log deleted file mode 100644 index 198ab0a..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log +++ /dev/null @@ -1,506 +0,0 @@ -2025-12-23 18:19:55 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:272] - ================================================== -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:273] - Workflow MCP Server 启动 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:274] - ================================================== -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:279] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 使用模式: api -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-23 18:19:55 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-23 18:19:55 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:19:54 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:19:55 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:19:55 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:19:54 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-23 18:19:55 - lzwcai_workflow_to_mcp.main - INFO - [main.py:290] - 开始运行 MCP Server (stdio 模式) -2025-12-23 18:19:55 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-23 18:19:55 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-23 18:19:56 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:19:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-23 18:19:56 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-23 18:19:56 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-23 18:19:56 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:20:00 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:20:00 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-23 18:20:00 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:20:00 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:20:00 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'2c574f18-2d6b-4580-bd8a-eda8c94c26eb'), (b'X-RateLimit-Limit', b'100'), (b'X-RateLimit-Remaining', b'99'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:20:00 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:20:01 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 200 " -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:20:01 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 200 -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '2c574f18-2d6b-4580-bd8a-eda8c94c26eb', 'x-ratelimit-limit': '100', 'x-ratelimit-remaining': '99', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:20:00 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:170] - [API响应] 执行工作流成功 -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:171] - [API响应] Body: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "d8ff9fc7-530c-4b77-a8a8-07d51aa79870", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 0.926932, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/d8ff9fc7-530c-4b77-a8a8-07d51aa79870.json" - } -} -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.main - INFO - [main.py:233] - 工作流执行成功: 2002300699510763521 -2025-12-23 18:20:01 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:234] - 工作流执行结果: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "d8ff9fc7-530c-4b77-a8a8-07d51aa79870", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 0.926932, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!看起来你可能感兴趣于某方面,但还没具体说明?如果需要帮助,尽管告诉我,我很乐意帮忙解答或讨论各种话题!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/d8ff9fc7-530c-4b77-a8a8-07d51aa79870.json" - } -} -2025-12-23 18:20:01 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:24:44 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:275] - ================================================== -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:276] - Workflow MCP Server 启动 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:277] - ================================================== -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:285] - 使用模式: api -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-23 18:24:44 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-23 18:24:44 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:24:43 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:24:44 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:24:44 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:24:43 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-23 18:24:44 - lzwcai_workflow_to_mcp.main - INFO - [main.py:293] - 开始运行 MCP Server (stdio 模式) -2025-12-23 18:24:44 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-23 18:24:44 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-23 18:24:45 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:24:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-23 18:24:45 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-23 18:24:45 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-23 18:24:45 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-23 18:24:48 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-23 18:24:48 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-23 18:24:48 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 18:31:56", - "serviceId": "2002300699804364801", - "uniqueName": "3123123123-workflow服务_weimingminggongdefangshizuoliu001_91bc43ee", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"问题\",\"type\":\"string\",\"format\":\"paragraph\",\"maxLength\":3000}},\"required\":[\"query\"]}", - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-23 18:24:48 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-23 18:24:48 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'282e2c5e-e68e-48f5-bfdb-1e04ffc18682'), (b'X-RateLimit-Limit', b'100'), (b'X-RateLimit-Remaining', b'99'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Tue, 23 Dec 2025 10:24:48 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-23 18:24:49 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 200 " -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-23 18:24:49 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 200 -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '282e2c5e-e68e-48f5-bfdb-1e04ffc18682', 'x-ratelimit-limit': '100', 'x-ratelimit-remaining': '99', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Tue, 23 Dec 2025 10:24:48 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:170] - [API响应] 执行工作流成功 -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:171] - [API响应] Body: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "1f154317-ca19-4073-b7b5-a2b2beb38a3f", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 1.146157, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/1f154317-ca19-4073-b7b5-a2b2beb38a3f.json" - } -} -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.main - INFO - [main.py:233] - 工作流执行成功: 2002300699510763521 -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:234] - 工作流执行结果: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "1f154317-ca19-4073-b7b5-a2b2beb38a3f", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 1.146157, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/1f154317-ca19-4073-b7b5-a2b2beb38a3f.json" - } -} -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:294] - 提取 final_output 最后节点成功: key=f54a21ba-e3a3-461e-9a6b-e952087f4a5b -2025-12-23 18:24:49 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:295] - 提取结果: {"q": "你好!很高兴见到你。我注意到你用了特别的表情符号,是想表达亲切吗?🤗 如果你有任何问题或需要帮助,随时告诉我哦。今天想聊些什么呢?"} -2025-12-23 18:24:49 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log.2025-12-20 b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log.2025-12-20 deleted file mode 100644 index b9c9ce5..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log.2025-12-20 +++ /dev/null @@ -1,473 +0,0 @@ -2025-12-20 18:03:07 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:272] - ================================================== -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:273] - Workflow MCP Server 启动 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:274] - ================================================== -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:279] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 使用模式: api -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'Bearer wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-20 18:03:07 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-20 18:03:07 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Sat, 20 Dec 2025 10:03:06 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:03:07 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:03:07 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Sat, 20 Dec 2025 10:03:06 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-20 18:03:07 - lzwcai_workflow_to_mcp.main - INFO - [main.py:290] - 开始运行 MCP Server (stdio 模式) -2025-12-20 18:03:07 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-20 18:03:07 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-20 18:03:08 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:03:08 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-20 18:03:08 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-20 18:03:08 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-20 18:03:08 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-20 18:03:12 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:03:12 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-20 18:03:12 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "Bearer wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 401, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'40f82dcb-2055-46b4-a9ae-1bbee5591306'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Content-Length', b'61'), (b'Date', b'Sat, 20 Dec 2025 10:03:10 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:03:12 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 401 " -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:03:12 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 401 -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '40f82dcb-2055-46b4-a9ae-1bbee5591306', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'content-length': '61', 'date': 'Sat, 20 Dec 2025 10:03:10 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - ERROR - [api_client.py:182] - [API错误] 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - ERROR - [api_client.py:183] - [API错误] 响应内容: {"msg":"API Key无效","code":401,"errorCode":"UNAUTHORIZED"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - ERROR - [main.py:236] - 工作流执行失败: 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 167, in execute_workflow - 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 '401 ' for url 'http://192.168.2.236:8088/open/workflow/execute' -For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 - -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_workflow_to_mcp\lzwcai_workflow_to_mcp\main.py", line 232, in handle_call_tool - result = execute_workflow(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 268, in execute_workflow - return default_client.execute_workflow(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 184, in execute_workflow - raise Exception(error_msg) -Exception: 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-20 18:04:18 - root - INFO - [logger_config.py:117] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\logs -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'workflow_mcp_server' -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:272] - ================================================== -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:273] - Workflow MCP Server 启动 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:274] - ================================================== -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:279] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:282] - 使用模式: api -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:56] - [客户端初始化] base_url=http://192.168.2.236:8088 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:57] - [客户端初始化] token=wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:58] - [客户端初始化] execute_timeout=300.0s -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:89] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:90] - [API请求] Headers: {'X-API-Key': 'wf_buh230o9iaea4n6aefsddcexa7p27ydl'} -2025-12-20 18:04:18 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2025-12-20 18:04:18 - httpcore.connection - DEBUG - [_trace.py:45] - connect_tcp.complete return_value= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Sat, 20 Dec 2025 10:04:16 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:04:18 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2002300699510763521 "HTTP/1.1 200 " -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:04:18 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:97] - [API响应] HTTP 200 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:98] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json', 'transfer-encoding': 'chunked', 'date': 'Sat, 20 Dec 2025 10:04:16 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:103] - [API响应] 获取工作流配置成功: workflow_id=2002300699510763521 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:104] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } - ] -} -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null - } -] -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2002300699510763521, 配置数量: 1 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2025-12-20 18:04:18 - lzwcai_workflow_to_mcp.main - INFO - [main.py:290] - 开始运行 MCP Server (stdio 模式) -2025-12-20 18:04:18 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2025-12-20 18:04:18 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2025-12-20 18:04:19 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:04:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2025-12-20 18:04:19 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:179] - 处理工具配置: name=weimingminggongdefangshizuoliu001_91bc43ee, description=3123123123... -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:183] - 工具 weimingminggongdefangshizuoliu001_91bc43ee 的 inputSchema: {"type": "object", "properties": {"query": {"description": "问题", "type": "string", "format": "paragraph", "maxLength": 3000}}, "required": ["query"]} -2025-12-20 18:04:19 - lzwcai_workflow_to_mcp.main - INFO - [main.py:193] - ListTools 响应: 返回 1 个工具 -2025-12-20 18:04:19 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2025-12-20 18:04:22 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2025-12-20 18:04:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2025-12-20 18:04:22 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - INFO - [main.py:203] - 收到 CallTool 请求: name=weimingminggongdefangshizuoliu001_91bc43ee, arguments={"query": "你好啊"} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:216] - 找到工具配置: { - "id": "2002300699812753409", - "createBy": "yy8z9", - "createTime": "2025-12-20 16:51:02", - "updateBy": "yy8z9", - "updateTime": "2025-12-20 17:59:26", - "serviceId": "2002300699804364801", - "uniqueName": "未命名工的方式作流001", - "name": "weimingminggongdefangshizuoliu001_91bc43ee", - "description": "3123123123", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"query\",\"displayName\":\"问题\",\"maxLength\":3000,\"defaultValue\":\"\",\"required\":true}]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": null, - "outputJsonSchema": null, - "lastExecutionTime": null -} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - INFO - [main.py:220] - 使用工作流ID: 2002300699510763521 -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.main - INFO - [main.py:228] - 执行工作流请求数据: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:150] - [执行工作流] URL: http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:151] - [执行工作流] Token: wf_buh230o9iaea4n6aefsddcexa7p27ydl -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:152] - [执行工作流] Headers: {"X-API-Key": "wf_buh230o9iaea4n6aefsddcexa7p27ydl", "Content-Type": "application/json", "Accept": "*/*"} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:153] - [执行工作流] 请求参数: { - "workflowId": "2002300699510763521", - "inputs": { - "query": "你好啊" - } -} -2025-12-20 18:04:22 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:154] - [执行工作流] 超时时间: 300.0s -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.started request= -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_headers.complete -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.started request= -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - send_request_body.complete -2025-12-20 18:04:22 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.started request= -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Trace-Id', b'62311551-e7ab-4d5d-adef-f59b1969a4cf'), (b'X-RateLimit-Limit', b'100'), (b'X-RateLimit-Remaining', b'99'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Content-Type', b'application/json;charset=UTF-8'), (b'Transfer-Encoding', b'chunked'), (b'Date', b'Sat, 20 Dec 2025 10:04:22 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2025-12-20 18:04:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/open/workflow/execute "HTTP/1.1 200 " -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.started request= -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - receive_response_body.complete -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.started -2025-12-20 18:04:24 - httpcore.http11 - DEBUG - [_trace.py:45] - response_closed.complete -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:164] - [API响应] HTTP 200 -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:165] - [API响应] Headers: {'vary': 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers', 'x-trace-id': '62311551-e7ab-4d5d-adef-f59b1969a4cf', 'x-ratelimit-limit': '100', 'x-ratelimit-remaining': '99', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json;charset=UTF-8', 'transfer-encoding': 'chunked', 'date': 'Sat, 20 Dec 2025 10:04:22 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:170] - [API响应] 执行工作流成功 -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:171] - [API响应] Body: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "e6dcd942-1592-45bb-8af7-d7d25df338ed", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 2.206175, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/e6dcd942-1592-45bb-8af7-d7d25df338ed.json" - } -} -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.main - INFO - [main.py:233] - 工作流执行成功: 2002300699510763521 -2025-12-20 18:04:24 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:234] - 工作流执行结果: { - "msg": "操作成功", - "code": 200, - "data": { - "success": true, - "execution_id": "e6dcd942-1592-45bb-8af7-d7d25df338ed", - "workflow_id": "3dc9d954-c270-4bbf-aa3d-5945c6acb2f5", - "status": "success", - "duration": 2.206175, - "nodes_executed": 3, - "errors_count": 0, - "final_output": { - "da9400b0-4754-4842-8a5e-dc20e2868cc6": { - "query": "你好啊" - }, - "a480a344-7fa9-439b-8db6-79b136a79736": { - "text": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - }, - "f54a21ba-e3a3-461e-9a6b-e952087f4a5b": { - "q": "你好呀!✨ 很高兴见到你!今天过得怎么样呀?希望你度过了愉快的一天。我今天刚整理完知识库,准备了很多有趣的话题和实用的信息,希望能和你一起探讨、学习。不知道你今天想聊些什么呢?无论是有趣的故事、需要解决的问题,还是纯粹想聊聊日常,我都很乐意陪伴你聊天哦!😊" - } - }, - "error": null, - "log_id": "logs/3dc9d954-c270-4bbf-aa3d-5945c6acb2f5/e6dcd942-1592-45bb-8af7-d7d25df338ed.json" - } -} -2025-12-20 18:04:24 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_error.log b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_error.log deleted file mode 100644 index a0a8755..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_error.log +++ /dev/null @@ -1,23 +0,0 @@ -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - ERROR - [api_client.py:182] - [API错误] 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.utils.api_client - ERROR - [api_client.py:183] - [API错误] 响应内容: {"msg":"API Key无效","code":401,"errorCode":"UNAUTHORIZED"} -2025-12-20 18:03:12 - lzwcai_workflow_to_mcp.main - ERROR - [main.py:236] - 工作流执行失败: 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 167, in execute_workflow - 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 '401 ' for url 'http://192.168.2.236:8088/open/workflow/execute' -For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 - -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_workflow_to_mcp\lzwcai_workflow_to_mcp\main.py", line 232, in handle_call_tool - result = execute_workflow(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 268, in execute_workflow - return default_client.execute_workflow(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_workflow_to_mcp\lzwcai_workflow_to_mcp\utils\api_client.py", line 184, in execute_workflow - raise Exception(error_msg) -Exception: 执行工作流API请求失败 (HTTP 401): http://192.168.2.236:8088/open/workflow/execute diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__init__.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__init__.py index 53da55d..892d416 100644 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__init__.py +++ b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__init__.py @@ -10,7 +10,10 @@ from .api_client import ( process_workflow_response, get_default_client, DEFAULT_TIMEOUT, - WORKFLOW_EXECUTE_TIMEOUT + WORKFLOW_EXECUTE_TIMEOUT, + DEFAULT_POLLING_INTERVAL, + DEFAULT_MAX_POLL_COUNT, + DEFAULT_MAX_ERROR_COUNT, ) from .env_config import get_workflow_id, get_backend_base_url, get_env_config, set_env_variable @@ -26,6 +29,9 @@ __all__ = [ 'get_default_client', 'DEFAULT_TIMEOUT', 'WORKFLOW_EXECUTE_TIMEOUT', + 'DEFAULT_POLLING_INTERVAL', + 'DEFAULT_MAX_POLL_COUNT', + 'DEFAULT_MAX_ERROR_COUNT', 'get_workflow_id', 'get_backend_base_url', 'get_env_config', diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/__init__.cpython-312.pyc b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/__init__.cpython-312.pyc index a0546a9..2000b8d 100644 Binary files a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/__init__.cpython-312.pyc and b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/api_client.cpython-312.pyc b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/api_client.cpython-312.pyc index eea50fb..d25d8eb 100644 Binary files a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/api_client.cpython-312.pyc and b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/api_client.cpython-312.pyc differ diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/api_client.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/api_client.py index 8658ef0..cedb2a2 100644 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/api_client.py +++ b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/api_client.py @@ -5,7 +5,9 @@ import httpx import json -from typing import Dict, Any, Optional, List +import os +import time +from typing import Dict, Any, Optional, List, Tuple try: from .env_config import get_backend_base_url, get_workflow_execute_key @@ -18,7 +20,64 @@ logger = get_logger(__name__) # 默认超时配置(秒) DEFAULT_TIMEOUT = 30.0 # 普通请求超时 -WORKFLOW_EXECUTE_TIMEOUT = 300.0 # 工作流执行超时(5分钟) +WORKFLOW_EXECUTE_TIMEOUT = 600.0 # 工作流执行超时(10分钟,参考 workflow-execution-api.md) + +# 异步轮询默认配置(参考 workflow-execution-api.md) +DEFAULT_POLLING_INTERVAL = 1.0 +DEFAULT_MAX_POLL_COUNT = 120 +DEFAULT_MAX_ERROR_COUNT = 3 + + +def _get_polling_config() -> Tuple[float, int, int]: + """ + 获取轮询配置(允许用环境变量覆盖) + + Env: + - workflowPollingIntervalSeconds: float, 默认 1 + - workflowMaxPollCount: int, 默认 120 + - workflowMaxErrorCount: int, 默认 3 + """ + try: + interval = float(os.environ.get("workflowPollingIntervalSeconds", str(DEFAULT_POLLING_INTERVAL))) + except ValueError: + interval = DEFAULT_POLLING_INTERVAL + + try: + max_polls = int(os.environ.get("workflowMaxPollCount", str(DEFAULT_MAX_POLL_COUNT))) + except ValueError: + max_polls = DEFAULT_MAX_POLL_COUNT + + try: + max_errors = int(os.environ.get("workflowMaxErrorCount", str(DEFAULT_MAX_ERROR_COUNT))) + except ValueError: + max_errors = DEFAULT_MAX_ERROR_COUNT + + # 避免被错误配置成 0 或负数 + interval = interval if interval > 0 else DEFAULT_POLLING_INTERVAL + max_polls = max_polls if max_polls > 0 else DEFAULT_MAX_POLL_COUNT + max_errors = max_errors if max_errors > 0 else DEFAULT_MAX_ERROR_COUNT + + return interval, max_polls, max_errors + + +def _extract_async_ids(execute_response: Dict[str, Any]) -> Optional[Tuple[str, str, str]]: + """ + 从执行接口响应中提取 (status, execution_id, workflow_id) + 兼容 snake_case / camelCase。 + """ + if not isinstance(execute_response, dict): + return None + data = execute_response.get("data") + if not isinstance(data, dict): + return None + + status = data.get("status") + execution_id = data.get("execution_id") or data.get("executionId") + workflow_id = data.get("workflow_id") or data.get("workflowId") + + if status and execution_id and workflow_id: + return str(status), str(execution_id), str(workflow_id) + return None class WorkflowAPIClient: @@ -170,7 +229,20 @@ class WorkflowAPIClient: logger.info(f"[API响应] 执行工作流成功") logger.debug(f"[API响应] Body: {json.dumps(data, ensure_ascii=False, indent=2)}") - return data + # 兼容:如果服务端返回 async 的 execution_id/workflow_id,则按文档轮询状态接口直到结束 + async_info = _extract_async_ids(data) + if async_info is None: + return data + + status, execution_id, workflow_id = async_info + if status not in ("pending", "running"): + # 有些服务端可能直接返回 success/failed 但仍带 execution_id + return data + + logger.info( + f"[执行工作流] 检测到异步执行: status={status}, execution_id={execution_id}, workflow_id={workflow_id}" + ) + return self.poll_workflow_result(execution_id=execution_id, workflow_id=workflow_id) except httpx.TimeoutException: error_msg = f"执行工作流API请求超时: {url}" @@ -193,6 +265,88 @@ class WorkflowAPIClient: logger.error(f"[API错误] {error_msg}", exc_info=True) raise Exception(error_msg) + def get_workflow_execute_status(self, execution_id: str, workflow_id: str) -> Dict[str, Any]: + """ + 查询异步执行状态 + + GET /open/workflow/execute/status/{executionId}?workflowId={workflowId} + """ + url = f"{self.base_url}/open/workflow/status/{execution_id}" + try: + logger.info(f"[API请求] GET {url}") + response = self.client.get( + url, + headers=self._get_headers(), + params={"workflowId": workflow_id}, + ) + logger.info(f"[API响应] HTTP {response.status_code}") + response.raise_for_status() + data = response.json() + logger.debug(f"[API响应] Body: {json.dumps(data, ensure_ascii=False, indent=2)}") + return data + except httpx.TimeoutException: + error_msg = f"查询工作流执行状态超时: {url}" + logger.error(f"[API错误] {error_msg}") + raise Exception(error_msg) + except httpx.HTTPStatusError as e: + error_msg = f"查询工作流执行状态失败 (HTTP {e.response.status_code}): {url}" + logger.error(f"[API错误] {error_msg}") + logger.error(f"[API错误] 响应内容: {e.response.text}") + raise Exception(error_msg) + except httpx.RequestError as e: + error_msg = f"查询工作流执行状态请求异常: {url}, 错误: {str(e)}" + logger.error(f"[API错误] {error_msg}") + raise Exception(error_msg) + except Exception as e: + error_msg = f"处理查询执行状态响应时出错: {str(e)}" + logger.error(f"[API错误] {error_msg}", exc_info=True) + raise Exception(error_msg) + + def poll_workflow_result(self, execution_id: str, workflow_id: str) -> Dict[str, Any]: + """轮询直到工作流执行完成(success/failed)或超时""" + interval, max_polls, max_errors = _get_polling_config() + error_count = 0 + + logger.info( + f"[轮询] 开始轮询执行状态: execution_id={execution_id}, workflow_id={workflow_id}, " + f"interval={interval}s, max_polls={max_polls}, max_errors={max_errors}" + ) + + for i in range(max_polls): + try: + resp = self.get_workflow_execute_status(execution_id=execution_id, workflow_id=workflow_id) + if resp.get("code") != 200: + error_count += 1 + logger.warning(f"[轮询] 状态接口返回非200(code={resp.get('code')}), error_count={error_count}") + if error_count >= max_errors: + raise Exception(f"轮询连续错误次数过多: code={resp.get('code')}, msg={resp.get('msg')}") + time.sleep(interval) + continue + + error_count = 0 + data = resp.get("data") or {} + status = data.get("status") + + logger.info(f"[轮询] 第{i+1}/{max_polls}次: status={status}") + + if status in ("pending", "running"): + time.sleep(interval) + continue + if status in ("success", "failed"): + return resp + + # 兜底:未知状态 + raise Exception(f"未知执行状态: {status}") + + except Exception as e: + error_count += 1 + logger.warning(f"[轮询] 轮询异常: {e}, error_count={error_count}") + if error_count >= max_errors: + raise + time.sleep(interval) + + raise Exception("轮询超时:超过最大轮询次数仍未完成") + def close(self): """关闭HTTP客户端""" if self._client is not None: diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/workflow-execution-api.md b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/workflow-execution-api.md new file mode 100644 index 0000000..c042932 --- /dev/null +++ b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/workflow-execution-api.md @@ -0,0 +1,321 @@ +# 工作流执行接口文档 + +## 概述 +工作流执行分为**同步执行**和**异步执行**两种模式,通过 `async` 参数控制。 + +--- + +## 1. 调试执行接口 + +### 接口信息 +- **URL**: `POST /open/workflow/execute` +- **超时**: 600000ms (10分钟) + +### 请求参数 +```json +{ + "workflow": { + "nodes": [...], + "edges": [...] + }, + "inputs": { + "param1": "value1", + "param2": "value2" + }, + "saveLog": false, + "async": true +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| workflow | Object | 是 | 工作流配置(节点和边) | +| inputs | Object | 否 | 开始节点的输入参数 | +| saveLog | Boolean | 否 | 是否保存执行日志 | +| async | Boolean | 否 | 是否异步执行(默认 false) | + +--- + +## 2. 响应处理 + +### 2.1 同步执行响应 (async=false) +直接返回完整执行结果: + +```json +{ + "code": 200, + "data": { + "success": true, + "status": "success", + "duration": 2.5, + "nodes_executed": 5, + "errors_count": 0, + "final_output": {...}, + "execution_history": [ + { + "node_id": "node_1", + "node_type": "start", + "status": "success", + "inputs": {...}, + "outputs": {...}, + "error": null, + "duration": 0.1, + "timestamp": "2024-01-20T10:00:00" + } + ] + } +} +``` + +### 2.2 异步执行响应 (async=true) +返回执行 ID,需要轮询查询状态: + +```json +{ + "code": 200, + "data": { + "status": "pending", + "execution_id": "exec_123456", + "workflow_id": "wf_789" + } +} +``` + +--- + +## 3. 异步执行状态查询 + +### 接口信息 +- **URL**: `GET /open/workflow/status/{executionId}?workflowId={workflowId}` + +### 请求参数 +| 参数 | 位置 | 类型 | 必填 | 说明 | +|------|------|------|------|------| +| executionId | Path | String | 是 | 执行ID | +| workflowId | Query | String | 是 | 工作流ID | + +### 响应数据 +```json +{ + "code": 200, + "data": { + "status": "success", + "success": true, + "execution_id": "exec_123456", + "workflow_id": "wf_789", + "duration": 2.5, + "nodes_executed": 5, + "errors_count": 0, + "final_output": {...}, + "execution_history": [...] + } +} +``` + +### 状态值说明 +| 状态 | 说明 | 处理方式 | +|------|------|----------| +| pending | 等待执行 | 继续轮询 | +| running | 执行中 | 继续轮询 | +| success | 执行成功 | 停止轮询,显示结果 | +| failed | 执行失败 | 停止轮询,显示错误 | + +--- + +## 4. 轮询策略 + +### 前端实现参数 +```javascript +const POLLING_INTERVAL = 1000; // 轮询间隔:1秒 +const MAX_POLL_COUNT = 120; // 最大轮询次数:2分钟 +const MAX_ERROR_COUNT = 3; // 最大连续错误次数 +``` + +### 轮询逻辑 +1. 提交异步执行请求,获取 `execution_id` 和 `workflow_id` +2. 每隔 1 秒调用状态查询接口 +3. 根据返回的 `status` 判断: + - `pending/running`: 继续轮询 + - `success/failed`: 停止轮询,处理结果 +4. 超过 120 次轮询或连续 3 次错误,停止轮询 + +--- + +## 5. Python 实现示例 + +### 5.1 同步执行 +```python +import requests + +def execute_workflow_sync(workflow_config, inputs=None): + """同步执行工作流""" + url = "http://your-api/api/system/workflowManage/debug" + + payload = { + "workflow": workflow_config, + "inputs": inputs or {}, + "saveLog": False, + "async": False + } + + response = requests.post(url, json=payload, timeout=600) + result = response.json() + + if result["code"] == 200: + return result["data"] + else: + raise Exception(f"执行失败: {result.get('msg')}") +``` + +### 5.2 异步执行 + 轮询 +```python +import requests +import time + +def execute_workflow_async(workflow_config, inputs=None): + """异步执行工作流并轮询结果""" + # 1. 提交异步执行 + url = "http://your-api/api/system/workflowManage/debug" + payload = { + "workflow": workflow_config, + "inputs": inputs or {}, + "saveLog": False, + "async": True + } + + response = requests.post(url, json=payload, timeout=600) + result = response.json() + + if result["code"] != 200: + raise Exception(f"提交失败: {result.get('msg')}") + + execution_id = result["data"]["execution_id"] + workflow_id = result["data"]["workflow_id"] + + # 2. 轮询执行状态 + status_url = f"http://your-api/api/system/workflowManage/status/{execution_id}" + params = {"workflowId": workflow_id} + + max_polls = 120 + poll_interval = 1 + error_count = 0 + + for i in range(max_polls): + try: + response = requests.get(status_url, params=params) + result = response.json() + + if result["code"] == 200: + error_count = 0 # 重置错误计数 + data = result["data"] + status = data["status"] + + if status == "success": + return data + elif status == "failed": + raise Exception(f"执行失败: {data.get('error')}") + elif status in ["pending", "running"]: + time.sleep(poll_interval) + continue + else: + raise Exception(f"未知状态: {status}") + else: + error_count += 1 + if error_count >= 3: + raise Exception("连续错误次数过多") + time.sleep(poll_interval) + + except requests.RequestException as e: + error_count += 1 + if error_count >= 3: + raise Exception(f"网络异常: {str(e)}") + time.sleep(poll_interval) + + raise Exception("执行超时") +``` + +### 5.3 完整使用示例 +```python +# 工作流配置 +workflow_config = { + "nodes": [ + { + "id": "start_1", + "type": "start", + "data": { + "config": { + "inputs": [ + {"name": "text", "type": "string", "required": True} + ] + } + } + }, + # ... 其他节点 + ], + "edges": [ + {"source": "start_1", "target": "node_2"} + ] +} + +# 输入参数 +inputs = { + "text": "Hello World" +} + +# 同步执行 +try: + result = execute_workflow_sync(workflow_config, inputs) + print("执行成功:", result) +except Exception as e: + print("执行失败:", e) + +# 异步执行 +try: + result = execute_workflow_async(workflow_config, inputs) + print("执行成功:", result) +except Exception as e: + print("执行失败:", e) +``` + +--- + +## 6. 响应数据结构 + +### execution_history 节点执行记录 +```python +{ + "node_id": "node_1", # 节点ID + "node_type": "llm", # 节点类型 + "status": "success", # 执行状态 + "inputs": {...}, # 输入参数 + "outputs": {...}, # 输出结果 + "error": None, # 错误信息 + "duration": 1.2, # 执行耗时(秒) + "timestamp": "2024-01-20..." # 时间戳 +} +``` + +### 节点类型枚举 +- `start`: 开始节点 +- `end`: 结束节点 +- `llm`: 大模型节点 +- `code`: 代码节点 +- `condition`: 条件分支 +- `loop`: 循环节点 +- `http`: HTTP请求 +- `knowledge`: 知识库 +- `agent`: 智能体 +- `tool`: 工具节点 +- `variable`: 变量节点 +- `template`: 模板节点 +- `subflow`: 子流程 + +--- + +## 7. 注意事项 + +1. **超时设置**: 调试接口超时时间为 10 分钟,适合长时间运行的工作流 +2. **异步推荐**: 对于复杂工作流,建议使用异步模式避免超时 +3. **轮询频率**: 建议 1 秒轮询一次,避免过于频繁 +4. **错误处理**: 需要处理网络异常、超时、连续错误等情况 +5. **日志保存**: `saveLog` 参数控制是否保存执行日志到数据库 diff --git a/lzwcai_workflow_to_mcp/main.py b/lzwcai_workflow_to_mcp/main.py index 80107e5..4077d21 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"] = "2002300699510763521" - os.environ["workflowExecuteKey"] = "wf_buh230o9iaea4n6aefsddcexa7p27ydl" + os.environ["workflowId"] = "2005892514011795457" + os.environ["workflowExecuteKey"] = "wf_ce270212b2ee45ab9c81714a7c243c56" os.environ["backendBaseUrl"] = "http://192.168.2.236:8088" # Import and run the actual MCP server diff --git a/lzwcai_workflow_to_mcp/pyproject.toml b/lzwcai_workflow_to_mcp/pyproject.toml index 753605b..9b6cab5 100644 --- a/lzwcai_workflow_to_mcp/pyproject.toml +++ b/lzwcai_workflow_to_mcp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-workflow-to-mcp" -version = "0.1.1" +version = "0.1.2" description = "MCP server for executing business SQL queries with dynamic tool generation" readme = "README.md" requires-python = ">=3.13"