From d71458669a70468d692bd2b48e2f32a995af9989 Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Thu, 11 Jun 2026 18:56:15 +0800 Subject: [PATCH] =?UTF-8?q?```=20docs:=20=E5=88=A0=E9=99=A4=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=96=87=E6=A1=A3=E5=92=8C=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除主 README.md 文件,包含项目概述、安装指南和配置示例 移除 lzwcai_mcp_sqlexecutor 模块的完整文档、配置文件和初始化文件 删除 businessQueries.json 配置模板和 .python-version 版本指定文件 更新 .gitignore 文件以清理 Python 缓存文件 ``` --- README.md | 68 - lzwcai_mcp_sqlexecutor/README.md | 138 --- .../__pycache__/main.cpython-312.pyc | Bin 820 -> 0 bytes .../lzwcai_mcp_sqlexecutor/.gitignore | 10 - .../lzwcai_mcp_sqlexecutor/.python-version | 1 - .../lzwcai_mcp_sqlexecutor/README.md | 154 --- .../lzwcai_mcp_sqlexecutor/__init__.py | 9 - .../businessQueries.json | 1 - .../logs/lzwcai_mcp_sqlexecutor.log | 289 ----- .../logs/lzwcai_mcp_sqlexecutor_daily.log | 289 ----- .../logs/lzwcai_mcp_sqlexecutor_error.log | 73 -- .../logs/mcp_services.log | 149 --- .../lzwcai_mcp_sqlexecutor/main.py | 268 ---- .../lzwcai_mcp_sqlexecutor/pyproject.toml | 35 - .../lzwcai_mcp_sqlexecutor/utils/__init__.py | 24 - .../utils/api_client.py | 209 ---- .../utils/env_config.py | 87 -- .../utils/json_helper.py | 60 - .../utils/logger_config.py | 489 -------- .../utils/name_helper.py | 41 - .../utils/schema_helper.py | 93 -- .../lzwcai_mcp_sqlexecutor/uv.lock | 497 -------- lzwcai_mcp_sqlexecutor/main.py | 20 - lzwcai_mcp_sqlexecutor/pyproject.toml | 38 - lzwcai_mcp_sqlexecutor/uv.lock | 842 ------------- lzwcai_mcpskills_analyzeWorkOrder/README.md | 138 --- .../.gitignore | 10 - .../.python-version | 1 - .../README.md | 154 --- .../__init__.py | 9 - .../businessQueries.json | 35 - .../lzwcai_mcpskills_analyzeWorkOrder/main.py | 373 ------ .../pyproject.toml | 35 - .../lzwcai_mcpskills_analyzeWorkOrder/sql11 | 169 --- .../utils/__init__.py | 25 - .../utils/api_client.py | 318 ----- .../utils/env_config.py | 106 -- .../utils/json_helper.py | 60 - .../utils/logger_config.py | 489 -------- .../utils/name_helper.py | 41 - .../utils/schema_helper.py | 166 --- .../lzwcai_mcpskills_analyzeWorkOrder/uv.lock | 497 -------- lzwcai_mcpskills_analyzeWorkOrder/main.py | 13 - .../pyproject.toml | 38 - lzwcai_mcpskills_mfg_data_agent/README.md | 138 --- .../html/EfficiencyOutputLossDashboard.html | 543 -------- .../MetricTrendAndTurningPointWarning.html | 791 ------------ .../html/OnePageDecisionBrief.html | 567 --------- .../html/OrderDelayWarningAnalysis.html | 626 ---------- .../html/SupplyChainRiskWarning.html | 680 ---------- .../WorkOrderProgressAndAnomalyNodes.html | 696 ----------- .../.gitignore | 10 - .../.python-version | 1 - .../lzwcai_mcpskills_mfg_data_agent/README.md | 154 --- .../__init__.py | 9 - .../businessQueries.json | 50 - .../logs/lzwcai_mcp_sqlexecutor.log | 922 -------------- .../logs/lzwcai_mcp_sqlexecutor_daily.log | 143 --- ...zwcai_mcp_sqlexecutor_daily.log.2026-01-08 | 362 ------ ...zwcai_mcp_sqlexecutor_daily.log.2026-01-09 | 139 --- ...zwcai_mcp_sqlexecutor_daily.log.2026-01-10 | 45 - ...zwcai_mcp_sqlexecutor_daily.log.2026-01-26 | 109 -- .../logs/lzwcai_mcp_sqlexecutor_error.log | 0 .../logs/mcp_services.log | 295 ----- .../lzwcai_mcpskills_mfg_data_agent/main.py | 373 ------ .../pyproject.toml | 35 - .../lzwcai_mcpskills_mfg_data_agent/sql11 | 169 --- .../utils/__init__.py | 25 - .../utils/api_client.py | 332 ----- .../utils/env_config.py | 106 -- .../utils/json_helper.py | 60 - .../utils/logger_config.py | 489 -------- .../utils/name_helper.py | 41 - .../utils/schema_helper.py | 166 --- .../lzwcai_mcpskills_mfg_data_agent/uv.lock | 497 -------- lzwcai_mcpskills_mfg_data_agent/main.py | 13 - .../manufacturing_data_model_v1.0.0.md | 1094 ----------------- .../pyproject.toml | 38 - .../sql/一页式决策简报.sql | 317 ----- .../sql/人效产值损耗三维模型仪表盘.sql | 193 --- .../sql/供应链风险预警.sql | 416 ------- .../sql/工单执行进度与异常节点.sql | 409 ------ .../sql/指标趋势分析与拐点预警.sql | 426 ------- .../sql/订单延迟预警分析.sql | 154 --- lzwcai_mcpskills_mfg_data_agent/test.py | 111 -- lzwcai_mcpskills_mfg_data_agentv2/README.md | 138 --- .../.gitignore | 10 - .../.python-version | 1 - .../README.md | 154 --- .../__init__.py | 9 - .../businessQueries.json | 50 - .../logs/lzwcai_mcp_sqlexecutor.log | 496 -------- .../logs/lzwcai_mcp_sqlexecutor_daily.log | 94 -- ...zwcai_mcp_sqlexecutor_daily.log.2026-01-09 | 402 ------ .../logs/lzwcai_mcp_sqlexecutor_error.log | 0 .../logs/mcp_services.log | 148 --- .../lzwcai_mcpskills_mfg_data_agentv2/main.py | 373 ------ .../pyproject.toml | 35 - .../lzwcai_mcpskills_mfg_data_agentv2/sql11 | 169 --- .../utils/__init__.py | 25 - .../utils/api_client.py | 332 ----- .../utils/env_config.py | 106 -- .../utils/json_helper.py | 60 - .../utils/logger_config.py | 489 -------- .../utils/name_helper.py | 41 - .../utils/schema_helper.py | 166 --- .../lzwcai_mcpskills_mfg_data_agentv2/uv.lock | 497 -------- lzwcai_mcpskills_mfg_data_agentv2/main.py | 14 - .../manufacturing_data_model_v1.0.0.md | 1094 ----------------- .../pyproject.toml | 38 - lzwcai_mcpskills_mfg_data_agentv2/技能清单.md | 10 - lzwcai_mcpskills_template/README.md | 65 - .../lzwcai_mcpskills_template/.python-version | 1 - .../__pycache__/main.cpython-312.pyc | Bin 14593 -> 0 bytes .../schema_converter.cpython-312.pyc | Bin 5537 -> 0 bytes .../logs/lzwcai_mcpskills_template.log | 89 -- .../logs/lzwcai_mcpskills_template_daily.log | 89 -- .../logs/lzwcai_mcpskills_template_error.log | 8 - .../lzwcai_mcpskills_template/main.py | 345 ------ .../schema_converter.py | 185 --- .../tools_config.json | 58 - .../utils/__init__.py | 19 - .../__pycache__/__init__.cpython-312.pyc | Bin 707 -> 0 bytes .../__pycache__/api_client.cpython-312.pyc | Bin 7295 -> 0 bytes .../__pycache__/env_config.cpython-312.pyc | Bin 1946 -> 0 bytes .../__pycache__/json_helper.cpython-312.pyc | Bin 1940 -> 0 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 5886 -> 0 bytes .../utils/api_client.py | 197 --- .../utils/env_config.py | 60 - .../utils/json_helper.py | 41 - .../utils/logger_config.py | 130 -- lzwcai_mcpskills_template/main.py | 15 - lzwcai_mcpskills_template/pyproject.toml | 31 - lzwcai_mcpskills_visual2url/README.md | 37 - .../.python-version | 1 - .../__pycache__/main.cpython-312.pyc | Bin 12742 -> 0 bytes .../schema_converter.cpython-312.pyc | Bin 5534 -> 0 bytes .../lzwcai_mcpskills_visual2url/main.py | 72 -- lzwcai_mcpskills_visual2url/main.py | 9 - lzwcai_mcpskills_visual2url/pyproject.toml | 31 - lzwcai_workflow_to_mcp/README.md | 39 - .../lzwcai_workflow_to_mcp/.python-version | 1 - .../lzwcai_workflow_to_mcp/README.md | 0 .../__pycache__/main.cpython-312.pyc | Bin 17061 -> 0 bytes .../schema_converter.cpython-312.pyc | Bin 9905 -> 0 bytes .../businessQueries.json | 80 -- .../logs/lzwcai_workflow_to_mcp.log | 137 --- .../logs/lzwcai_workflow_to_mcp_daily.log | 137 --- .../logs/lzwcai_workflow_to_mcp_error.log | 0 .../lzwcai_workflow_to_mcp/main.py | 358 ------ .../lzwcai_workflow_to_mcp/pyproject.toml | 18 - .../schema_converter.py | 304 ----- .../lzwcai_workflow_to_mcp/utils/__init__.py | 39 - .../__pycache__/__init__.cpython-312.pyc | Bin 1073 -> 0 bytes .../__pycache__/api_client.cpython-312.pyc | Bin 22890 -> 0 bytes .../__pycache__/env_config.cpython-312.pyc | Bin 2917 -> 0 bytes .../__pycache__/json_helper.cpython-312.pyc | Bin 2311 -> 0 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 7496 -> 0 bytes .../__pycache__/name_helper.cpython-312.pyc | Bin 1543 -> 0 bytes .../__pycache__/schema_helper.cpython-312.pyc | Bin 3042 -> 0 bytes .../utils/api_client.py | 500 -------- .../utils/env_config.py | 85 -- .../utils/json_helper.py | 59 - .../utils/logger_config.py | 174 --- .../utils/name_helper.py | 40 - .../utils/schema_helper.py | 94 -- .../workflow-execution-api.md | 321 ----- lzwcai_workflow_to_mcp/main.py | 16 - lzwcai_workflow_to_mcp/pyproject.toml | 38 - 169 files changed, 27179 deletions(-) delete mode 100644 README.md delete mode 100644 lzwcai_mcp_sqlexecutor/README.md delete mode 100644 lzwcai_mcp_sqlexecutor/__pycache__/main.cpython-312.pyc delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/.gitignore delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/.python-version delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/README.md delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/__init__.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/businessQueries.json delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_error.log delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/main.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/__init__.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/api_client.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/env_config.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/json_helper.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/logger_config.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/name_helper.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/schema_helper.py delete mode 100644 lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/uv.lock delete mode 100644 lzwcai_mcp_sqlexecutor/main.py delete mode 100644 lzwcai_mcp_sqlexecutor/pyproject.toml delete mode 100644 lzwcai_mcp_sqlexecutor/uv.lock delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/README.md delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.gitignore delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.python-version delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/README.md delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/__init__.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/main.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/sql11 delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/__init__.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/api_client.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/env_config.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/json_helper.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/logger_config.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/name_helper.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/schema_helper.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/uv.lock delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/main.py delete mode 100644 lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml delete mode 100644 lzwcai_mcpskills_mfg_data_agent/README.md delete mode 100644 lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html delete mode 100644 lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html delete mode 100644 lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html delete mode 100644 lzwcai_mcpskills_mfg_data_agent/html/OrderDelayWarningAnalysis.html delete mode 100644 lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html delete mode 100644 lzwcai_mcpskills_mfg_data_agent/html/WorkOrderProgressAndAnomalyNodes.html delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.gitignore delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.python-version delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/README.md delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/__init__.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-10 delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_error.log delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/main.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/pyproject.toml delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/sql11 delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/__init__.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/api_client.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/env_config.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/json_helper.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/logger_config.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/name_helper.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/schema_helper.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/uv.lock delete mode 100644 lzwcai_mcpskills_mfg_data_agent/main.py delete mode 100644 lzwcai_mcpskills_mfg_data_agent/manufacturing_data_model_v1.0.0.md delete mode 100644 lzwcai_mcpskills_mfg_data_agent/pyproject.toml delete mode 100644 lzwcai_mcpskills_mfg_data_agent/sql/一页式决策简报.sql delete mode 100644 lzwcai_mcpskills_mfg_data_agent/sql/人效产值损耗三维模型仪表盘.sql delete mode 100644 lzwcai_mcpskills_mfg_data_agent/sql/供应链风险预警.sql delete mode 100644 lzwcai_mcpskills_mfg_data_agent/sql/工单执行进度与异常节点.sql delete mode 100644 lzwcai_mcpskills_mfg_data_agent/sql/指标趋势分析与拐点预警.sql delete mode 100644 lzwcai_mcpskills_mfg_data_agent/sql/订单延迟预警分析.sql delete mode 100644 lzwcai_mcpskills_mfg_data_agent/test.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/README.md delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_error.log delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/main.py delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/技能清单.md delete mode 100644 lzwcai_mcpskills_template/README.md delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/.python-version delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/main.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/schema_converter.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template.log delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__init__.py delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/api_client.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/env_config.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/json_helper.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/logger_config.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/api_client.py delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/env_config.py delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/json_helper.py delete mode 100644 lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/logger_config.py delete mode 100644 lzwcai_mcpskills_template/main.py delete mode 100644 lzwcai_mcpskills_template/pyproject.toml delete mode 100644 lzwcai_mcpskills_visual2url/README.md delete mode 100644 lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/.python-version delete mode 100644 lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/__pycache__/main.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/__pycache__/schema_converter.cpython-312.pyc delete mode 100644 lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/main.py delete mode 100644 lzwcai_mcpskills_visual2url/main.py delete mode 100644 lzwcai_mcpskills_visual2url/pyproject.toml delete mode 100644 lzwcai_workflow_to_mcp/README.md delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/.python-version delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/README.md delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/__pycache__/main.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/__pycache__/schema_converter.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/businessQueries.json delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp.log delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_error.log delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/pyproject.toml delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__init__.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/api_client.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/env_config.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/json_helper.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/logger_config.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/name_helper.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/schema_helper.cpython-312.pyc delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/api_client.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/env_config.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/json_helper.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/logger_config.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/name_helper.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/schema_helper.py delete mode 100644 lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/workflow-execution-api.md delete mode 100644 lzwcai_workflow_to_mcp/main.py delete mode 100644 lzwcai_workflow_to_mcp/pyproject.toml diff --git a/README.md b/README.md deleted file mode 100644 index 38d5a1f..0000000 --- a/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# lzwcai-mcp-server-package - -MCP (Model Context Protocol) 服务器工具集,为 AI 助手提供企业级业务能力扩展。 - -## 📦 包含模块 - -| 模块 | 版本 | 说明 | -|------|------|------| -| [lzwcai-mcp-iot](./lzwcai_mcp_iot) | 0.3.3 | IoT 设备控制服务器,支持设备查询、定位和控制 | -| [lzwcai-mcp-sqlexecutor](./lzwcai_mcp_sqlexecutor) | 0.1.8 | SQL 查询执行服务器,支持动态工具生成 | -| [lzwcai-mcp-api-converter](./lzwcai_mcp_api_converter) | 0.1.30 | API 转换服务器,将业务 API 转换为 MCP 工具 | -| [lzwcai-demp-tool-server-dify-to-mcp](./lzwcai_demp_tool_server_dify_to_mcp) | 0.1.4 | Dify 集成工具,将 Dify 模型部署到 MCP | -| [lzwcai-demp-tool-server-dify-to-mcp-test](./lzwcai_demp_tool_server_dify_to_mcp_test) | 0.1.0 | Dify 集成工具测试版 | - -## 🚀 快速安装 - -```bash -# IoT 设备控制 -pip install lzwcai-mcp-iot - -# SQL 查询执行 -pip install lzwcai-mcp-sqlexecutor - -# API 转换器 -pip install lzwcai-mcp-api-converter - -# Dify 集成 -pip install lzwcai-demp-tool-server-dify-to-mcp -``` - -## �️ 打包 与发布 - -```bash -# 进入子模块目录 -cd lzwcai_mcp_iot - -# 使用 uv 打包 -uv build - -# 上传到管理端技能广场 -# 将 dist/ 目录下的 .tar.gz 文件上传至技能广场 -``` - -## 🔧 MCP 客户端配置示例 - -```json -{ - "mcpServers": { - "iot": { - "command": "lzwcai-mcp-iot" - }, - "sql": { - "command": "lzwcai-mcp-sqlexecutor" - }, - "api": { - "command": "lzwcai-mcp-api-converter" - } - } -} -``` - -## 📄 许可证 - -专有软件 - 版权所有 © LZWCAI开发团队 - -## 📧 联系方式 - -- 邮箱:dev@lzwcai.com diff --git a/lzwcai_mcp_sqlexecutor/README.md b/lzwcai_mcp_sqlexecutor/README.md deleted file mode 100644 index 44ee71d..0000000 --- a/lzwcai_mcp_sqlexecutor/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# lzwcai-mcp-sqlexecutor - -一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录 -- 🌐 中文支持:工具名称自动转换为拼音 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcp-sqlexecutor -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcp_sqlexecutor -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcp-sqlexecutor -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcp-sqlexecutor -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcp_sqlexecutor.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "lzwcai-sqlexecutor": { - "command": "lzwcai-mcp-sqlexecutor" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "用户订单查询", - "businessDescription": "根据用户ID查询订单信息", - "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", - "parameters": { - "type": "object", - "required": ["userId"], - "properties": { - "userId": { - "type": "integer", - "description": "用户的唯一标识符", - "examples": [10086] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcp_sqlexecutor - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcp_sqlexecutor.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 许可证 - -MIT License - -## 作者 - -lzwcai - diff --git a/lzwcai_mcp_sqlexecutor/__pycache__/main.cpython-312.pyc b/lzwcai_mcp_sqlexecutor/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 7730fb54bd94f9743b969e74f53fda8638537ffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 820 zcmbtS!EVz)5S_I*v1_$ZseqJn$ps0c#&t}aH~}HV0ca07AaTiM<$AX?&BhM410(_o zBr5oW@(G}R3YUN)AuI$!z=2yJH%{!jDWq3e$>W*ld2hx$^Lk;y0CA{xYWEWY_#%U& zqfDKNEu4Lz&;*L8!ch}Z^(izJrc&HK4-n}JFy&yHj-apf6*D)e8n;+Cj9W<<^-DbT zJznHqFYtF*RNM}uA|KrMy;iB?cT4eI&K@woCsHlryA*2pQ@QMgg;m8f`6$djBpE2p z?opF}5P{`pf~hMTKbYpCf8mQ?{V#rw=Ku1kMs==s@FPH=z(bh1Ixy!NsX@KO+wx+z zK~vRPvr{c@w>THkl4GyCZn;*gtl8D-deyOOZhB(N^V=+-*97Bs#0Q0)BySIafW zDpy>~v7C#Qx@)^`B(K0kDeic!z|5se7^fNw?zN&YNR5~!lx=%Go}_0FheO10$n0<= zFPb|PneU3S-W^6H7>n(J;xA45R=1z!WJVWwydlhE_=@w-HlJ+1!-Y4v@M7gP;4!jN{ z -cd lzwcai_mcp_sqlexecutor -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcp-sqlexecutor -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcp-sqlexecutor -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcp_sqlexecutor.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "lzwcai-sqlexecutor": { - "command": "lzwcai-mcp-sqlexecutor" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "用户订单查询", - "businessDescription": "根据用户ID查询订单信息", - "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", - "parameters": { - "type": "object", - "required": ["userId"], - "properties": { - "userId": { - "type": "integer", - "description": "用户的唯一标识符", - "examples": [10086] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcp_sqlexecutor - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcp_sqlexecutor.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 常见问题 - -### MCP Inspector 显示 JSON 解析错误 - -如果在使用 MCP Inspector 测试时遇到 `SyntaxError: Unexpected non-whitespace character after JSON` 错误,这是因为: - -1. **原因**:MCP 协议使用 stdio(标准输入输出)进行 JSON-RPC 通信,任何输出到 stdout 的内容(如 print 语句或控制台日志)都会破坏 JSON 格式。 - -2. **解决方案**:本服务器已将所有日志输出配置为仅写入文件(位于 `logs/` 目录),不输出到控制台。日志文件包括: - - `lzwcai_mcp_sqlexecutor.log` - 主日志文件 - - `lzwcai_mcp_sqlexecutor_error.log` - 错误日志 - - `lzwcai_mcp_sqlexecutor_daily.log` - 按日期滚动的日志 - - `mcp_services.log` - MCP 服务专用日志 - -3. **查看日志**:如果需要调试,请查看 `logs/` 目录下的日志文件。 - -## 许可证 - -MIT License - -## 作者 - -lzwcai - diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/__init__.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/__init__.py deleted file mode 100644 index 5df6d9c..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -lzwcai-mcp-sqlexecutor - MCP server for executing business SQL queries -""" - -__version__ = "0.1.2" -__author__ = "lzwcai" - -__all__ = [] - diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/businessQueries.json b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/businessQueries.json deleted file mode 100644 index 0637a08..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/businessQueries.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log deleted file mode 100644 index fd68929..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor.log +++ /dev/null @@ -1,289 +0,0 @@ -2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接... -2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098 -2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 " -2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具 -2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628 -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API... -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: { - "datasourceId": "240", - "businessName": "qian50tiaodaorushujumingxichaxun_32b1d628", - "businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。", - "sqlTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "parameters": {}, - "testParams": {} -} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 240 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: qian50tiaodaorushujumingxichaxun_32b1d628 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50; -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:144] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:153] - 正在调用测试SQL API: http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-05-25 16:05:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - test_sql_with_schema 接口返回的数据: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - HTTP状态码: 200 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:173] - 响应数据类型: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应数据内容: { - "msg": "操作成功", - "code": 200, - "data": { - "resultCount": 6, - "data": [ - { - "记录ID": 80001, - "任务ID": "3320260418001", - "原始数据": "{\"name\": \"张三\", \"mobile\": \"13800000101\", \"dept\": \"销售部\", \"amount\": 1200}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:19:19.000+08:00" - }, - { - "记录ID": 80002, - "任务ID": "3320260418002", - "原始数据": "{\"name\": \"李四\", \"mobile\": \"13800000102\", \"dept\": \"财务部\", \"amount\": 980}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:19:34.000+08:00" - }, - { - "记录ID": 80003, - "任务ID": "3320260418003", - "原始数据": "{\"name\": \"王五\", \"mobile\": \"13800000103\", \"dept\": \"技术部\", \"amount\": 1500}", - "处理结果": "导入失败", - "错误原因": "字段校验失败:mobile格式不正确", - "处理状态": "FAILED", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:19:49.000+08:00" - }, - { - "记录ID": 80004, - "任务ID": "3320260418004", - "原始数据": "{\"name\": \"赵六\", \"mobile\": \"13800000104\", \"dept\": \"人事部\", \"amount\": 0}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:20:04.000+08:00" - }, - { - "记录ID": 80005, - "任务ID": "3320260418005", - "原始数据": "{\"name\": \"孙七\", \"mobile\": \"13800000105\", \"dept\": \"运营部\", \"amount\": 2000}", - "处理结果": "导入失败", - "错误原因": "目标表写入失败:唯一键冲突", - "处理状态": "FAILED", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:20:19.000+08:00" - }, - { - "记录ID": 80006, - "任务ID": "3320260418006", - "原始数据": "{\"name\": \"周八\", \"mobile\": \"13800000106\", \"dept\": \"法务部\", \"amount\": 300}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:20:34.000+08:00" - } - ], - "databaseName": "import_log_db_168", - "businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。", - "originalTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "convertedTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "executionStatus": "success", - "businessName": "qian50tiaodaorushujumingxichaxun_32b1d628", - "testParams": {}, - "errorMessage": null, - "executionTime": 8, - "datasourceId": "240", - "logId": "2058821884796174336", - "executableSql": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "datasourceName": "import_log_db_168" - } -} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应code: 200 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - 响应msg: 操作成功 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:178] - 响应data: {'resultCount': 6, 'data': [{'记录ID': 80001, '任务ID': '3320260418001', '原始数据': '{"name": "张三", "mobile": "13800000101", "dept": "销售部", "amount": 1200}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:19.000+08:00'}, {'记录ID': 80002, '任务ID': '3320260418002', '原始数据': '{"name": "李四", "mobile": "13800000102", "dept": "财务部", "amount": 980}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:34.000+08:00'}, {'记录ID': 80003, '任务ID': '3320260418003', '原始数据': '{"name": "王五", "mobile": "13800000103", "dept": "技术部", "amount": 1500}', '处理结果': '导入失败', '错误原因': '字段校验失败:mobile格式不正确', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:49.000+08:00'}, {'记录ID': 80004, '任务ID': '3320260418004', '原始数据': '{"name": "赵六", "mobile": "13800000104", "dept": "人事部", "amount": 0}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:04.000+08:00'}, {'记录ID': 80005, '任务ID': '3320260418005', '原始数据': '{"name": "孙七", "mobile": "13800000105", "dept": "运营部", "amount": 2000}', '处理结果': '导入失败', '错误原因': '目标表写入失败:唯一键冲突', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:19.000+08:00'}, {'记录ID': 80006, '任务ID': '3320260418006', '原始数据': '{"name": "周八", "mobile": "13800000106", "dept": "法务部", "amount": 300}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:34.000+08:00'}], 'databaseName': 'import_log_db_168', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'originalTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'convertedTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'executionStatus': 'success', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'testParams': {}, 'errorMessage': None, 'executionTime': 8, 'datasourceId': '240', 'logId': '2058821884796174336', 'executableSql': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'datasourceName': 'import_log_db_168'} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:181] - 测试SQL API调用成功 -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功 -2026-05-25 16:15:08 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:15:08 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:15:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 API,skill_id: 2058819964077572098 -2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 " -2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具 -2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: -2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/ -2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -Traceback (most recent call last): - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions - yield - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request - resp = self._pool.handle_request(req) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request - raise exc from None - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request - response = connection.handle_request( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request - raise exc - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request - stream = self._connect(request) - ^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect - stream = self._network_backend.connect_tcp(**kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp - with map_exceptions(exc_map): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions - raise to_exc(exc) from exc -httpcore.ConnectError: [Errno 11001] getaddrinfo failed - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id - response = self.client.get(url, headers=self._get_headers()) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get - return self.request( - ^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request - return self.send(request, auth=auth, follow_redirects=follow_redirects) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send - response = self._send_handling_auth( - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth - response = self._send_handling_redirects( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects - response = self._send_single_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request - response = transport.handle_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request - with map_httpcore_exceptions(): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions - raise mapped_exc(message) from exc -httpx.ConnectError: [Errno 11001] getaddrinfo failed - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api - raw_result = get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id - return default_client.get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id - raise Exception(error_msg) -Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098 -2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 " -2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log deleted file mode 100644 index fd68929..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_daily.log +++ /dev/null @@ -1,289 +0,0 @@ -2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接... -2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098 -2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 " -2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具 -2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628 -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API... -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: { - "datasourceId": "240", - "businessName": "qian50tiaodaorushujumingxichaxun_32b1d628", - "businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。", - "sqlTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "parameters": {}, - "testParams": {} -} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 240 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: qian50tiaodaorushujumingxichaxun_32b1d628 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50; -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:144] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:153] - 正在调用测试SQL API: http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-05-25 16:05:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - test_sql_with_schema 接口返回的数据: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - HTTP状态码: 200 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:173] - 响应数据类型: -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应数据内容: { - "msg": "操作成功", - "code": 200, - "data": { - "resultCount": 6, - "data": [ - { - "记录ID": 80001, - "任务ID": "3320260418001", - "原始数据": "{\"name\": \"张三\", \"mobile\": \"13800000101\", \"dept\": \"销售部\", \"amount\": 1200}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:19:19.000+08:00" - }, - { - "记录ID": 80002, - "任务ID": "3320260418002", - "原始数据": "{\"name\": \"李四\", \"mobile\": \"13800000102\", \"dept\": \"财务部\", \"amount\": 980}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:19:34.000+08:00" - }, - { - "记录ID": 80003, - "任务ID": "3320260418003", - "原始数据": "{\"name\": \"王五\", \"mobile\": \"13800000103\", \"dept\": \"技术部\", \"amount\": 1500}", - "处理结果": "导入失败", - "错误原因": "字段校验失败:mobile格式不正确", - "处理状态": "FAILED", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:19:49.000+08:00" - }, - { - "记录ID": 80004, - "任务ID": "3320260418004", - "原始数据": "{\"name\": \"赵六\", \"mobile\": \"13800000104\", \"dept\": \"人事部\", \"amount\": 0}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:20:04.000+08:00" - }, - { - "记录ID": 80005, - "任务ID": "3320260418005", - "原始数据": "{\"name\": \"孙七\", \"mobile\": \"13800000105\", \"dept\": \"运营部\", \"amount\": 2000}", - "处理结果": "导入失败", - "错误原因": "目标表写入失败:唯一键冲突", - "处理状态": "FAILED", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:20:19.000+08:00" - }, - { - "记录ID": 80006, - "任务ID": "3320260418006", - "原始数据": "{\"name\": \"周八\", \"mobile\": \"13800000106\", \"dept\": \"法务部\", \"amount\": 300}", - "处理结果": "导入成功", - "错误原因": null, - "处理状态": "SUCCESS", - "关联目标表": "employee_import", - "创建时间": "2026-04-18T15:20:34.000+08:00" - } - ], - "databaseName": "import_log_db_168", - "businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。", - "originalTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "convertedTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "executionStatus": "success", - "businessName": "qian50tiaodaorushujumingxichaxun_32b1d628", - "testParams": {}, - "errorMessage": null, - "executionTime": 8, - "datasourceId": "240", - "logId": "2058821884796174336", - "executableSql": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;", - "datasourceName": "import_log_db_168" - } -} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应code: 200 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - 响应msg: 操作成功 -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:178] - 响应data: {'resultCount': 6, 'data': [{'记录ID': 80001, '任务ID': '3320260418001', '原始数据': '{"name": "张三", "mobile": "13800000101", "dept": "销售部", "amount": 1200}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:19.000+08:00'}, {'记录ID': 80002, '任务ID': '3320260418002', '原始数据': '{"name": "李四", "mobile": "13800000102", "dept": "财务部", "amount": 980}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:34.000+08:00'}, {'记录ID': 80003, '任务ID': '3320260418003', '原始数据': '{"name": "王五", "mobile": "13800000103", "dept": "技术部", "amount": 1500}', '处理结果': '导入失败', '错误原因': '字段校验失败:mobile格式不正确', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:49.000+08:00'}, {'记录ID': 80004, '任务ID': '3320260418004', '原始数据': '{"name": "赵六", "mobile": "13800000104", "dept": "人事部", "amount": 0}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:04.000+08:00'}, {'记录ID': 80005, '任务ID': '3320260418005', '原始数据': '{"name": "孙七", "mobile": "13800000105", "dept": "运营部", "amount": 2000}', '处理结果': '导入失败', '错误原因': '目标表写入失败:唯一键冲突', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:19.000+08:00'}, {'记录ID': 80006, '任务ID': '3320260418006', '原始数据': '{"name": "周八", "mobile": "13800000106", "dept": "法务部", "amount": 300}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:34.000+08:00'}], 'databaseName': 'import_log_db_168', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'originalTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'convertedTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'executionStatus': 'success', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'testParams': {}, 'errorMessage': None, 'executionTime': 8, 'datasourceId': '240', 'logId': '2058821884796174336', 'executableSql': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'datasourceName': 'import_log_db_168'} -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - ================================================================================ -2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:181] - 测试SQL API调用成功 -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功 -2026-05-25 16:15:08 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:15:08 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:15:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 API,skill_id: 2058819964077572098 -2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 " -2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具 -2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: -2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/ -2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -Traceback (most recent call last): - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions - yield - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request - resp = self._pool.handle_request(req) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request - raise exc from None - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request - response = connection.handle_request( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request - raise exc - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request - stream = self._connect(request) - ^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect - stream = self._network_backend.connect_tcp(**kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp - with map_exceptions(exc_map): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions - raise to_exc(exc) from exc -httpcore.ConnectError: [Errno 11001] getaddrinfo failed - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id - response = self.client.get(url, headers=self._get_headers()) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get - return self.request( - ^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request - return self.send(request, auth=auth, follow_redirects=follow_redirects) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send - response = self._send_handling_auth( - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth - response = self._send_handling_redirects( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects - response = self._send_single_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request - response = transport.handle_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request - with map_httpcore_exceptions(): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions - raise mapped_exc(message) from exc -httpx.ConnectError: [Errno 11001] getaddrinfo failed - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api - raw_result = get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id - return default_client.get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id - raise Exception(error_msg) -Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs -2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098 -2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 " -2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_error.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_error.log deleted file mode 100644 index 6e209b0..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/lzwcai_mcp_sqlexecutor_error.log +++ /dev/null @@ -1,73 +0,0 @@ -2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -Traceback (most recent call last): - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions - yield - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request - resp = self._pool.handle_request(req) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request - raise exc from None - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request - response = connection.handle_request( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request - raise exc - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request - stream = self._connect(request) - ^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect - stream = self._network_backend.connect_tcp(**kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp - with map_exceptions(exc_map): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions - raise to_exc(exc) from exc -httpcore.ConnectError: [Errno 11001] getaddrinfo failed - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id - response = self.client.get(url, headers=self._get_headers()) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get - return self.request( - ^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request - return self.send(request, auth=auth, follow_redirects=follow_redirects) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send - response = self._send_handling_auth( - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth - response = self._send_handling_redirects( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects - response = self._send_single_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request - response = transport.handle_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request - with map_httpcore_exceptions(): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions - raise mapped_exc(message) from exc -httpx.ConnectError: [Errno 11001] getaddrinfo failed - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api - raw_result = get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id - return default_client.get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id - raise Exception(error_msg) -Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log deleted file mode 100644 index 29d95f7..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/logs/mcp_services.log +++ /dev/null @@ -1,149 +0,0 @@ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================ -2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接... -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)... -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条 -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具 -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628 -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API... -2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================ -2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 API,skill_id: 2058819964077572098 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条 -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086 -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================ -2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api -2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: -2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -Traceback (most recent call last): - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions - yield - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request - resp = self._pool.handle_request(req) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request - raise exc from None - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request - response = connection.handle_request( - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request - raise exc - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request - stream = self._connect(request) - ^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect - stream = self._network_backend.connect_tcp(**kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp - with map_exceptions(exc_map): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions - raise to_exc(exc) from exc -httpcore.ConnectError: [Errno 11001] getaddrinfo failed - -The above exception was the direct cause of the following exception: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id - response = self.client.get(url, headers=self._get_headers()) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get - return self.request( - ^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request - return self.send(request, auth=auth, follow_redirects=follow_redirects) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send - response = self._send_handling_auth( - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth - response = self._send_handling_redirects( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects - response = self._send_single_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request - response = transport.handle_request(request) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request - with map_httpcore_exceptions(): - File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ - self.gen.throw(value) - File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions - raise mapped_exc(message) from exc -httpx.ConnectError: [Errno 11001] getaddrinfo failed - -During handling of the above exception, another exception occurred: - -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api - raw_result = get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id - return default_client.get_skill_by_id(skill_id) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id - raise Exception(error_msg) -Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================ -2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接... -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]} -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条 -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}] -2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具 diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/main.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/main.py deleted file mode 100644 index 05e84e7..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/main.py +++ /dev/null @@ -1,268 +0,0 @@ -from pathlib import Path -from typing import Any -import asyncio -import json -import logging - -try: - from .utils import load_json, generate_input_schema - from .utils import get_skill_by_id, process_skill_response, test_sql_with_schema - from .utils import get_skill_id, get_env_config - from .utils.logger_config import logger_config -except ImportError: - from utils import load_json, generate_input_schema - from utils import get_skill_by_id, process_skill_response, test_sql_with_schema - from utils import get_skill_id, get_env_config - from utils.logger_config import logger_config - -from mcp.server.models import InitializationOptions -from mcp.server import NotificationOptions, Server -import mcp.types as types - - -mcp_logger = logger_config.setup_mcp_logging() - -DATA_SOURCE_API = "api" -DATA_SOURCE_LOCAL = "local" -DATA_SOURCE_BOTH = "both" -DEFAULT_DATA_SOURCE = DATA_SOURCE_API - - -def _text_response(payload: dict[str, Any]) -> list[types.TextContent]: - """Build a JSON text response.""" - return [ - types.TextContent( - type="text", - text=json.dumps(payload, ensure_ascii=False, indent=2) - ) - ] - - -def _extract_user_id(arguments: dict[str, Any]) -> Any: - """Allow numeric 0, reject None and blank strings.""" - user_id = arguments.get("userId") - if user_id is None: - return None - if isinstance(user_id, str) and not user_id.strip(): - return None - return user_id - - -def get_queries(): - """Read local business query config.""" - try: - current_dir = Path(__file__).parent - json_path = current_dir / "businessQueries.json" - mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}") - queries = load_json(json_path) - mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置") - return queries - except Exception as e: - mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True) - raise - - -def generate_tool_schema_from_query(query: dict) -> types.Tool: - """Generate an MCP tool definition from one query config.""" - try: - parameters = query.get("parameters", {}) - input_schema = generate_input_schema(parameters) - tool_name = query["businessName"] - description = f"{query['businessName']}: {query['businessDescription']}" - - mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}") - - return types.Tool( - name=tool_name, - description=description, - inputSchema=input_schema - ) - except Exception as e: - mcp_logger.error( - f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", - exc_info=True - ) - raise - - -server = Server("lzwcai-mcp-sqlexecutor") -_queries_cache = None - - -async def get_queries_cache(source: str = None): - """Get or initialize query config cache.""" - global _queries_cache - if _queries_cache is None: - source = source or DEFAULT_DATA_SOURCE - mcp_logger.info(f"初始化查询配置,数据源: {source}") - - if source == DATA_SOURCE_LOCAL: - _queries_cache = get_queries() - mcp_logger.info(f"本地配置: {len(_queries_cache)} 条") - elif source == DATA_SOURCE_API: - try: - _queries_cache = await call_third_party_api() - mcp_logger.info(f"API 配置: {len(_queries_cache)} 条") - mcp_logger.info(f"API 配置数组: {_queries_cache}") - except Exception as e: - mcp_logger.warning(f"API 获取失败,降级使用本地配置: {e}") - _queries_cache = get_queries() - else: - local = get_queries() - try: - api = await call_third_party_api() - except Exception as e: - mcp_logger.warning(f"API 获取失败: {e}") - api = [] - _queries_cache = local + api - mcp_logger.info(f"配置总数: {len(_queries_cache)} 条,本地 {len(local)} + API {len(api)}") - - return _queries_cache - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """List all dynamically generated MCP tools.""" - try: - mcp_logger.info("收到列出工具请求") - queries = await get_queries_cache() - tools = [generate_tool_schema_from_query(query) for query in queries] - mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具") - mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}") - return tools - except Exception as e: - mcp_logger.error(f"列出工具失败: {e}", exc_info=True) - raise - - -@server.call_tool() -async def handle_call_tool( - name: str, - arguments: dict[str, Any] | None -) -> list[types.TextContent]: - """Handle MCP tool invocation.""" - try: - mcp_logger.info(f"收到工具调用请求: {name}") - mcp_logger.debug(f"工具参数: {arguments}") - - queries = await get_queries_cache() - tool_item = next((query for query in queries if query["businessName"] == name), None) - - if not tool_item: - return _text_response({"error": f"未找到工具 {name} 对应的配置"}) - - normalized_parameters = generate_input_schema(tool_item.get("parameters")) - normalized_arguments = arguments or {} - user_id = _extract_user_id(normalized_arguments) - - if user_id is None: - error_msg = f"工具 {name} 缺少必填参数 userId" - mcp_logger.warning(error_msg) - return _text_response({"error": error_msg, "required": ["userId"]}) - - request_data = { - "datasourceId": tool_item.get("datasourceId"), - "businessName": tool_item.get("businessName"), - "businessDescription": tool_item.get("businessDescription"), - "sqlTemplate": tool_item.get("sqlTemplate"), - "parameters": normalized_parameters, - "testParams": normalized_arguments, - "userId": user_id - } - - target_database_name = normalized_arguments.get("targetDatabaseName") - if target_database_name: - request_data["targetDatabaseName"] = target_database_name - mcp_logger.debug(f"添加目标数据库名称: {target_database_name}") - - try: - mcp_logger.info("正在调用测试 SQL API...") - result_payload = test_sql_with_schema(request_data) - mcp_logger.info("测试 SQL API 调用成功") - except Exception as e: - error_msg = f"调用测试 SQL API 失败: {str(e)}" - mcp_logger.error(error_msg, exc_info=True) - result_payload = {"error": error_msg} - - mcp_logger.debug(f"工具调用结果: {result_payload}") - return _text_response(result_payload) - except Exception as e: - error_msg = f"工具调用失败: {name}, 错误: {e}" - mcp_logger.error(error_msg, exc_info=True) - return [types.TextContent(type="text", text=f"错误: {error_msg}")] - - -async def call_third_party_api(skill_id: str = None) -> list: - """Call backend API and map the response into query configs.""" - try: - if skill_id is None: - skill_id = get_skill_id() - - mcp_logger.info(f"调用第三方 API,skill_id: {skill_id}") - raw_result = get_skill_by_id(skill_id) - mcp_logger.info(f"成功获取原始响应: {raw_result}") - - processed_queries = process_skill_response(raw_result) - mcp_logger.info(f"成功处理 {len(processed_queries)} 条数据") - return processed_queries - except Exception as e: - mcp_logger.error(f"API 调用失败: {e}", exc_info=True) - raise - - -async def async_main(): - """Async entry for the MCP server.""" - try: - mcp_logger.info("=" * 60) - mcp_logger.info("正在启动 MCP 服务: lzwcai-mcp-sqlexecutor") - mcp_logger.info("版本: 0.1.0") - mcp_logger.info("=" * 60) - - env_config = get_env_config() - mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}") - mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}") - mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}") - mcp_logger.info("=" * 60) - - from mcp.server.stdio import stdio_server - - async with stdio_server() as (read_stream, write_stream): - mcp_logger.info("MCP 服务已启动,等待客户端连接...") - await server.run( - read_stream, - write_stream, - InitializationOptions( - server_name="lzwcai-mcp-sqlexecutor", - server_version="0.1.0", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - mcp_logger.info("MCP 服务已关闭") - except Exception as e: - mcp_logger.error(f"MCP 服务运行失败: {e}", exc_info=True) - raise - - -def main(): - """Console entrypoint.""" - try: - logger_config.setup_logging( - app_name="lzwcai_mcp_sqlexecutor", - log_level=logging.INFO, - console_output=False - ) - mcp_logger.info("开始运行 MCP SQL Executor 服务") - asyncio.run(async_main()) - except KeyboardInterrupt: - mcp_logger.info("收到中断信号,正在关闭服务...") - except Exception as e: - mcp_logger.error(f"程序运行失败: {e}", exc_info=True) - raise - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml deleted file mode 100644 index 583cab2..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcp-sqlexecutor" -version = "0.1.6" -description = "MCP server for executing business SQL queries with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "executor", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcp-sqlexecutor = "lzwcai_mcp_sqlexecutor.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcp_sqlexecutor"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcp_sqlexecutor/businessQueries.json" = "lzwcai_mcp_sqlexecutor/businessQueries.json" diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/__init__.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/__init__.py deleted file mode 100644 index 4fdc6f7..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Utils package for lzwcai_mcp_sqlexecutor""" - -from .json_helper import load_json -from .name_helper import generate_tool_name -from .schema_helper import generate_input_schema, validate_input_schema -from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema -from .env_config import get_database_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable - -__all__ = [ - 'load_json', - 'generate_tool_name', - 'generate_input_schema', - 'validate_input_schema', - 'DataSourceAPIClient', - 'get_skill_by_id', - 'process_skill_response', - 'test_sql_with_schema', - 'get_database_id', - 'get_skill_id', - 'get_backend_base_url', - 'get_env_config', - 'set_env_variable' -] - diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/api_client.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/api_client.py deleted file mode 100644 index fd0bc89..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/api_client.py +++ /dev/null @@ -1,209 +0,0 @@ -"""Backend API client helpers.""" - -import json -import logging -from typing import Any, Dict, List, Optional - -import httpx - -try: - from .env_config import get_backend_base_url -except ImportError: - from env_config import get_backend_base_url - - -logger = logging.getLogger(__name__) - - -class DataSourceAPIClient: - """HTTP client for backend datasource APIs.""" - - def __init__( - self, - base_url: Optional[str] = None, - token: Optional[str] = None - ): - if base_url is None: - base_url = get_backend_base_url() - - self.base_url = base_url.rstrip("/") - self.token = token or ( - "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1i" - "ZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dn" - "vqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw" - ) - self.client = httpx.Client(timeout=30.0) - - def _get_headers(self) -> Dict[str, str]: - return { - "Authorization": f"Bearer {self.token}", - } - - def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]: - """Fetch skill details by skill id.""" - try: - url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}" - - logger.info(f"正在调用 API: {url}") - logger.debug(f"请求参数 - skill_id: {skill_id}") - - response = self.client.get(url, headers=self._get_headers()) - response.raise_for_status() - data = response.json() - - logger.info(f"API 调用成功: {url}") - logger.debug(f"响应数据: {data}") - return data - - except httpx.TimeoutException: - error_msg = f"API 请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - except httpx.HTTPStatusError as e: - error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - except httpx.RequestError as e: - error_msg = f"API 请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - except Exception as e: - error_msg = f"处理 API 响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]: - """Call backend SQL test endpoint.""" - try: - logger.info("=" * 80) - logger.info("test_sql_with_schema 接口接收到的数据:") - logger.info(f"数据类型: {type(request_data)}") - logger.info(f"数据内容: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - logger.info(f"数据源 ID: {request_data.get('datasourceId')}") - logger.info(f"业务名称: {request_data.get('businessName')}") - logger.info(f"业务描述: {request_data.get('businessDescription')}") - logger.info(f"SQL 模板: {request_data.get('sqlTemplate')}") - logger.info(f"参数定义: {request_data.get('parameters')}") - logger.info(f"测试参数: {request_data.get('testParams')}") - if "userId" in request_data: - logger.info(f"用户 ID: {request_data.get('userId')}") - logger.info("=" * 80) - - url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema" - headers = self._get_headers() - headers["Content-Type"] = "application/json" - headers["Accept"] = "*/*" - - logger.info(f"正在调用测试 SQL API: {url}") - logger.debug(f"请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - - response = self.client.post(url, headers=headers, json=request_data) - response.raise_for_status() - data = response.json() - - logger.info("=" * 80) - logger.info("test_sql_with_schema 接口返回的数据:") - logger.info(f"HTTP 状态码: {response.status_code}") - logger.info(f"响应数据类型: {type(data)}") - logger.info(f"响应数据内容: {json.dumps(data, ensure_ascii=False, indent=2)}") - if isinstance(data, dict): - logger.info(f"响应 code: {data.get('code')}") - logger.info(f"响应 msg: {data.get('msg')}") - logger.info(f"响应 data: {data.get('data')}") - logger.info("=" * 80) - - logger.info("测试 SQL API 调用成功") - return data - - except httpx.TimeoutException: - error_msg = f"测试 SQL API 请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - except httpx.HTTPStatusError as e: - error_msg = f"测试 SQL API 请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - except httpx.RequestError as e: - error_msg = f"测试 SQL API 请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - except Exception as e: - error_msg = f"处理测试 SQL API 响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def close(self): - """Close the underlying HTTP client.""" - self.client.close() - - -default_client = DataSourceAPIClient() - - -def get_skill_by_id( - skill_id: str, - base_url: Optional[str] = None, - token: Optional[str] = None -) -> Dict[str, Any]: - """Convenience wrapper for skill lookup.""" - if base_url or token: - client = DataSourceAPIClient(base_url=base_url, token=token) - return client.get_skill_by_id(skill_id) - return default_client.get_skill_by_id(skill_id) - - -def test_sql_with_schema( - request_data: Dict[str, Any], - base_url: Optional[str] = None, - token: Optional[str] = None -) -> Dict[str, Any]: - """Convenience wrapper for SQL test endpoint.""" - if base_url or token: - client = DataSourceAPIClient(base_url=base_url, token=token) - return client.test_sql_with_schema(request_data) - return default_client.test_sql_with_schema(request_data) - - -def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: - """Map backend skill response into business query configs.""" - try: - data_list = response.get("data", []) - queries = [] - - for skill in data_list: - sql_params_raw = skill.get("sqlParams") - sql_params: Dict[str, Any] = {} - - if isinstance(sql_params_raw, dict): - sql_params = sql_params_raw - elif isinstance(sql_params_raw, str) and sql_params_raw.strip(): - try: - parsed_sql_params = json.loads(sql_params_raw) - if isinstance(parsed_sql_params, dict): - sql_params = parsed_sql_params - else: - logger.warning( - f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是对象,已回退为空对象" - ) - except json.JSONDecodeError: - logger.warning( - f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是合法 JSON,已回退为空对象" - ) - - query = { - "id": skill.get("id"), - "businessName": skill.get("name"), - "businessDescription": skill.get("description"), - "sqlTemplate": skill.get("sqlTemplate"), - "parameters": sql_params, - "datasourceId": skill.get("datasourceId"), - } - queries.append(query) - - logger.info(f"成功处理 {len(queries)} 条技能数据") - return queries - except Exception as e: - logger.error(f"处理 API 响应数据失败: {e}", exc_info=True) - raise diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/env_config.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/env_config.py deleted file mode 100644 index 418b0aa..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/env_config.py +++ /dev/null @@ -1,87 +0,0 @@ -"""环境变量配置模块""" - -import os - - -def get_database_id(default: str = "29") -> str: - """ - 获取数据库ID环境变量 - - Args: - default: 默认值(默认为 "29") - - Returns: - str: 数据库ID - - Environment Variables: - databaseId: 数据库ID - """ - return os.environ.get("databaseId", default) - - -def get_skill_id(default: str = "") -> str: - """ - 获取技能ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 技能ID - - Environment Variables: - skillId: 技能ID - """ - return os.environ.get("skillId", default) - - -def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: - """ - 获取后端API基础URL环境变量 - - Args: - default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086") - - Returns: - str: 后端API基础URL - - Environment Variables: - backendBaseUrl: 后端API基础URL - """ - return os.environ.get("backendBaseUrl", default) - - -def get_env_config() -> dict: - """ - 获取所有环境配置 - - Returns: - dict: 包含所有配置的字典 - - Example: - config = get_env_config() - print(config['database_id']) # 输出: "29" - print(config['skill_id']) # 输出: "" - print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086" - """ - return { - "database_id": get_database_id(), - "skill_id": get_skill_id(), - "backend_base_url": get_backend_base_url() - } - - -def set_env_variable(key: str, value: str) -> None: - """ - 设置环境变量(仅在当前进程中有效) - - Args: - key: 环境变量名 - value: 环境变量值 - - Example: - set_env_variable("databaseId", "30") - set_env_variable("skillId", "1234567890") - """ - os.environ[key] = value - diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/json_helper.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/json_helper.py deleted file mode 100644 index 1a6fc95..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/json_helper.py +++ /dev/null @@ -1,60 +0,0 @@ -"""JSON 文件读取工具""" - -import json -from pathlib import Path -from typing import Any, Union - - -def load_json(json_path: Union[str, Path]) -> Any: - """ - 读取 JSON 文件并返回其内容 - - Args: - json_path: JSON 文件的路径(支持字符串或 Path 对象) - - Returns: - JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型) - - Raises: - FileNotFoundError: 当文件不存在时 - json.JSONDecodeError: 当 JSON 格式无效时 - Exception: 其他读取错误 - - Example: - >>> data = load_json('config.json') - >>> print(data) - {'key': 'value'} - - >>> data = load_json(Path('data/users.json')) - >>> print(data) - [{'id': 1, 'name': 'Alice'}] - """ - try: - # 转换为 Path 对象 - path = Path(json_path) - - # 检查文件是否存在 - if not path.exists(): - raise FileNotFoundError(f"JSON 文件不存在: {json_path}") - - # 检查是否为文件 - if not path.is_file(): - raise ValueError(f"路径不是一个文件: {json_path}") - - # 读取并解析 JSON 文件 - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - return data - - except json.JSONDecodeError as e: - raise json.JSONDecodeError( - f"JSON 格式错误: {e.msg}", - e.doc, - e.pos - ) - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") - diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/logger_config.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/logger_config.py deleted file mode 100644 index 07c411e..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/logger_config.py +++ /dev/null @@ -1,489 +0,0 @@ -# -*- coding: utf-8 -*- -""" -统一日志配置模块 -提供系统级别的日志配置和管理 -""" - -import os -import sys -import logging -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from datetime import datetime -from pathlib import Path - - -class LoggerConfig: - """日志配置管理类""" - - def __init__(self, logs_dir: str = None): - """初始化日志配置 - - Args: - logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹 - """ - # 确定日志目录 - if logs_dir: - self.logs_dir = Path(logs_dir) - else: - # 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录) - project_root = Path(__file__).parent.parent - self.logs_dir = project_root / "logs" - - # 创建日志目录(包括父目录) - self.logs_dir.mkdir(parents=True, exist_ok=True) - - # 日志格式 - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' - self.date_format = '%Y-%m-%d %H:%M:%S' - - # 从环境变量获取日志级别,默认为INFO - self.log_level = self._get_log_level_from_env() - - # 是否已初始化 - self._initialized = False - - def _get_log_level_from_env(self) -> int: - """从环境变量获取日志级别 - - Returns: - int: 日志级别 - """ - log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() - - # 日志级别映射 - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'WARN': logging.WARNING, # 兼容性别名 - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, - 'FATAL': logging.CRITICAL # 兼容性别名 - } - - return level_mapping.get(log_level_str, logging.INFO) - - def setup_logging(self, - app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO, - max_file_size: int = 10 * 1024 * 1024, # 10MB - backup_count: int = 5, - console_output: bool = True) -> logging.Logger: - """设置系统日志配置 - - Args: - app_name: 应用名称,用于日志文件命名 - log_level: 日志级别 - max_file_size: 单个日志文件最大大小(字节) - backup_count: 保留的备份文件数量 - console_output: 是否输出到控制台 - - Returns: - logging.Logger: 配置好的根日志器 - """ - if self._initialized: - return logging.getLogger() - - # 设置根日志器 - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # 清除现有的处理器 - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - # 创建格式化器 - formatter = logging.Formatter(self.log_format, self.date_format) - - # 1. 主日志文件 - 按大小滚动 - main_log_file = self.logs_dir / f"{app_name}.log" - file_handler = RotatingFileHandler( - main_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # 2. 错误日志文件 - 只记录ERROR及以上级别 - error_log_file = self.logs_dir / f"{app_name}_error.log" - error_handler = RotatingFileHandler( - error_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - error_handler.setLevel(logging.ERROR) - error_handler.setFormatter(formatter) - root_logger.addHandler(error_handler) - - # 3. 按日期滚动的日志文件 - daily_log_file = self.logs_dir / f"{app_name}_daily.log" - daily_handler = TimedRotatingFileHandler( - daily_log_file, - when='midnight', - interval=1, - backupCount=30, # 保留30天 - encoding='utf-8' - ) - daily_handler.setLevel(log_level) - daily_handler.setFormatter(formatter) - daily_handler.suffix = "%Y-%m-%d" - root_logger.addHandler(daily_handler) - - # 4. 控制台输出 - # 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信 - if console_output: - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setLevel(log_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - self.date_format - ) - console_handler.setFormatter(console_formatter) - root_logger.addHandler(console_handler) - - self._initialized = True - - # 记录初始化信息 - root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") - root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}") - - return root_logger - - def get_module_logger(self, module_name: str) -> logging.Logger: - """获取模块专用日志器 - - Args: - module_name: 模块名称 - - Returns: - logging.Logger: 模块日志器 - """ - return logging.getLogger(module_name) - - def create_component_logger(self, - component_name: str, - log_file: str = None, - log_level: int = None) -> logging.Logger: - """为特定组件创建独立的日志器 - - Args: - component_name: 组件名称 - log_file: 独立日志文件名(可选) - log_level: 日志级别(可选) - - Returns: - logging.Logger: 组件日志器 - """ - logger = logging.getLogger(component_name) - - if log_file: - # 为组件创建独立的日志文件 - component_log_file = self.logs_dir / log_file - handler = RotatingFileHandler( - component_log_file, - maxBytes=5 * 1024 * 1024, # 5MB - backupCount=3, - encoding='utf-8' - ) - - formatter = logging.Formatter(self.log_format, self.date_format) - handler.setFormatter(formatter) - - if log_level: - handler.setLevel(log_level) - - logger.addHandler(handler) - logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}") - - return logger - - def setup_mqtt_logging(self) -> logging.Logger: - """设置MQTT专用日志 - - Returns: - logging.Logger: MQTT日志器 - """ - return self.create_component_logger( - "mqtt_communication", - "mqtt_communication.log", - logging.DEBUG - ) - - def setup_mcp_logging(self) -> logging.Logger: - """设置MCP专用日志 - - Returns: - logging.Logger: MCP日志器 - """ - return self.create_component_logger( - "mcp_services", - "mcp_services.log", - logging.DEBUG - ) - - def setup_api_logging(self) -> logging.Logger: - """设置API专用日志 - - Returns: - logging.Logger: API日志器 - """ - return self.create_component_logger( - "api_requests", - "api_requests.log", - logging.INFO - ) - - def get_logs_info(self) -> dict: - """获取日志系统信息 - - Returns: - dict: 日志系统信息 - """ - log_files = [] - if self.logs_dir.exists(): - for log_file in self.logs_dir.glob("*.log*"): - stat = log_file.stat() - log_files.append({ - "name": log_file.name, - "size": stat.st_size, - "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() - }) - - return { - "logs_directory": str(self.logs_dir), - "initialized": self._initialized, - "log_files": log_files, - "total_files": len(log_files) - } - - def cleanup_old_logs(self, days: int = 30): - """清理旧日志文件 - - Args: - days: 保留天数 - """ - if not self.logs_dir.exists(): - return - - from datetime import timedelta - cutoff_time = datetime.now() - timedelta(days=days) - - cleaned_files = [] - for log_file in self.logs_dir.glob("*.log*"): - if log_file.stat().st_mtime < cutoff_time.timestamp(): - try: - log_file.unlink() - cleaned_files.append(log_file.name) - except Exception as e: - logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}") - - if cleaned_files: - logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}") - - def set_log_level(self, level: int, logger_name: str = None): - """动态调整日志级别 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - old_level = logger.level - logger.setLevel(level) - - # 同时调整所有处理器的级别 - for handler in logger.handlers: - if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr): - # 不调整控制台处理器的级别,保持原有设置 - handler.setLevel(level) - - level_name = logging.getLevelName(level) - old_level_name = logging.getLevelName(old_level) - target = logger_name or "根日志器" - - logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}") - - def set_temporary_log_level(self, level: int, logger_name: str = None): - """临时调整日志级别(会保存原始级别用于恢复) - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if not hasattr(self, '_original_levels'): - self._original_levels = {} - - target_name = logger_name or 'root' - - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - # 保存原始级别 - if target_name not in self._original_levels: - self._original_levels[target_name] = logger.level - - # 设置新级别 - self.set_log_level(level, logger_name) - - level_name = logging.getLevelName(level) - target = logger_name or "根日志器" - logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)") - - def restore_log_level(self, logger_name: str = None): - """恢复日志级别到调整前的状态 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - if not hasattr(self, '_original_levels'): - logging.warning("没有找到保存的原始日志级别") - return - - target_name = logger_name or 'root' - - if target_name not in self._original_levels: - logging.warning(f"没有找到 {target_name} 的原始日志级别") - return - - original_level = self._original_levels[target_name] - self.set_log_level(original_level, logger_name) - - # 清除保存的级别 - del self._original_levels[target_name] - - target = logger_name or "根日志器" - level_name = logging.getLevelName(original_level) - logging.info(f"已恢复日志级别: {target} -> {level_name}") - - def get_current_log_levels(self) -> dict: - """获取当前所有日志器的级别信息 - - Returns: - dict: 日志器级别信息 - """ - levels_info = {} - - # 根日志器 - root_logger = logging.getLogger() - levels_info['root'] = { - 'level': root_logger.level, - 'level_name': logging.getLevelName(root_logger.level), - 'handlers_count': len(root_logger.handlers) - } - - # 其他已创建的日志器 - for name, logger in logging.Logger.manager.loggerDict.items(): - if isinstance(logger, logging.Logger): - levels_info[name] = { - 'level': logger.level, - 'level_name': logging.getLevelName(logger.level), - 'handlers_count': len(logger.handlers) - } - - return levels_info - - -# 全局日志配置实例 -logger_config = LoggerConfig() - - -def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO) -> logging.Logger: - """系统日志初始化快捷函数 - - Args: - app_name: 应用名称 - log_level: 日志级别 - - Returns: - logging.Logger: 根日志器 - """ - return logger_config.setup_logging(app_name, log_level) - - -def get_logger(name: str) -> logging.Logger: - """获取日志器的快捷函数 - - Args: - name: 日志器名称 - - Returns: - logging.Logger: 日志器实例 - """ - return logger_config.get_module_logger(name) - - -def set_log_level(level: int, logger_name: str = None): - """动态调整日志级别的快捷函数 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 调整根日志器为DEBUG级别 - set_log_level(logging.DEBUG) - - # 调整特定模块日志器为WARNING级别 - set_log_level(logging.WARNING, "agent_ontology.core") - """ - logger_config.set_log_level(level, logger_name) - - -def set_temporary_log_level(level: int, logger_name: str = None): - """临时调整日志级别的快捷函数 - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 临时调整为DEBUG级别进行调试 - set_temporary_log_level(logging.DEBUG) - # ... 进行调试 ... - # 恢复原始级别 - restore_log_level() - """ - logger_config.set_temporary_log_level(level, logger_name) - - -def restore_log_level(logger_name: str = None): - """恢复日志级别的快捷函数 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - logger_config.restore_log_level(logger_name) - - -def get_current_log_levels() -> dict: - """获取当前日志级别信息的快捷函数 - - Returns: - dict: 日志器级别信息 - - Examples: - levels = get_current_log_levels() - print(f"根日志器级别: {levels['root']['level_name']}") - """ - return logger_config.get_current_log_levels() - - -# 便捷的日志级别常量 -class LogLevel: - """日志级别常量类""" - DEBUG = logging.DEBUG - INFO = logging.INFO - WARNING = logging.WARNING - ERROR = logging.ERROR - CRITICAL = logging.CRITICAL \ No newline at end of file diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/name_helper.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/name_helper.py deleted file mode 100644 index d66cf32..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/name_helper.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" -名称生成工具模块 -""" - -from pypinyin import lazy_pinyin, Style -import logging - -logger = logging.getLogger(__name__) - - -def generate_tool_name(business_name: str, tool_id: str) -> str: - """ - 根据业务名称和ID生成工具名称 - 格式: tool_拼音_id - - Args: - business_name: 业务名称(中文) - tool_id: 工具ID - - Returns: - str: 格式化的工具名称 - """ - try: - # 将中文转换为拼音(无音调,小写) - pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL) - # 拼接拼音 - pinyin_str = ''.join(pinyin_list) - - # 将 ID 中的 '-' 替换为 '_' - formatted_id = tool_id.replace('-', '_') - - # 组合成最终的工具名称 - tool_name = f"tool_{pinyin_str}_{formatted_id}" - - return tool_name - except Exception as e: - logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True) - # 降级处理:如果拼音转换失败,使用 ID - return f"tool_{tool_id.replace('-', '_')}" - diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/schema_helper.py b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/schema_helper.py deleted file mode 100644 index 01f4bd4..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/utils/schema_helper.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Schema 生成工具模块。 -""" - -from copy import deepcopy -from typing import Any, Dict - - -def _normalize_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]: - """将任意参数定义归一化为 object schema。""" - if not isinstance(parameters, dict) or not parameters: - return { - "type": "object", - "properties": {}, - "required": [] - } - - input_schema = deepcopy(parameters) - input_schema["type"] = "object" - - if not isinstance(input_schema.get("properties"), dict): - input_schema["properties"] = {} - - if not isinstance(input_schema.get("required"), list): - input_schema["required"] = [] - - return input_schema - - -def generate_input_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]: - """ - 从查询配置的参数定义生成 MCP 工具的 inputSchema。 - - 会统一补齐: - - `targetDatabaseName`:可选 - - `userId`:必填 - """ - input_schema = _normalize_schema(parameters) - - if "targetDatabaseName" not in input_schema["properties"]: - input_schema["properties"]["targetDatabaseName"] = { - "type": "string", - "description": "目标数据库名称", - "default": "" - } - - if "userId" not in input_schema["properties"]: - input_schema["properties"]["userId"] = { - "type": "string", - "description": "当前 AI 平台用户 ID" - } - - if "userId" not in input_schema["required"]: - input_schema["required"].append("userId") - - return input_schema - - -def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: - """ - 验证 inputSchema 是否符合基本的 JSON Schema 规范。 - """ - if not isinstance(schema, dict): - return False, "Schema 必须是一个字典对象" - - if schema.get("type") != "object": - return False, "Schema 的 type 字段必须是 'object'" - - if "properties" not in schema: - return False, "Schema 必须包含 properties 字段" - - if not isinstance(schema.get("properties"), dict): - return False, "Schema 的 properties 字段必须是一个字典对象" - - if "required" in schema: - required = schema["required"] - if not isinstance(required, list): - return False, "Schema 的 required 字段必须是一个列表" - - properties = schema["properties"] - for field in required: - if field not in properties: - return False, f"必填字段 '{field}' 未在 properties 中定义" - - for prop_name, prop_def in schema["properties"].items(): - if not isinstance(prop_def, dict): - return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" - - if "type" not in prop_def: - return False, f"属性 '{prop_name}' 必须包含 type 字段" - - return True, "Schema 验证通过" diff --git a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/uv.lock b/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/uv.lock deleted file mode 100644 index c541de4..0000000 --- a/lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/uv.lock +++ /dev/null @@ -1,497 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, -] - -[[package]] -name = "anyio" -version = "4.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, -] - -[[package]] -name = "certifi" -version = "2025.10.5" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" }, -] - -[[package]] -name = "click" -version = "8.3.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, -] - -[[package]] -name = "httpx-sse" -version = "0.4.3" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" }, -] - -[[package]] -name = "lzwcai-mcp-sqlexecutor" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "httpx" }, - { name = "mcp", extra = ["cli"] }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.10.1" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" }, -] - -[[package]] -name = "mcp" -version = "1.10.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "jsonschema" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-multipart" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" }, -] - -[package.optional-dependencies] -cli = [ - { name = "python-dotenv" }, - { name = "typer" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, -] - -[[package]] -name = "pydantic" -version = "2.12.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, -] - -[[package]] -name = "python-dotenv" -version = "1.1.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.20" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" }, -] - -[[package]] -name = "rpds-py" -version = "0.27.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" }, -] - -[[package]] -name = "sse-starlette" -version = "3.0.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" }, -] - -[[package]] -name = "starlette" -version = "0.48.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" }, -] - -[[package]] -name = "typer" -version = "0.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, -] - -[[package]] -name = "uvicorn" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" }, -] diff --git a/lzwcai_mcp_sqlexecutor/main.py b/lzwcai_mcp_sqlexecutor/main.py deleted file mode 100644 index 580155c..0000000 --- a/lzwcai_mcp_sqlexecutor/main.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Repository-local launcher for lzwcai-mcp-sqlexecutor. -""" - -import os - - -def main(): - # Keep local developer defaults without overriding explicit environment settings. - os.environ.setdefault("databaseId", "240") - os.environ.setdefault("skillId", "2058819964077572098") - os.environ.setdefault("backendBaseUrl", "http://192.168.2.236:8088") - - from lzwcai_mcp_sqlexecutor.main import main as package_main - - package_main() - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcp_sqlexecutor/pyproject.toml b/lzwcai_mcp_sqlexecutor/pyproject.toml deleted file mode 100644 index d95f3ff..0000000 --- a/lzwcai_mcp_sqlexecutor/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcp-sqlexecutor" -version = "0.1.13" -description = "MCP server for executing business SQL queries with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "executor", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcp-sqlexecutor = "lzwcai_mcp_sqlexecutor.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcp_sqlexecutor"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcp_sqlexecutor/businessQueries.json" = "lzwcai_mcp_sqlexecutor/businessQueries.json" diff --git a/lzwcai_mcp_sqlexecutor/uv.lock b/lzwcai_mcp_sqlexecutor/uv.lock deleted file mode 100644 index 7a9cc2e..0000000 --- a/lzwcai_mcp_sqlexecutor/uv.lock +++ /dev/null @@ -1,842 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.10" - -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/fbc/da96e87e9c92a/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/571/ac1dc6991c450/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, -] - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/334/b70e641fd2221/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/08b/310f9e24a9594/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708" }, -] - -[[package]] -name = "attrs" -version = "26.1.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/d03/ceb89cb322a8f/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/c64/7aa4a12dfbad9/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309" }, -] - -[[package]] -name = "certifi" -version = "2026.5.20" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/69d/ea482ab64caa7/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/3c5/2e209ba0a4ad7/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/44d/1b5909021139f/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/0cf/2d91ecc3fcc06/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f73/b96c41e3b2ade/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/53f/77cbe57044e88/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3e8/37e3695668847/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5ed/a85d6d1879e69/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/933/2088d75dc3241/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fc7/de24befaeae77/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/cf3/64028c016c030/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e11/e82b744887154/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8ea/985900c5c95ce/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1f7/2fb8906754ac8/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b18/a3ed7d5b3bd8d/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b4c/854ef3adc1779/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/2de/9a304e27f7596/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/baf/5215e0ab74c16/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/730/cacb21e1bdff3/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/682/4f87845e33960/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9de/40a7b0323d889/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/894/1aaadaf672462/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a05/d0c237b334909/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/946/98a9c5f91f9d1/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5fe/d36fccc0612a5/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c64/9e3a33450ec82/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/66f/011380d0e49ed/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c66/38687455baf64/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6d0/2d6655b0e54f5/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8ec/a2a813c1cb7ad/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/21d/1152871b01940/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b21/e08af67b8a103/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1e3/a615586f05fc4/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/81a/fed14892743bb/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3e1/7ed538242334b/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/392/5dd22fa2b7699/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/2c8/f814d84194c9e/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/da9/02562c3e9c550/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/da6/8248800ad6320/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/467/1d9dd5ec934cb/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/00b/df7acc5f79515/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/45d/5e886156860dc/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/07b/271772c100085/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d48/a880098c96020/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f93/fd8e5c8c0a4aa/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dd4/f05f54a52fb55/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c8d/3b5532fc71b7a/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d9b/29c1f0ae438d5/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6d5/0360be4546678/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/74a/03b9698e198d4/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/19f/705ada2530c11/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/256/f80b80ca3853f/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fc3/3c5141b55ed36/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c65/4de545946e0db/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/24b/6f81f1983e6df/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/128/73ca6cb9b0f0d/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d9b/97165e8aed927/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/afb/8db5439b81cf9/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/737/fe7d37e1a1bff/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/381/00abb9d1b1435/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/087/067fa8953339c/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/203/a48d1fb583fc7/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dbd/5c7a25a7cb98f/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9a6/7fc9e8eb39039/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7a6/6c7204d886929/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7cc/09976e8b56f8c/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/92b/68146a71df785/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b1e/74d11748e7e98/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/28a/3a209b96630bc/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/755/3fb2090d71822/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6c6/c373cfc5c83a9/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1fc/9ea04857caf66/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d68/b6cef7827e864/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0a1/527a803f0a659/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9" }, -] - -[[package]] -name = "click" -version = "8.4.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/918/b5633eddf6b41/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/482/be17c6991b8c1/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, -] - -[[package]] -name = "cryptography" -version = "48.0.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/5c3/932f4436d1ccc/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/0c5/58d2cdffd8f4b/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f53/33311663ea94f/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/799/5ef305d7165c3/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/40b/a1f85eaa69598/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/369/a6348999f94bb/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a0e/692c683f4df67/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/183/49bbc56f4743c/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7e8/eac43dfca5c4c/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9cc/dac7d40688ecb/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/bd7/2e68b06bb1e96/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/59b/aa2cb386c4f0b/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/924/9e3cd978541d6/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9c4/59db21422be75/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5b0/12212e08b8dd5/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3cb/07a3ed6431663/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8c7/378637d7d8801/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/cc9/0c0b39b2e3c65/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/763/41972e1eff8b4/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/55b/7718303bf06a5/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a64/697c641c7b1b2/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/561/215ea3879cb1c/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ad6/4688338ed4bc1/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/906/cbf0670286c6e/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ea8/990436d914540/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c18/684a7f0cc9a3c/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9be/5aafa5736574f/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c17/dfe85494deaed/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/272/41b1dc9962e05/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/58d/00498e8933e4a/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/614/d0949f4790582/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7ce/4bfae76319a53/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/2eb/992bbd4661238/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/22a/5cb272895dce1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/2b4/d59804e8408e2/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/984/a20b0f62a26f4/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5a5/ed8fde7a1d093/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8cd/666227ef7af43/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/907/1196d81abc88b/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1e2/d54c8be615285/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a5d/a777e32ffed6f/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/77a/2ccbbe917f671/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/16c/d65b9330583e4/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/84c/f79f0dc8b36ac/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fdf/ef35d751d510f/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/089/0f502ddf7d9c6/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ecd/e28a596bead48/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4de/fde8685ae324a/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/db6/3bf618e5dea46/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/8b4/12432c6055b0b/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/a7a/39a3bd276781e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, -] - -[[package]] -name = "httpx-sse" -version = "0.4.3" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" }, -] - -[[package]] -name = "idna" -version = "3.16" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/d7a/6da03db833450/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/cc2/46e3a3f89580c/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/0c2/6707e2efad8aa/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/d48/9f15263b8d200/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" }, -] - -[[package]] -name = "lzwcai-mcp-sqlexecutor" -version = "0.1.11" -source = { editable = "." } -dependencies = [ - { name = "httpx" }, - { name = "mcp", extra = ["cli"] }, - { name = "pypinyin" }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.10.1" }, - { name = "pypinyin", specifier = ">=0.53.0" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.2.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/04a/21681d6fbb623/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/9f7/ebbcd14fe5949/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a" }, -] - -[[package]] -name = "mcp" -version = "1.27.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "jsonschema" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "python-multipart" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/0f4/7e1820f8f8f94/mcp-1.27.1.tar.gz", hash = "sha256:0f47e1820f8f8f941466b39749eb1d1839a04caddca2bc60e9d46e8a99914924" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/1af/3c4203b329430/mcp-1.27.1-py3-none-any.whl", hash = "sha256:1af3c4203b329430fde7a87b4fcb6392a041f5cb851fd68fc674016ab4e7c06f" }, -] - -[package.optional-dependencies] -cli = [ - { name = "python-dotenv" }, - { name = "typer" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/600/f49d217304a59/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/b72/7414169a36b7d/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" }, -] - -[[package]] -name = "pydantic" -version = "2.13.4" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/c40/756b57adaa8b1/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/45a/282cde31d8082/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.4" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/62f/875393d7f2708/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/a39/6dcc17e5a0b16/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/da4/b951fe36dc7c3/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/bb6/3e0198ca18aad/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f47/286a97f0bc9b8/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/905/a0ed8ea6f2d61/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ea7/93e075b70290d/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/395/aebd9183f9d11/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b07/8afbc25f3a143/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f74/7929cf940cddb/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/daa/27d92c36f2438/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/19e/51f073cd3df25/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c17/47f85cee84c26/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/2f8/4c03c8607173d/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/835/8a950c8909158/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0e9/6592440881c74/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e0d/65b8c354be7fb/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7bf/b192b3f4b9e8a/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/903/7063db01f09b0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fc0/10ab034c8c745/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8c5/dac79fa1614d1/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f9f/a868638bf362d/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/172/99feefe090f2c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4c6/3ebc82684aa89/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/aaa/2a54443eff195/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/18e/5ceec2ab67e6d/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a0f/62d0a58f4e7da/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/041/bde0a48fd37cf/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6f2/eeda33a839975/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/14f/4c5d6db102bd7/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/324/5406455a5d981/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/962/ccbab7b642487/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/823/3f2947cf85404/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3a2/33125ac121aa3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5b7/12b53160b79a5/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/940/1557acd873c3a/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/926/c9541b14b12b1/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/56c/b4851bcaf3d11/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c68/fcd102d71ea85/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b2f/69dec1725e79a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8d0/820e8192167f8/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fbd/b89b3e1c94a30/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9aa/768456404a8bf/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e9c/26f834c65f575/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4fc/73cb559bdb54b/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5d5/902252db0d3ce/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c94/f0688e7b8d0a6/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f02/7324c56cd5406/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e73/9fee756ba1010/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9d5/6801be94b86a9/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/241/2e734dcb48da1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/955/1187363ffc0de/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/018/6750b482eefa1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/585/5698a4856556d/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/cba/f13819775b7f7/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/633/147d34cf45504/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/82c/f530117216810/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9fa/8ae11da9e2b31/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6b3/ace8194b0e520/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/184/c081504d17f1c/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/428/e04521a40150c/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/23a/ce664830ee0bf/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ce5/c1d2a8b27468f/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/728/3d57845ecf5a1/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8da/afc69c93ee8a0/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/cd2/213145bcc2ba8/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7a5/f930472650a82/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c1b/3f518abeca3aa/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1a7/dd0b3ee80d901/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3fb/702cd90b0446a/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b84/58003118a712e/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/372/429a130e469c9/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/85b/b3611ff1802f3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/811/ff8e9c313ab42/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/bfe/c22eab3c8cc2c/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/af8/244b2bef6aaad/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5a4/330cdbc57162e/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/29c/61fc04a3d8401/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c50/f2528cf200c5e/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0cb/e8b01f948de42/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/617/d7e2ca7dcb8c5/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/702/7560ee9221164/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f99/626688942fb74/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fc3/e9034a63de20e/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/97e/7cf2be5c77b7d/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3bf/92c5d0e00fefa/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3ec/bc122d18468d0/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e84/6ae7835bf0703/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/210/8ba5c1c1eca18/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4fc/be087dbc2068a/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/14d/4edf427bdcf95/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0ce/40cd7b21210e9/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/908/84113d8b48f76/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/66c/e7632c22d837c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1d8/ba486450b14f3/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/300/9f12e4e90b7f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ad7/85e92e6dc634c/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/00c/603d540afdd6b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0c5/63b08bca408dc/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/db0/6ffe51636ffe9/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/133/878133d271ade/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9bc/519fbf2b75783/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c7a/7bd4e39e8e4c1/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d39/6ec2b979760aa/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/86e/1a4418c6cd97d/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d51/026d73fcfd936/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.14.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/e87/4d3bec7e787b0/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/6e3/c7edfd8277687/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/675/7cd03768053ff/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/81a/9e26dd42fd28a/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" }, -] - -[[package]] -name = "pyjwt" -version = "2.13.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/415/71c89ca91598c/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/66a/dcc2aff09b3f1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pypinyin" -version = "0.55.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/b57/11b3a0c6f76e6/pypinyin-0.55.0.tar.gz", hash = "sha256:b5711b3a0c6f76e67408ec6b2e3c4987a3a806b7c528076e7c7b86fcf0eaa66b" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/d53/b1e8ad2cdb815/pypinyin-0.55.0-py2.py3-none-any.whl", hash = "sha256:d53b1e8ad2cdb815fb2cb604ed3123372f5a28c6f447571244aca36fc62a286f" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/2c3/71a91fbd7ba08/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/1d8/214789a24de45/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.29" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/643/e93849196645e/python_multipart-0.0.29.tar.gz", hash = "sha256:643e93849196645e2dbdd81a0f8829a23123ad7f797a84a364c6fb3563f18904" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/2dd/cc971cef26622/python_multipart-0.0.29-py3-none-any.whl", hash = "sha256:2ddcc971cef266225f54f552d8fa10bcfbb1f14446caec199060daac59ff2d69" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/d03/ff496d2a0cd4a/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/797/c277201785198/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/050/2d1facf1fed48/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/184/eb5e436dea364/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3ce/80b34b22b17cc/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a73/3f1388e1a842a/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/750/ec6e621af2b94/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b8c/095edad5c211f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e28/6f46a9a39c4a1/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f95/ba5a847cba10d/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/718/a38f7e5b058e7/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7b4/075d959648406/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b7a/2c10b93f89866/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3ac/a44c046bd2ed8/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a50/8e2d9025764a8/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" }, -] - -[[package]] -name = "rich" -version = "15.0.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/edd/07a4824c6b401/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/33b/d4ef74232fb73/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb" }, -] - -[[package]] -name = "rpds-py" -version = "0.30.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/dd8/ff7cf90014af0/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/679/ae98e00c0e8d6/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4cc/2206b76b4f576/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/389/a2d49eded1896/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/32c/8528634e1bf71/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f20/7f69853edd6f6/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/67b/02ec25ba7a9e8/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0c0/e95f6819a1996/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a45/2763cc5198f2f/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e0b/65193a413ccc9/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/858/738e9c32147f7/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/da2/79aa314f00acb/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7c6/4d38fb49b6cde/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6de/2a32a1665b932/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/172/6859cd0de969f/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a2b/ffea6a4ca9f01/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dc4/f992dfe1e2bc3/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/422/c3cb9856d80b0/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/07a/e8a593e1c3c6b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/12f/90dd7557b6bd5/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/99b/47d6ad9a6da00/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/33f/559f310450450/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/946/fe926af6e44f3/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/495/aeca4b93d465e/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d9a/0ca5da0386dee/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/8d6/d1cc13664ec13/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/389/6fa1be39912cf/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/55f/6602263220594/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a51/033ff701fca75/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/47b/0ef6231c58f50/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a16/1f20d9a430068/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6ab/c8880d9d036ec/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ca2/8829ae5f5d569/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a10/10ed9524c73b9/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f8d/1736cfb49381b/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d94/8b135c4693daf/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/47f/236970bccb223/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/2e6/ecb5a5bcacf59/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a8f/a71a2e078c527/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/73c/67f2db7bc334e/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5ba/103fb455be00f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7ce/e9c752c036458/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1ab/5b83dbcf55acc/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a09/0322ca841abd4/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/669/b1805bd639dd2/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f83/424d738204d97/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e75/36cd91353c527/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/277/1c6c15973347f/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0a5/9119fc6e3f460/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/76f/ec018282b4ead/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/692/bef75a5525db9/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/902/7da1ce107104c/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9cf/69cdda1f5968a/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a47/96a717bf12b9d/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/5d4/c2aa7c50ad472/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ba8/1a9203d078054/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/945/dccface01af02/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b40/fb160a2db369a/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/806/f36b1b605e2d6/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/d96/c2086587c7c30/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/eb0/b93f2e5c2189e/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/922/e10f31f303c7c/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/cdc/62c8286ba9bf7/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/47f/9a91efc418b54/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1f3/587eb9b17f378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/39c/02563fc592411/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/51a/1234d8febafdf/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/eb2/c4071ab598733/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/6bd/fdb946967d816/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c77/afbd5f5250bf2/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/610/46904275472a7/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4c5/f36a861bc4b7d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3d4/a69de7a3e50ff/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f14/fc5df50a716f7/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/68f/19c879420aa08/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ec7/c4490c672c1a0/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/f25/1c812357a3fed/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ac9/8b175585ecf4c/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3e6/2880792319dbe/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/4e7/fc54e0900ab35/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/47e/77dc9822d3ad6/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/b4d/c1a6ff022ff85/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/455/9c972db3a3608/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0ed/177ed9bded28f/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ad1/fa8db769b76ea/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/46e/83c697b1f1c72/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ee4/54b2a007d5736/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/95f/0802447ac2d10/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/613/aa4771c99f033/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/7e6/ecfcb62edfd63/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/a1d/0bc22a7cdc173/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/0d0/8f00679177226/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/596/5af57d5848192/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/9a4/e86e34e9ab6b6/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/e5d/3e6b26f2c785d/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/626/a7433c3456653/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/acd/7eb3f4471577b/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/fe5/fa731a1fa8a0a/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/74a/3243a41112636/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3e8/eeb0544f2eb0d/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dbd/936cde57abfee/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dc8/24125c72246d9/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/27f/4b0e92de5bfbc/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/c22/62bdba0ad4fc6/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ee6/af14263f25eed/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/3ad/bb8179ce342d2/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/250/fa00e9543ac9b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/985/4cf4f488b3d57/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/993/914b8e560023b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/58e/dca431fb9b299/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dea/5b552272a9447/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ba3/af48635eb83d0/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/dff/13836529b921e/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/1b1/51685b23929ab/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4" }, - { url = "http://192.168.2.236:3141/root/pypi/+f/ac3/7f9f516c51e57/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" }, -] - -[[package]] -name = "sse-starlette" -version = "3.4.4" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "starlette" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/07e/0fa0460138baf/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/3f4/dd50d8aed2771/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973" }, -] - -[[package]] -name = "starlette" -version = "1.1.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/e83/c7fe0ddecd871/starlette-1.1.0.tar.gz", hash = "sha256:e83c7fe0ddecd8719c5b840080325aec0260acec86e9832899e377b91d65e90f" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/7f0/dfd38e428aad5/starlette-1.1.0-py3-none-any.whl", hash = "sha256:7f0dfd38e428aad5cb6f9f667f0ca1d2d8ca3f3385dccac8305f79ec98458382" }, -] - -[[package]] -name = "typer" -version = "0.25.1" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/961/6eb8853a09ffe/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/75c/aa44ed46a03fb/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, -] - -[[package]] -name = "uvicorn" -version = "0.48.0" -source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/a55/04207195d08c2/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37" } -wheels = [ - { url = "http://192.168.2.236:3141/root/pypi/+f/480/97851328b87ec/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad" }, -] diff --git a/lzwcai_mcpskills_analyzeWorkOrder/README.md b/lzwcai_mcpskills_analyzeWorkOrder/README.md deleted file mode 100644 index e4bc0ac..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# lzwcai-mcpskills-analyzeOrder - -一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录 -- 🌐 中文支持:工具名称自动转换为拼音 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcpskills-analyzeOrder -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcp_sqlexecutor -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcpskills-analyzeOrder -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcpskills-analyzeOrder -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcp_sqlexecutor.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "lzwcai-sqlexecutor": { - "command": "lzwcai-mcpskills-analyzeOrder" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "用户订单查询", - "businessDescription": "根据用户ID查询订单信息", - "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", - "parameters": { - "type": "object", - "required": ["userId"], - "properties": { - "userId": { - "type": "integer", - "description": "用户的唯一标识符", - "examples": [10086] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcp_sqlexecutor - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcp_sqlexecutor.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 许可证 - -MIT License - -## 作者 - -lzwcai - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.gitignore b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.gitignore deleted file mode 100644 index 505a3b1..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Python-generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info - -# Virtual environments -.venv diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.python-version b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/README.md b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/README.md deleted file mode 100644 index 0ec9671..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# lzwcai-mcpskills-analyzeWorkOrder - -一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录(仅输出到文件,不干扰MCP通信) -- 🌐 中文支持:工具名称自动转换为拼音 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcpskills-analyzeWorkOrder -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcp_sqlexecutor -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcpskills-analyzeWorkOrder -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcpskills-analyzeWorkOrder -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcp_sqlexecutor.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "lzwcai-sqlexecutor": { - "command": "lzwcai-mcpskills-analyzeWorkOrder" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "用户订单查询", - "businessDescription": "根据用户ID查询订单信息", - "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", - "parameters": { - "type": "object", - "required": ["userId"], - "properties": { - "userId": { - "type": "integer", - "description": "用户的唯一标识符", - "examples": [10086] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcp_sqlexecutor - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcp_sqlexecutor.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 常见问题 - -### MCP Inspector 显示 JSON 解析错误 - -如果在使用 MCP Inspector 测试时遇到 `SyntaxError: Unexpected non-whitespace character after JSON` 错误,这是因为: - -1. **原因**:MCP 协议使用 stdio(标准输入输出)进行 JSON-RPC 通信,任何输出到 stdout 的内容(如 print 语句或控制台日志)都会破坏 JSON 格式。 - -2. **解决方案**:本服务器已将所有日志输出配置为仅写入文件(位于 `logs/` 目录),不输出到控制台。日志文件包括: - - `lzwcai_mcp_sqlexecutor.log` - 主日志文件 - - `lzwcai_mcp_sqlexecutor_error.log` - 错误日志 - - `lzwcai_mcp_sqlexecutor_daily.log` - 按日期滚动的日志 - - `mcp_services.log` - MCP 服务专用日志 - -3. **查看日志**:如果需要调试,请查看 `logs/` 目录下的日志文件。 - -## 许可证 - -MIT License - -## 作者 - -lzwcai - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/__init__.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/__init__.py deleted file mode 100644 index 3e52bf0..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -lzwcai-mcpskills-analyzeOrder - MCP server for executing business SQL queries -""" - -__version__ = "0.1.2" -__author__ = "lzwcai" - -__all__ = [] - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json deleted file mode 100644 index a180e7e..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "id": "2006300000000000006", - "businessName": "SalesOrderList", - "businessDescription": "历史订单列表:查询所有销售订单,包含客户信息、订单金额、收款状态、交货日期等关键信息", - "datasourceId": "19", - "sqlTemplate": "SELECT so.sales_order_id, so.sales_order_number, c.customer_name, so.contract_number, so.order_date_utc, so.delivery_date_utc, so.deal_amount, so.currency_code, so.payment_status, so.stage, so.result, so.transport_mode, so.production_doc_number, so.event_time_utc FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true ORDER BY so.order_date_utc DESC", - "parameters": {} - }, - { - "id": "2006300000000000007", - "businessName": "SalesOrderDetail", - "businessDescription": "订单详情:根据订单编号查询订单详细信息,包含客户信息、销售员、合同、金额、交货日期、收款状态等完整信息", - "datasourceId": "19", - "sqlTemplate": "SELECT so.sales_order_id, so.sales_order_number, c.customer_name, c.customer_level, c.phone AS customer_phone, c.email AS customer_email, c.address AS customer_address, p.person_name AS sales_person_name, so.contract_number, ct.contract_name, ct.contract_amount, so.order_date_utc, so.delivery_date_utc, so.deal_amount, so.currency_code, so.fx_rate, so.payment_status, so.transport_mode, so.packaging, so.customs_number, so.production_doc_number, so.stage, so.result, so.event_time_utc FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true LEFT JOIN dim_person p ON so.sales_person_id = p.person_id AND p.is_current = true LEFT JOIN dim_contract ct ON so.contract_id = ct.contract_id AND ct.is_current = true WHERE so.sales_order_number = '{salesOrderNumber}'", - "parameters": { - "salesOrderNumber": { - "type": "string", - "description": "销售订单编号", - "required": true, - "examples": [ - "SO20251225001" - ] - } - } - }, - { - "id": "2006300000000000008", - "businessName": "DeliveryRiskPrediction", - "businessDescription": "交付风险预测:基于历史订单的生产周期、物流延误、设备故障等特征,计算延迟概率与红/黄/绿预警等级", - "datasourceId": "19", - "sqlTemplate": "WITH global_production AS (SELECT COUNT(*) AS total_wo_count, AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate, SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count FROM fact_work_order), global_quality AS (SELECT COUNT(*) AS total_inspection_count, SUM(COALESCE(pass_qty, 0)) AS total_pass_qty, SUM(COALESCE(fail_qty, 0)) AS total_fail_qty, CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0 THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) ELSE 1 END AS qc_pass_rate FROM fact_quality_inspection), global_operation AS (SELECT COUNT(*) AS total_task_count, SUM(COALESCE(good_qty, 0)) AS total_good_qty, SUM(COALESCE(bad_qty, 0)) AS total_bad_qty, CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0 THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) ELSE 0 END AS operation_defect_rate FROM fact_operation_task), customer_shipment AS (SELECT customer_id, COUNT(*) AS shipment_count, SUM(COALESCE(amount, 0)) AS total_shipment_amount FROM fact_sales_shipment GROUP BY customer_id), customer_return AS (SELECT customer_id, COUNT(*) AS return_count, SUM(COALESCE(amount, 0)) AS total_return_amount FROM fact_sales_return GROUP BY customer_id), order_risk AS (SELECT so.sales_order_id, so.sales_order_number, c.customer_name, so.order_date_utc, so.deal_amount, so.payment_status, gp.avg_completion_rate AS production_completion_rate, gp.pending_wo_count, gp.total_wo_count AS work_order_count, gq.qc_pass_rate, gq.total_fail_qty, go.operation_defect_rate, COALESCE(cs.shipment_count, 0) AS shipment_count, COALESCE(cr.return_count, 0) AS return_count, CASE WHEN COALESCE(cs.shipment_count, 0) > 0 THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count ELSE 0 END AS return_rate FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true CROSS JOIN global_production gp CROSS JOIN global_quality gq CROSS JOIN global_operation go LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id) SELECT sales_order_id, sales_order_number, customer_name, order_date_utc, deal_amount, payment_status, work_order_count, ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate, pending_wo_count, ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate, ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate, return_count, ROUND(return_rate::NUMERIC, 4) AS return_rate, ROUND((CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END)::NUMERIC, 2) AS delay_probability, CASE WHEN (CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END) >= 0.50 THEN 'RED' WHEN (CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END) >= 0.25 THEN 'YELLOW' ELSE 'GREEN' END AS risk_level, CONCAT_WS(' | ', CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END, CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END, CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END, CASE WHEN return_rate > 0.05 THEN '历史退货率高' END, CASE WHEN payment_status = 'UNPAID' THEN '未付款' END) AS risk_reasons FROM order_risk ORDER BY delay_probability DESC, deal_amount DESC", - "parameters": {} - } -] \ No newline at end of file diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/main.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/main.py deleted file mode 100644 index 07f4694..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/main.py +++ /dev/null @@ -1,373 +0,0 @@ -from pathlib import Path -from typing import Any -import asyncio -import logging - -# 支持直接运行和模块导入两种方式 -try: - from .utils import load_json, generate_tool_name, generate_input_schema - from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema - from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config - from .utils.logger_config import logger_config -except ImportError: - from utils import load_json, generate_tool_name, generate_input_schema - from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema - from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config - from utils.logger_config import logger_config - -from mcp.server.models import InitializationOptions -from mcp.server import NotificationOptions, Server -import mcp.types as types - -# 初始化 MCP 专用日志器 -mcp_logger = logger_config.setup_mcp_logging() - -# ========== 数据源配置 ========== -# 数据源类型常量 -DATA_SOURCE_API = "api" # 仅使用API数据 -DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据 -DATA_SOURCE_BOTH = "both" # 合并本地和API数据 - -# 默认数据源(可修改) -DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL -# ================================ - - -def get_queries(): - """ - 获取业务查询配置 - - Returns: - list: 包含所有业务查询配置的列表 - """ - try: - # 获取当前文件所在目录 - current_dir = Path(__file__).parent - - # 构建 businessQueries.json 的路径 - json_path = current_dir / "businessQueries.json" - - mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}") - - # 使用 load_json 方法读取 JSON 文件 - queries = load_json(json_path) - - mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置") - - return queries - except Exception as e: - mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True) - raise - - -def generate_tool_schema_from_query(query: dict) -> types.Tool: - """ - 根据查询配置生成 MCP 工具模式 - - Args: - query: 单个查询配置字典 - - Returns: - types.Tool: MCP 工具对象 - """ - try: - # 获取参数定义并生成 inputSchema - parameters = query.get('parameters', {}) - input_schema = generate_input_schema(parameters) - - # 生成工具名称(格式: tool_拼音_id) - # tool_name = generate_tool_name(query['businessName'], query['id']) - tool_name = query['businessName'] - # 构建工具描述,包含业务名称和业务描述 - description = f"{query['businessName']}: {query['businessDescription']}" - - mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}") - - return types.Tool( - name=tool_name, - description=description, - inputSchema=input_schema - ) - except Exception as e: - mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True) - raise - - -# 创建 MCP 服务器实例 -server = Server("lzwcai-mcpskills-analyzeOrder") - -# 缓存查询配置,避免重复加载 -_queries_cache = None - - -async def get_queries_cache(source: str = None): - """ - 获取或初始化查询配置缓存 - - Args: - source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE) - - "api": 仅使用API数据 - - "local": 仅使用本地JSON数据 - - "both": 合并本地和API数据 - - Returns: - 查询配置列表 - """ - global _queries_cache - if _queries_cache is None: - source = source or DEFAULT_DATA_SOURCE - mcp_logger.info(f"初始化查询配置(数据源: {source})...") - - if source == DATA_SOURCE_LOCAL: - _queries_cache = get_queries() - mcp_logger.info(f"本地配置: {len(_queries_cache)} 条") - - elif source == DATA_SOURCE_API: - try: - _queries_cache = await call_third_party_api() - mcp_logger.info(f"API配置: {len(_queries_cache)} 条") - mcp_logger.info(f"API配置数组: {_queries_cache}") - except Exception as e: - mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}") - _queries_cache = get_queries() - - else: # DATA_SOURCE_BOTH - local = get_queries() - try: - api = await call_third_party_api() - except Exception as e: - mcp_logger.warning(f"API获取失败: {e}") - api = [] - _queries_cache = local + api - mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})") - - return _queries_cache - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """ - 列出所有动态生成的 MCP 工具 - - Returns: - list[types.Tool]: 所有可用的工具列表 - """ - try: - mcp_logger.info("收到列出工具请求") - - queries = await get_queries_cache() - tools = [] - - for query in queries: - tool = generate_tool_schema_from_query(query) - tools.append(tool) - - mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具") - mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}") - - return tools - except Exception as e: - mcp_logger.error(f"列出工具失败: {e}", exc_info=True) - raise - - -@server.call_tool() -async def handle_call_tool( - name: str, - arguments: dict[str, Any] | None -) -> list[types.TextContent]: - """ - 处理工具调用请求 - - Args: - name: 工具名称 - arguments: 工具参数 - - Returns: - list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置) - """ - try: - mcp_logger.info(f"收到工具调用请求: {name}") - mcp_logger.debug(f"工具参数: {arguments}") - - # 获取查询配置缓存 - queries = await get_queries_cache() - - # 根据工具名称查找对应的 item(接口配置) - tool_item = None - for query in queries: - # tool_name = generate_tool_name(query['businessName'], query['id']) - tool_name = query['businessName'] - if tool_name == name: - tool_item = query - break - - # 构建返回结果 - import json - - if tool_item: - request_data = { - "datasourceId": get_datasource_id(), - "businessName": tool_item.get("businessName"), - "businessDescription": tool_item.get("businessDescription"), - "sqlTemplate": tool_item.get("sqlTemplate"), - "parameters": tool_item.get("parameters"), - "testParams": arguments or {} - } - - # 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data - if arguments and arguments.get("targetDatabaseName"): - request_data["targetDatabaseName"] = arguments["targetDatabaseName"] - mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}") - - # 调用测试SQL API - try: - mcp_logger.info("正在调用测试SQL API...") - api_response = test_sql_with_schema(request_data) - mcp_logger.info("测试SQL API调用成功") - - # 返回包含 data 字段的结果 - result = { - "success": True, - "data": api_response - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - - except Exception as e: - error_msg = f"调用测试SQL API失败: {str(e)}" - mcp_logger.error(error_msg, exc_info=True) - result = { - "success": False, - "error": error_msg, - "data": None - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - else: - error_msg = f"未找到工具 {name} 对应的配置" - result = { - "success": False, - "error": error_msg, - "data": None - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - - mcp_logger.debug(f"工具调用结果: {result_text}") - - return [ - types.TextContent( - type="text", - text=result_text - ) - ] - except Exception as e: - error_msg = f"工具调用失败: {name}, 错误: {e}" - mcp_logger.error(error_msg, exc_info=True) - return [ - types.TextContent( - type="text", - text=f"错误: {error_msg}" - ) - ] - - -async def call_third_party_api(skill_id: str = None) -> list: - """ - 调用第三方API获取技能信息并返回处理后的数据 - - Args: - skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178) - - Returns: - 处理后的查询配置列表(businessQueries格式) - - Example: - queries = await call_third_party_api() - # 返回: [{"id": "...", "businessName": "...", ...}, ...] - """ - try: - # 如果没有传入 skill_id,则从环境变量读取 - if skill_id is None: - skill_id = get_skill_id() - - mcp_logger.info(f"调用第三方API,skill_id: {skill_id}") - - # 获取原始数据 - raw_result = get_skill_by_id(skill_id) - - mcp_logger.info(f"成功{raw_result}") - - # 处理并返回 - processed_queries = process_skill_response(raw_result) - - mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据") - return processed_queries - - except Exception as e: - mcp_logger.error(f"API调用失败: {e}", exc_info=True) - raise - - -async def async_main(): - """MCP 服务器异步主函数""" - try: - mcp_logger.info("=" * 60) - mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder") - mcp_logger.info("版本: 0.1.0") - mcp_logger.info("=" * 60) - - # 输出环境配置信息 - env_config = get_env_config() - mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}") - mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}") - mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}") - mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}") - mcp_logger.info("=" * 60) - - from mcp.server.stdio import stdio_server - - async with stdio_server() as (read_stream, write_stream): - mcp_logger.info("MCP 服务器已启动,等待客户端连接...") - - await server.run( - read_stream, - write_stream, - InitializationOptions( - server_name="lzwcai-mcpskills-analyzeOrder", - server_version="0.1.0", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - mcp_logger.info("MCP 服务器已关闭") - - except Exception as e: - mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True) - raise - - -def main(): - """入口点函数(用于 console_scripts)""" - try: - # 初始化系统日志 - # MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信 - logger_config.setup_logging( - app_name="lzwcai_mcp_sqlexecutor", - log_level=logging.INFO, - console_output=False # 禁用控制台输出 - ) - - mcp_logger.info("开始运行 MCP SQL Executor 服务器") - asyncio.run(async_main()) - - except KeyboardInterrupt: - mcp_logger.info("收到中断信号,正在关闭服务器...") - except Exception as e: - mcp_logger.error(f"程序运行失败: {e}", exc_info=True) - raise - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml deleted file mode 100644 index c12ec43..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcpskills-analyzeWorkOrder" -version = "0.1.10" -description = "MCP server for executing business SQL queries with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.13" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "executor", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcpskills-analyzeWorkOrder = "lzwcai_mcpskills_analyzeWorkOrder.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_analyzeWorkOrder"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json" = "lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json" diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/sql11 b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/sql11 deleted file mode 100644 index 39b7319..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/sql11 +++ /dev/null @@ -1,169 +0,0 @@ --- ===================================================== --- 交付风险预测:延迟概率与红/黄/绿预警等级 --- 基于历史订单的生产周期、物流延误、设备故障等特征 --- ===================================================== - -WITH --- 1. 全局生产特征(汇总所有工单) -global_production AS ( - SELECT - COUNT(*) AS total_wo_count, - AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate, - SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count, - SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count - FROM fact_work_order -), - --- 2. 全局质检特征 -global_quality AS ( - SELECT - COUNT(*) AS total_inspection_count, - SUM(COALESCE(pass_qty, 0)) AS total_pass_qty, - SUM(COALESCE(fail_qty, 0)) AS total_fail_qty, - CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0 - THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) - ELSE 1 END AS qc_pass_rate - FROM fact_quality_inspection -), - --- 3. 全局工序不良特征 -global_operation AS ( - SELECT - COUNT(*) AS total_task_count, - SUM(COALESCE(good_qty, 0)) AS total_good_qty, - SUM(COALESCE(bad_qty, 0)) AS total_bad_qty, - CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0 - THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) - ELSE 0 END AS operation_defect_rate - FROM fact_operation_task -), - --- 4. 客户级别发货统计 -customer_shipment AS ( - SELECT - customer_id, - COUNT(*) AS shipment_count, - SUM(COALESCE(amount, 0)) AS total_shipment_amount - FROM fact_sales_shipment - GROUP BY customer_id -), - --- 5. 客户级别退货统计 -customer_return AS ( - SELECT - customer_id, - COUNT(*) AS return_count, - SUM(COALESCE(amount, 0)) AS total_return_amount - FROM fact_sales_return - GROUP BY customer_id -), - --- 6. 订单风险评估 -order_risk AS ( - SELECT - so.sales_order_id, - so.sales_order_number, - c.customer_name, - so.order_date_utc, - so.deal_amount, - so.payment_status, - - -- 全局生产指标 - gp.avg_completion_rate AS production_completion_rate, - gp.pending_wo_count, - gp.total_wo_count AS work_order_count, - - -- 全局质检指标 - gq.qc_pass_rate, - gq.total_fail_qty, - - -- 全局工序指标 - go.operation_defect_rate, - - -- 客户级别指标 - COALESCE(cs.shipment_count, 0) AS shipment_count, - COALESCE(cr.return_count, 0) AS return_count, - CASE WHEN COALESCE(cs.shipment_count, 0) > 0 - THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count - ELSE 0 END AS return_rate - - FROM fact_sales_order so - LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true - CROSS JOIN global_production gp - CROSS JOIN global_quality gq - CROSS JOIN global_operation go - LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id - LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id -) - --- 7. 最终输出 -SELECT - sales_order_id, - sales_order_number, - customer_name, - order_date_utc, - deal_amount, - payment_status, - - -- 风险特征 - work_order_count, - ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate, - pending_wo_count, - ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate, - ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate, - return_count, - ROUND(return_rate::NUMERIC, 4) AS return_rate, - - -- 延迟概率 - ROUND(( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 - WHEN production_completion_rate < 0.5 THEN 0.20 - WHEN production_completion_rate < 0.8 THEN 0.10 - ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 - WHEN qc_pass_rate < 0.9 THEN 0.15 - WHEN qc_pass_rate < 0.95 THEN 0.08 - ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 - WHEN operation_defect_rate > 0.05 THEN 0.12 - WHEN operation_defect_rate > 0.02 THEN 0.05 - ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 - WHEN return_rate > 0.05 THEN 0.08 - WHEN return_rate > 0.02 THEN 0.03 - ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 - WHEN payment_status = 'PARTIAL' THEN 0.05 - ELSE 0 END - )::NUMERIC, 2) AS delay_probability, - - -- 红/黄/绿预警 - CASE - WHEN ( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END - ) >= 0.50 THEN 'RED' - WHEN ( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END - ) >= 0.25 THEN 'YELLOW' - ELSE 'GREEN' - END AS risk_level, - - -- 风险原因 - CONCAT_WS(' | ', - CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END, - CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END, - CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END, - CASE WHEN return_rate > 0.05 THEN '历史退货率高' END, - CASE WHEN payment_status = 'UNPAID' THEN '未付款' END - ) AS risk_reasons - -FROM order_risk -ORDER BY delay_probability DESC, deal_amount DESC; diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/__init__.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/__init__.py deleted file mode 100644 index 3df2b44..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Utils package for lzwcai_mcp_sqlexecutor""" - -from .json_helper import load_json -from .name_helper import generate_tool_name -from .schema_helper import generate_input_schema, validate_input_schema -from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema -from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable - -__all__ = [ - 'load_json', - 'generate_tool_name', - 'generate_input_schema', - 'validate_input_schema', - 'DataSourceAPIClient', - 'get_skill_by_id', - 'process_skill_response', - 'test_sql_with_schema', - 'get_database_id', - 'get_datasource_id', - 'get_skill_id', - 'get_backend_base_url', - 'get_env_config', - 'set_env_variable' -] - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/api_client.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/api_client.py deleted file mode 100644 index cf49a0a..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/api_client.py +++ /dev/null @@ -1,318 +0,0 @@ -""" -第三方API调用客户端 -用于调用外部数据源接口 -""" - -import httpx -import logging -import json -from typing import Dict, Any, Optional, List - -# 支持直接运行和模块导入两种方式 -try: - from .env_config import get_backend_base_url -except ImportError: - from env_config import get_backend_base_url - -# 获取日志记录器 -logger = logging.getLogger(__name__) - - -class DataSourceAPIClient: - """数据源API客户端""" - - def __init__( - self, - base_url: Optional[str] = None, - token: Optional[str] = None - ): - """ - 初始化API客户端 - - Args: - base_url: API基础URL(默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088) - token: 认证令牌(Bearer Token) - """ - # 如果没有传入 base_url,则从环境变量读取 - if base_url is None: - base_url = get_backend_base_url() - - self.base_url = base_url.rstrip('/') - self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw" - self.client = httpx.Client(timeout=30.0) - - def _get_headers(self) -> Dict[str, str]: - """ - 获取请求头 - - Returns: - 请求头字典 - """ - return { - 'Authorization': f'Bearer {self.token}', - } - - def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]: - """ - 根据技能ID获取技能信息 - - Args: - skill_id: 技能ID - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - try: - url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}" - - logger.info(f"正在调用API: {url}") - logger.info(f"请求参数 - skill_id: {skill_id}") - - response = self.client.get( - url, - headers=self._get_headers() - ) - - # 检查HTTP状态码 - response.raise_for_status() - - # 解析JSON响应 - data = response.json() - - logger.info(f"API调用成功: {url}") - logger.debug(f"响应数据: {data}") - - return data - - except httpx.TimeoutException: - error_msg = f"API请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - - except httpx.RequestError as e: - error_msg = f"API请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理API响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]: - """ - 测试SQL语句并返回执行结果 - - Args: - request_data: 请求数据,包含以下字段: - - datasourceId: 数据源ID - - businessName: 业务名称 - - businessDescription: 业务描述 - - sqlTemplate: SQL模板 - - parameters: 参数定义 - - testParams: 测试参数 - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - try: - url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema" - - # 构建请求头(包含Content-Type) - headers = self._get_headers() - headers['Content-Type'] = 'application/json' - headers['Accept'] = '*/*' - - logger.info(f"正在调用测试SQL API: {url}") - logger.info(f"请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - - # 发送POST请求 - response = self.client.post( - url, - headers=headers, - json=request_data - ) - - # 检查HTTP状态码 - response.raise_for_status() - - # 解析JSON响应 - result = response.json() - - logger.info(f"测试SQL API调用成功") - logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") - - # 处理返回数据结构: {code, data: {errorMessage, data}, msg} - # 检查外层 code - if result.get("code") != 200: - error_msg = result.get("msg", "接口返回错误") - logger.error(f"接口返回错误: code={result.get('code')}, msg={error_msg}") - raise Exception(error_msg) - - # 检查内层 errorMessage - inner_data = result.get("data", {}) - if inner_data.get("errorMessage"): - error_msg = inner_data.get("errorMessage") - logger.error(f"接口业务错误: {error_msg}") - raise Exception(error_msg) - - # 返回 data.data - return inner_data.get("data") - - except httpx.TimeoutException: - error_msg = f"测试SQL API请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - - except httpx.RequestError as e: - error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理测试SQL API响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def close(self): - """关闭HTTP客户端""" - self.client.close() - - -# 创建默认客户端实例 -default_client = DataSourceAPIClient() - - -def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:根据技能ID获取技能信息 - - Args: - skill_id: 技能ID - base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) - token: 认证令牌(可选,使用默认值) - - Returns: - API响应数据 - """ - if base_url or token: - client = DataSourceAPIClient( - base_url=base_url, - token=token - ) - return client.get_skill_by_id(skill_id) - else: - return default_client.get_skill_by_id(skill_id) - - -def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:测试SQL语句并返回执行结果 - - Args: - request_data: 请求数据,包含以下字段: - - datasourceId: 数据源ID - - businessName: 业务名称 - - businessDescription: 业务描述 - - sqlTemplate: SQL模板 - - parameters: 参数定义 - - testParams: 测试参数 - base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) - token: 认证令牌(可选,使用默认值) - - Returns: - API响应数据 - """ - if base_url or token: - client = DataSourceAPIClient( - base_url=base_url, - token=token - ) - return client.test_sql_with_schema(request_data) - else: - return default_client.test_sql_with_schema(request_data) - - -def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: - """ - 处理API响应数据,映射为businessQueries格式 - - Args: - response: API原始响应数据 - - Returns: - 处理后的查询配置列表 - """ - try: - # 提取data数组 - data_list = response.get("data", []) - - # 默认的员工ID参数schema - default_employee_schema = { - "type": "object", - "required": ["employeeId"], - "properties": { - "employeeId": { - "type": "number", - "description": "员工ID,用于标识员工的唯一数字标识符", - "examples": [1001, 2002] - } - } - } - - # 映射每个skill为businessQuery格式 - queries = [] - for skill in data_list: - # 解析sqlParams字符串为JSON对象 - sql_params = json.loads(skill.get("sqlParams", "{}")) - - # 判断sqlParams是否为空对象 - is_empty_params = ( - not sql_params.get("properties") or - len(sql_params.get("properties", {})) == 0 - ) and ( - not sql_params.get("required") or - len(sql_params.get("required", [])) == 0 - ) - - # 如果是空对象,使用默认的员工ID参数 - if is_empty_params: - logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空,使用默认员工ID参数") - sql_params = default_employee_schema - - # 映射字段 - query = { - "id": skill.get("id"), - "businessName": skill.get("name"), - "businessDescription": skill.get("description"), - "sqlTemplate": skill.get("sqlTemplate"), - "parameters": sql_params, - "datasourceId": skill.get("datasourceId") - } - queries.append(query) - - logger.info(f"成功处理 {len(queries)} 条技能数据") - return queries - - except Exception as e: - logger.error(f"处理API响应数据失败: {e}", exc_info=True) - raise - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/env_config.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/env_config.py deleted file mode 100644 index 585b0a6..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/env_config.py +++ /dev/null @@ -1,106 +0,0 @@ -"""环境变量配置模块""" - -import os -from typing import Optional - - -def get_database_id(default: str = "29") -> str: - """ - 获取数据库ID环境变量 - - Args: - default: 默认值(默认为 "29") - - Returns: - str: 数据库ID - - Environment Variables: - databaseId: 数据库ID - """ - return os.environ.get("databaseId", default) - - -def get_datasource_id(default: str = "") -> str: - """ - 获取数据源ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 数据源ID - - Environment Variables: - datasourceId: 数据源ID - """ - return os.environ.get("datasourceId", default) - - -def get_skill_id(default: str = "") -> str: - """ - 获取技能ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 技能ID - - Environment Variables: - skillId: 技能ID - """ - return os.environ.get("skillId", default) - - -def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: - """ - 获取后端API基础URL环境变量 - - Args: - default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086") - - Returns: - str: 后端API基础URL - - Environment Variables: - backendBaseUrl: 后端API基础URL - """ - return os.environ.get("backendBaseUrl", default) - - -def get_env_config() -> dict: - """ - 获取所有环境配置 - - Returns: - dict: 包含所有配置的字典 - - Example: - config = get_env_config() - print(config['database_id']) # 输出: "29" - print(config['datasource_id']) # 输出: "" - print(config['skill_id']) # 输出: "" - print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086" - """ - return { - "database_id": get_database_id(), - "datasource_id": get_datasource_id(), - "skill_id": get_skill_id(), - "backend_base_url": get_backend_base_url() - } - - -def set_env_variable(key: str, value: str) -> None: - """ - 设置环境变量(仅在当前进程中有效) - - Args: - key: 环境变量名 - value: 环境变量值 - - Example: - set_env_variable("databaseId", "30") - set_env_variable("skillId", "1234567890") - """ - os.environ[key] = value - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/json_helper.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/json_helper.py deleted file mode 100644 index 1a6fc95..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/json_helper.py +++ /dev/null @@ -1,60 +0,0 @@ -"""JSON 文件读取工具""" - -import json -from pathlib import Path -from typing import Any, Union - - -def load_json(json_path: Union[str, Path]) -> Any: - """ - 读取 JSON 文件并返回其内容 - - Args: - json_path: JSON 文件的路径(支持字符串或 Path 对象) - - Returns: - JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型) - - Raises: - FileNotFoundError: 当文件不存在时 - json.JSONDecodeError: 当 JSON 格式无效时 - Exception: 其他读取错误 - - Example: - >>> data = load_json('config.json') - >>> print(data) - {'key': 'value'} - - >>> data = load_json(Path('data/users.json')) - >>> print(data) - [{'id': 1, 'name': 'Alice'}] - """ - try: - # 转换为 Path 对象 - path = Path(json_path) - - # 检查文件是否存在 - if not path.exists(): - raise FileNotFoundError(f"JSON 文件不存在: {json_path}") - - # 检查是否为文件 - if not path.is_file(): - raise ValueError(f"路径不是一个文件: {json_path}") - - # 读取并解析 JSON 文件 - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - return data - - except json.JSONDecodeError as e: - raise json.JSONDecodeError( - f"JSON 格式错误: {e.msg}", - e.doc, - e.pos - ) - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/logger_config.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/logger_config.py deleted file mode 100644 index 290964f..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/logger_config.py +++ /dev/null @@ -1,489 +0,0 @@ -# -*- coding: utf-8 -*- -""" -统一日志配置模块 -提供系统级别的日志配置和管理 -""" - -import os -import sys -import logging -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from datetime import datetime -from pathlib import Path - - -class LoggerConfig: - """日志配置管理类""" - - def __init__(self, logs_dir: str = None): - """初始化日志配置 - - Args: - logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹 - """ - # 确定日志目录 - if logs_dir: - self.logs_dir = Path(logs_dir) - else: - # 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录) - project_root = Path(__file__).parent.parent - self.logs_dir = project_root / "logs" - - # 创建日志目录 - self.logs_dir.mkdir(exist_ok=True) - - # 日志格式 - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' - self.date_format = '%Y-%m-%d %H:%M:%S' - - # 从环境变量获取日志级别,默认为INFO - self.log_level = self._get_log_level_from_env() - - # 是否已初始化 - self._initialized = False - - def _get_log_level_from_env(self) -> int: - """从环境变量获取日志级别 - - Returns: - int: 日志级别 - """ - log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() - - # 日志级别映射 - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'WARN': logging.WARNING, # 兼容性别名 - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, - 'FATAL': logging.CRITICAL # 兼容性别名 - } - - return level_mapping.get(log_level_str, logging.INFO) - - def setup_logging(self, - app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO, - max_file_size: int = 10 * 1024 * 1024, # 10MB - backup_count: int = 5, - console_output: bool = True) -> logging.Logger: - """设置系统日志配置 - - Args: - app_name: 应用名称,用于日志文件命名 - log_level: 日志级别 - max_file_size: 单个日志文件最大大小(字节) - backup_count: 保留的备份文件数量 - console_output: 是否输出到控制台 - - Returns: - logging.Logger: 配置好的根日志器 - """ - if self._initialized: - return logging.getLogger() - - # 设置根日志器 - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # 清除现有的处理器 - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - # 创建格式化器 - formatter = logging.Formatter(self.log_format, self.date_format) - - # 1. 主日志文件 - 按大小滚动 - main_log_file = self.logs_dir / f"{app_name}.log" - file_handler = RotatingFileHandler( - main_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # 2. 错误日志文件 - 只记录ERROR及以上级别 - error_log_file = self.logs_dir / f"{app_name}_error.log" - error_handler = RotatingFileHandler( - error_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - error_handler.setLevel(logging.ERROR) - error_handler.setFormatter(formatter) - root_logger.addHandler(error_handler) - - # 3. 按日期滚动的日志文件 - daily_log_file = self.logs_dir / f"{app_name}_daily.log" - daily_handler = TimedRotatingFileHandler( - daily_log_file, - when='midnight', - interval=1, - backupCount=30, # 保留30天 - encoding='utf-8' - ) - daily_handler.setLevel(log_level) - daily_handler.setFormatter(formatter) - daily_handler.suffix = "%Y-%m-%d" - root_logger.addHandler(daily_handler) - - # 4. 控制台输出 - # 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信 - if console_output: - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setLevel(log_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - self.date_format - ) - console_handler.setFormatter(console_formatter) - root_logger.addHandler(console_handler) - - self._initialized = True - - # 记录初始化信息 - root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") - root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}") - - return root_logger - - def get_module_logger(self, module_name: str) -> logging.Logger: - """获取模块专用日志器 - - Args: - module_name: 模块名称 - - Returns: - logging.Logger: 模块日志器 - """ - return logging.getLogger(module_name) - - def create_component_logger(self, - component_name: str, - log_file: str = None, - log_level: int = None) -> logging.Logger: - """为特定组件创建独立的日志器 - - Args: - component_name: 组件名称 - log_file: 独立日志文件名(可选) - log_level: 日志级别(可选) - - Returns: - logging.Logger: 组件日志器 - """ - logger = logging.getLogger(component_name) - - if log_file: - # 为组件创建独立的日志文件 - component_log_file = self.logs_dir / log_file - handler = RotatingFileHandler( - component_log_file, - maxBytes=5 * 1024 * 1024, # 5MB - backupCount=3, - encoding='utf-8' - ) - - formatter = logging.Formatter(self.log_format, self.date_format) - handler.setFormatter(formatter) - - if log_level: - handler.setLevel(log_level) - - logger.addHandler(handler) - logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}") - - return logger - - def setup_mqtt_logging(self) -> logging.Logger: - """设置MQTT专用日志 - - Returns: - logging.Logger: MQTT日志器 - """ - return self.create_component_logger( - "mqtt_communication", - "mqtt_communication.log", - logging.DEBUG - ) - - def setup_mcp_logging(self) -> logging.Logger: - """设置MCP专用日志 - - Returns: - logging.Logger: MCP日志器 - """ - return self.create_component_logger( - "mcp_services", - "mcp_services.log", - logging.DEBUG - ) - - def setup_api_logging(self) -> logging.Logger: - """设置API专用日志 - - Returns: - logging.Logger: API日志器 - """ - return self.create_component_logger( - "api_requests", - "api_requests.log", - logging.INFO - ) - - def get_logs_info(self) -> dict: - """获取日志系统信息 - - Returns: - dict: 日志系统信息 - """ - log_files = [] - if self.logs_dir.exists(): - for log_file in self.logs_dir.glob("*.log*"): - stat = log_file.stat() - log_files.append({ - "name": log_file.name, - "size": stat.st_size, - "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() - }) - - return { - "logs_directory": str(self.logs_dir), - "initialized": self._initialized, - "log_files": log_files, - "total_files": len(log_files) - } - - def cleanup_old_logs(self, days: int = 30): - """清理旧日志文件 - - Args: - days: 保留天数 - """ - if not self.logs_dir.exists(): - return - - from datetime import timedelta - cutoff_time = datetime.now() - timedelta(days=days) - - cleaned_files = [] - for log_file in self.logs_dir.glob("*.log*"): - if log_file.stat().st_mtime < cutoff_time.timestamp(): - try: - log_file.unlink() - cleaned_files.append(log_file.name) - except Exception as e: - logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}") - - if cleaned_files: - logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}") - - def set_log_level(self, level: int, logger_name: str = None): - """动态调整日志级别 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - old_level = logger.level - logger.setLevel(level) - - # 同时调整所有处理器的级别 - for handler in logger.handlers: - if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr): - # 不调整控制台处理器的级别,保持原有设置 - handler.setLevel(level) - - level_name = logging.getLevelName(level) - old_level_name = logging.getLevelName(old_level) - target = logger_name or "根日志器" - - logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}") - - def set_temporary_log_level(self, level: int, logger_name: str = None): - """临时调整日志级别(会保存原始级别用于恢复) - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if not hasattr(self, '_original_levels'): - self._original_levels = {} - - target_name = logger_name or 'root' - - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - # 保存原始级别 - if target_name not in self._original_levels: - self._original_levels[target_name] = logger.level - - # 设置新级别 - self.set_log_level(level, logger_name) - - level_name = logging.getLevelName(level) - target = logger_name or "根日志器" - logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)") - - def restore_log_level(self, logger_name: str = None): - """恢复日志级别到调整前的状态 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - if not hasattr(self, '_original_levels'): - logging.warning("没有找到保存的原始日志级别") - return - - target_name = logger_name or 'root' - - if target_name not in self._original_levels: - logging.warning(f"没有找到 {target_name} 的原始日志级别") - return - - original_level = self._original_levels[target_name] - self.set_log_level(original_level, logger_name) - - # 清除保存的级别 - del self._original_levels[target_name] - - target = logger_name or "根日志器" - level_name = logging.getLevelName(original_level) - logging.info(f"已恢复日志级别: {target} -> {level_name}") - - def get_current_log_levels(self) -> dict: - """获取当前所有日志器的级别信息 - - Returns: - dict: 日志器级别信息 - """ - levels_info = {} - - # 根日志器 - root_logger = logging.getLogger() - levels_info['root'] = { - 'level': root_logger.level, - 'level_name': logging.getLevelName(root_logger.level), - 'handlers_count': len(root_logger.handlers) - } - - # 其他已创建的日志器 - for name, logger in logging.Logger.manager.loggerDict.items(): - if isinstance(logger, logging.Logger): - levels_info[name] = { - 'level': logger.level, - 'level_name': logging.getLevelName(logger.level), - 'handlers_count': len(logger.handlers) - } - - return levels_info - - -# 全局日志配置实例 -logger_config = LoggerConfig() - - -def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO) -> logging.Logger: - """系统日志初始化快捷函数 - - Args: - app_name: 应用名称 - log_level: 日志级别 - - Returns: - logging.Logger: 根日志器 - """ - return logger_config.setup_logging(app_name, log_level) - - -def get_logger(name: str) -> logging.Logger: - """获取日志器的快捷函数 - - Args: - name: 日志器名称 - - Returns: - logging.Logger: 日志器实例 - """ - return logger_config.get_module_logger(name) - - -def set_log_level(level: int, logger_name: str = None): - """动态调整日志级别的快捷函数 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 调整根日志器为DEBUG级别 - set_log_level(logging.DEBUG) - - # 调整特定模块日志器为WARNING级别 - set_log_level(logging.WARNING, "agent_ontology.core") - """ - logger_config.set_log_level(level, logger_name) - - -def set_temporary_log_level(level: int, logger_name: str = None): - """临时调整日志级别的快捷函数 - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 临时调整为DEBUG级别进行调试 - set_temporary_log_level(logging.DEBUG) - # ... 进行调试 ... - # 恢复原始级别 - restore_log_level() - """ - logger_config.set_temporary_log_level(level, logger_name) - - -def restore_log_level(logger_name: str = None): - """恢复日志级别的快捷函数 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - logger_config.restore_log_level(logger_name) - - -def get_current_log_levels() -> dict: - """获取当前日志级别信息的快捷函数 - - Returns: - dict: 日志器级别信息 - - Examples: - levels = get_current_log_levels() - print(f"根日志器级别: {levels['root']['level_name']}") - """ - return logger_config.get_current_log_levels() - - -# 便捷的日志级别常量 -class LogLevel: - """日志级别常量类""" - DEBUG = logging.DEBUG - INFO = logging.INFO - WARNING = logging.WARNING - ERROR = logging.ERROR - CRITICAL = logging.CRITICAL \ No newline at end of file diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/name_helper.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/name_helper.py deleted file mode 100644 index d66cf32..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/name_helper.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" -名称生成工具模块 -""" - -from pypinyin import lazy_pinyin, Style -import logging - -logger = logging.getLogger(__name__) - - -def generate_tool_name(business_name: str, tool_id: str) -> str: - """ - 根据业务名称和ID生成工具名称 - 格式: tool_拼音_id - - Args: - business_name: 业务名称(中文) - tool_id: 工具ID - - Returns: - str: 格式化的工具名称 - """ - try: - # 将中文转换为拼音(无音调,小写) - pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL) - # 拼接拼音 - pinyin_str = ''.join(pinyin_list) - - # 将 ID 中的 '-' 替换为 '_' - formatted_id = tool_id.replace('-', '_') - - # 组合成最终的工具名称 - tool_name = f"tool_{pinyin_str}_{formatted_id}" - - return tool_name - except Exception as e: - logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True) - # 降级处理:如果拼音转换失败,使用 ID - return f"tool_{tool_id.replace('-', '_')}" - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/schema_helper.py b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/schema_helper.py deleted file mode 100644 index 626559f..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/utils/schema_helper.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Schema 生成工具模块 -""" - -from typing import Any, Dict, List - - -def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]: - """ - 从查询配置的参数定义生成 MCP 工具的 inputSchema - - 此函数会保留完整的 JSON Schema 信息,包括: - - type: Schema 类型(通常是 "object") - - required: 必填字段列表 - - properties: 属性定义(包括每个属性的 type, description, format, examples 等) - - description: Schema 的整体描述(如果有) - - 以及其他任何 JSON Schema 标准字段 - - 此函数还会自动添加以下字段(如果原始 parameters 中未定义): - - targetDatabaseName: 目标数据库名称(非必填,默认为空字符串) - - Args: - parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象 - - Returns: - Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象 - - Example: - >>> params = { - ... "type": "object", - ... "required": ["userId", "startTime"], - ... "properties": { - ... "userId": { - ... "type": "integer", - ... "description": "用户的唯一标识符", - ... "examples": [10086] - ... }, - ... "startTime": { - ... "type": "string", - ... "format": "date-time", - ... "description": "查询的起始时间", - ... "examples": ["2023-01-01 00:00:00"] - ... } - ... } - ... } - >>> schema = generate_input_schema(params) - >>> # schema 将包含所有原始信息,包括 format 和 examples - >>> # 同时会自动添加 targetDatabaseName 字段 - """ - # 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用 - # 但确保至少包含 type 和 properties - if not parameters: - # 如果 parameters 为空,返回一个空的 object schema - return { - "type": "object", - "properties": {}, - "required": [] - } - - # 检查 parameters 是否已经是完整的 JSON Schema 格式 - # 完整格式应该有 "type": "object" 和 "properties" 字段 - if "type" in parameters and parameters.get("type") == "object" and "properties" in parameters: - # 已经是完整的 JSON Schema 格式,直接使用 - input_schema = dict(parameters) - else: - # parameters 是简化格式(直接是参数定义),需要转换为 JSON Schema 格式 - # 例如: {"workOrderNumber": {"type": "string", "description": "...", "required": true}} - properties = {} - required = [] - - for param_name, param_def in parameters.items(): - if isinstance(param_def, dict): - # 提取 required 标记 - if param_def.get("required", False): - required.append(param_name) - - # 复制参数定义,但移除 required 字段(它应该在顶层的 required 数组中) - prop_def = {k: v for k, v in param_def.items() if k != "required"} - properties[param_name] = prop_def - - input_schema = { - "type": "object", - "properties": properties, - "required": required - } - - # 确保必需的字段存在 - if "type" not in input_schema: - input_schema["type"] = "object" - - if "properties" not in input_schema: - input_schema["properties"] = {} - - if "required" not in input_schema: - input_schema["required"] = [] - - # 添加 targetDatabaseName 字段(如果不存在) - if "targetDatabaseName" not in input_schema["properties"]: - input_schema["properties"]["targetDatabaseName"] = { - "type": "string", - "description": "目标数据库名称", - "default": "" - } - - # 保留所有其他字段,如 description, examples, format 等 - # JSON Schema 标准支持的字段都会被保留: - # - additionalProperties - # - patternProperties - # - minProperties / maxProperties - # - dependencies - # - 等等 - - return input_schema - - -def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: - """ - 验证 inputSchema 是否符合基本的 JSON Schema 规范 - - Args: - schema: 要验证的 schema 对象 - - Returns: - tuple[bool, str]: (是否有效, 错误消息或成功消息) - - Example: - >>> schema = {"type": "object", "properties": {"id": {"type": "string"}}} - >>> is_valid, msg = validate_input_schema(schema) - >>> print(is_valid, msg) - True, "Schema 验证通过" - """ - if not isinstance(schema, dict): - return False, "Schema 必须是一个字典对象" - - if schema.get("type") != "object": - return False, "Schema 的 type 字段必须是 'object'" - - if "properties" not in schema: - return False, "Schema 必须包含 properties 字段" - - if not isinstance(schema.get("properties"), dict): - return False, "Schema 的 properties 字段必须是一个字典对象" - - # 验证 required 字段(如果存在) - if "required" in schema: - required = schema["required"] - if not isinstance(required, list): - return False, "Schema 的 required 字段必须是一个列表" - - # 验证所有 required 的字段都在 properties 中定义 - properties = schema["properties"] - for field in required: - if field not in properties: - return False, f"必填字段 '{field}' 未在 properties 中定义" - - # 验证 properties 中每个字段的定义 - for prop_name, prop_def in schema["properties"].items(): - if not isinstance(prop_def, dict): - return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" - - if "type" not in prop_def: - return False, f"属性 '{prop_name}' 必须包含 type 字段" - - return True, "Schema 验证通过" - diff --git a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/uv.lock b/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/uv.lock deleted file mode 100644 index 017ec10..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/lzwcai_mcpskills_analyzeWorkOrder/uv.lock +++ /dev/null @@ -1,497 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, -] - -[[package]] -name = "anyio" -version = "4.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, -] - -[[package]] -name = "certifi" -version = "2025.10.5" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" }, -] - -[[package]] -name = "click" -version = "8.3.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, -] - -[[package]] -name = "httpx-sse" -version = "0.4.3" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" }, -] - -[[package]] -name = "lzwcai-mcpskills-analyzeWorkOrder" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "httpx" }, - { name = "mcp", extra = ["cli"] }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.10.1" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" }, -] - -[[package]] -name = "mcp" -version = "1.10.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "jsonschema" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-multipart" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" }, -] - -[package.optional-dependencies] -cli = [ - { name = "python-dotenv" }, - { name = "typer" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, -] - -[[package]] -name = "pydantic" -version = "2.12.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, -] - -[[package]] -name = "python-dotenv" -version = "1.1.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.20" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" }, -] - -[[package]] -name = "rpds-py" -version = "0.27.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" }, -] - -[[package]] -name = "sse-starlette" -version = "3.0.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" }, -] - -[[package]] -name = "starlette" -version = "0.48.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" }, -] - -[[package]] -name = "typer" -version = "0.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, -] - -[[package]] -name = "uvicorn" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" }, -] diff --git a/lzwcai_mcpskills_analyzeWorkOrder/main.py b/lzwcai_mcpskills_analyzeWorkOrder/main.py deleted file mode 100644 index 015f7d0..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/main.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Entry point for lzwcai-mcpskills-analyzeOrder -Runs the MCP server for SQL query execution -""" -import os - -os.environ["databaseId"] = "19" -os.environ["datasourceId"] = "19" -os.environ["backendBaseUrl"] = "http://192.168.11.24:8088" -if __name__ == "__main__": - # Import and run the actual MCP server - from lzwcai_mcpskills_analyzeOrder.main import main - main() diff --git a/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml b/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml deleted file mode 100644 index bf24164..0000000 --- a/lzwcai_mcpskills_analyzeWorkOrder/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcpskills-analyzeWorkOrder" -version = "0.1.12" -description = "MCP server for executing business SQL queries with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "executor", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcpskills-analyzeWorkOrder = "lzwcai_mcpskills_analyzeWorkOrder.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_analyzeWorkOrder"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json" = "lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json" diff --git a/lzwcai_mcpskills_mfg_data_agent/README.md b/lzwcai_mcpskills_mfg_data_agent/README.md deleted file mode 100644 index 46ac7be..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# lzwcai-mcpskills-mfg-data-agent (制造业数据智能体) - -一个基于 MCP (Model Context Protocol) 的制造业数据智能体服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录 -- 🌐 中文支持:工具名称自动转换为拼音 -- 🏭 制造业场景:专注于制造业数据分析与智能决策 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcpskills-mfg-data-agent -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcpskills_mfg_data_agent -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcpskills-mfg-data-agent -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcpskills-mfg-data-agent -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcpskills_mfg_data_agent.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "mfg-data-agent": { - "command": "lzwcai-mcpskills-mfg-data-agent" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "生产订单查询", - "businessDescription": "根据订单ID查询生产订单信息", - "sqlTemplate": "SELECT * FROM production_orders WHERE order_id = {{orderId}}", - "parameters": { - "type": "object", - "required": ["orderId"], - "properties": { - "orderId": { - "type": "string", - "description": "生产订单的唯一标识符", - "examples": ["PO-2024-001"] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcpskills_mfg_data_agent - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcpskills_mfg_data_agent.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 许可证 - -MIT License - -## 作者 - -lzwcai diff --git a/lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html b/lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html deleted file mode 100644 index 49013b8..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html +++ /dev/null @@ -1,543 +0,0 @@ - - - - - - 人效-产值-损耗三维模型仪表盘 - - - - -
- - - diff --git a/lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html b/lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html deleted file mode 100644 index 4667d07..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html +++ /dev/null @@ -1,791 +0,0 @@ - - - - - - 指标趋势分析与拐点预警 - - - - - - - - -
- - diff --git a/lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html b/lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html deleted file mode 100644 index 6ea2528..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html +++ /dev/null @@ -1,567 +0,0 @@ - - - - - - 一页式决策简报 - - - - -
- - - \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agent/html/OrderDelayWarningAnalysis.html b/lzwcai_mcpskills_mfg_data_agent/html/OrderDelayWarningAnalysis.html deleted file mode 100644 index 0fa2b22..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/html/OrderDelayWarningAnalysis.html +++ /dev/null @@ -1,626 +0,0 @@ - - - - - - 订单延迟预警分析 - - - - - - - - -
- - diff --git a/lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html b/lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html deleted file mode 100644 index 3aa9caa..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html +++ /dev/null @@ -1,680 +0,0 @@ - - - - - - 供应链风险预警 - - - - - - - - - - - - -
- - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.gitignore b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.gitignore deleted file mode 100644 index 505a3b1..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Python-generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info - -# Virtual environments -.venv diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.python-version b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/README.md b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/README.md deleted file mode 100644 index 0ec9671..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# lzwcai-mcpskills-analyzeWorkOrder - -一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录(仅输出到文件,不干扰MCP通信) -- 🌐 中文支持:工具名称自动转换为拼音 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcpskills-analyzeWorkOrder -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcp_sqlexecutor -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcpskills-analyzeWorkOrder -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcpskills-analyzeWorkOrder -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcp_sqlexecutor.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "lzwcai-sqlexecutor": { - "command": "lzwcai-mcpskills-analyzeWorkOrder" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "用户订单查询", - "businessDescription": "根据用户ID查询订单信息", - "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", - "parameters": { - "type": "object", - "required": ["userId"], - "properties": { - "userId": { - "type": "integer", - "description": "用户的唯一标识符", - "examples": [10086] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcp_sqlexecutor - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcp_sqlexecutor.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 常见问题 - -### MCP Inspector 显示 JSON 解析错误 - -如果在使用 MCP Inspector 测试时遇到 `SyntaxError: Unexpected non-whitespace character after JSON` 错误,这是因为: - -1. **原因**:MCP 协议使用 stdio(标准输入输出)进行 JSON-RPC 通信,任何输出到 stdout 的内容(如 print 语句或控制台日志)都会破坏 JSON 格式。 - -2. **解决方案**:本服务器已将所有日志输出配置为仅写入文件(位于 `logs/` 目录),不输出到控制台。日志文件包括: - - `lzwcai_mcp_sqlexecutor.log` - 主日志文件 - - `lzwcai_mcp_sqlexecutor_error.log` - 错误日志 - - `lzwcai_mcp_sqlexecutor_daily.log` - 按日期滚动的日志 - - `mcp_services.log` - MCP 服务专用日志 - -3. **查看日志**:如果需要调试,请查看 `logs/` 目录下的日志文件。 - -## 许可证 - -MIT License - -## 作者 - -lzwcai - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/__init__.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/__init__.py deleted file mode 100644 index 3e52bf0..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -lzwcai-mcpskills-analyzeOrder - MCP server for executing business SQL queries -""" - -__version__ = "0.1.2" -__author__ = "lzwcai" - -__all__ = [] - 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 deleted file mode 100644 index 2498d8b..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "id": "2006300000000000001", - "businessName": "OrderDelayWarningAnalysis", - "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", - "datasourceId": "19", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000002", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", - "datasourceId": "19", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC LIMIT 15 ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END LIMIT 15 ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC LIMIT 15 ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000003", - "businessName": "SupplyChainRiskWarning", - "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", - "datasourceId": "19", - "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 15 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 15 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 15 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 15 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs LIMIT 15 ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000004", - "businessName": "EfficiencyOutputLossDashboard", - "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", - "datasourceId": "19", - "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 15 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 15 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 15 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000005", - "businessName": "OnePageDecisionBrief", - "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", - "datasourceId": "19", - "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 88 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 65 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 12 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 80 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 40 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 15 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr LIMIT 15 ; WITH current_month AS (SELECT 'current_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE)), last_month AS (SELECT 'last_month' AS period, COUNT(*) AS order_count, SUM(deal_amount) AS sales_amount FROM fact_sales_order WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')) SELECT period, order_count, ROUND(sales_amount, 2) AS sales_amount FROM current_month UNION ALL SELECT * FROM last_month LIMIT 15 ; SELECT c.customer_name, COUNT(so.sales_order_id) AS order_count, ROUND(SUM(so.deal_amount), 2) AS total_order_amount, ROUND(SUM(so.deal_amount) * 100.0 / (SELECT SUM(deal_amount) FROM fact_sales_order), 2) AS contribution_rate, SUM(CASE WHEN so.payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_order_count, SUM(CASE WHEN so.payment_status = 'UNPAID' THEN so.deal_amount ELSE 0 END) AS receivable_amount FROM fact_sales_order so INNER JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' GROUP BY c.customer_name ORDER BY total_order_amount DESC LIMIT 15 ; SELECT p.product_category, COUNT(DISTINCT wo.work_order_id) AS work_order_count, ROUND(SUM(wo.planned_qty), 0) AS planned_qty, ROUND(SUM(wo.completed_qty), 0) AS completed_qty, ROUND(SUM(wo.completed_qty) * 100.0 / NULLIF(SUM(wo.planned_qty), 0), 1) AS completion_rate, COUNT(DISTINCT lr.worker_name) AS worker_count, ROUND(SUM(lr.duration_minutes) / 60.0, 1) AS total_work_hours FROM dim_product p LEFT JOIN fact_work_order wo ON p.product_id = wo.product_id LEFT JOIN fact_labor_report lr ON p.product_id = lr.product_id WHERE p.is_current = 't' GROUP BY p.product_category ORDER BY completed_qty DESC LIMIT 15 ; SELECT 'warning_metrics' AS category, 'unpaid_order_amount' AS metric_name, ROUND(SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END), 2) AS current_value, 200000 AS threshold, CASE WHEN SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) > 200000 THEN 'EXCEEDED' ELSE 'NORMAL' END AS status FROM fact_sales_order UNION ALL SELECT 'warning_metrics', 'defect_rate_pct', ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0), 2), 12, CASE WHEN SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0) > 12 THEN 'EXCEEDED' ELSE 'NORMAL' END FROM fact_quality_inspection UNION ALL SELECT 'warning_metrics', 'production_completion_rate', ROUND(SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0), 2), 40, CASE WHEN SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0) < 40 THEN 'LOW' ELSE 'NORMAL' END FROM fact_work_order UNION ALL SELECT 'warning_metrics', 'return_rate_pct', ROUND((SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0), 2), 12, CASE WHEN (SELECT SUM(amount) FROM fact_sales_return) * 100.0 / NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0) > 12 THEN 'EXCEEDED' ELSE 'NORMAL' END LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', order_date_utc::timestamp), 'YYYY-MM') AS month, COUNT(*) AS order_count, ROUND(SUM(deal_amount), 2) AS sales_amount, ROUND(AVG(deal_amount), 2) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_count FROM fact_sales_order GROUP BY DATE_TRUNC('month', order_date_utc::timestamp) ORDER BY month DESC LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000006", - "businessName": "MetricTrendAndTurningPointWarning", - "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", - "datasourceId": "19", - "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 15 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 15 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs LIMIT 15 ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 15", - "parameters": {} - } -] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log deleted file mode 100644 index 75e57ee..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log +++ /dev/null @@ -1,922 +0,0 @@ -2026-01-08 00:15:16 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_analyzeOrder\lzwcai_mcpskills_analyzeOrder\logs -2026-01-08 00:15:16 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:15:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:15:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.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 \"订单编号\", c.customer_name AS \"客户名称\", so.order_date_utc::DATE AS \"订单日期\", so.deal_amount AS \"订单金额\", so.payment_status AS \"付款状态\", ROUND(gm.avg_production_days::NUMERIC, 1) AS \"历史平均生产周期(天)\", ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS \"历史平均物流延迟(天)\", COALESCE(lds.delay_count, 0)::INT AS \"历史延误次数\", ROUND(gm.defect_rate_pct::NUMERIC, 2) AS \"产品不良率%\", ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS \"报废率%\", gm.active_wo_count::INT AS \"进行中工单数\", gm.lagging_wo_count::INT AS \"滞后工单数\", 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 \"延迟概率%\", 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 '红色' 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 '黄色' ELSE '绿色' END AS \"预警等级\", CASE WHEN gm.lagging_wo_count >= 2 THEN '生产严重滞后' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN '物流延误风险高' WHEN gm.avg_production_days > 15 THEN '生产周期过长' WHEN gm.defect_rate_pct > 10 THEN '质量问题突出' ELSE '正常' END AS \"主要风险因素\" 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 \"延迟概率%\" DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:19 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:15:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→待生产, STARTED→生产中, CLOSED→已完成),呈现执行进度与异常节点", - "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 '待生产' WHEN 'STARTED' THEN '生产中' WHEN 'CLOSED' THEN '已完成' ELSE '未知' 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 '进度严重滞后' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' 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 '质量异常' 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 '报工停滞' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number AS \"工单号\", product_name AS \"产品名称\", product_category AS \"产品类别\", status_name AS \"状态\", planned_qty AS \"计划数量\", completed_qty AS \"完成数量\", remaining_qty AS \"剩余数量\", completion_rate AS \"完成率(%)\", worker_count AS \"参与人数\", ROUND(total_work_minutes / 60.0, 1) AS \"累计工时(小时)\", elapsed_hours AS \"已用时间(小时)\", 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 \"异常标记\", CASE WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' WHEN efficiency_anomaly IS NOT NULL THEN '低' ELSE '-' END AS \"风险等级\" 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:28 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:15:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:37 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:37 - lzwcai_mcpskills_analyzeOrder.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 '经营决策简报' AS \"报告标题\", CURRENT_DATE AS \"报告日期\", ss.total_orders AS \"销售订单总数\", ROUND(ss.total_sales_amount, 2) AS \"销售总金额(元)\", ROUND(ss.avg_order_amount, 2) AS \"平均订单金额(元)\", ss.paid_orders AS \"已付款订单\", ss.partial_orders AS \"部分付款订单\", ss.unpaid_orders AS \"未付款订单\", ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS \"付款完成率(%)\", ROUND(ss.unpaid_amount, 2) AS \"待收款金额(元)\", ps.total_work_orders AS \"工单总数\", ps.closed_orders AS \"已完成工单\", ps.started_orders AS \"进行中工单\", ps.open_orders AS \"待开始工单\", ROUND(ps.total_planned_qty, 0) AS \"计划产量\", ROUND(ps.total_completed_qty, 0) AS \"完成产量\", ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS \"生产完成率(%)\", ls.worker_count AS \"活跃员工数\", ROUND(ls.total_work_minutes / 60.0, 1) AS \"总工时(小时)\", ROUND(ls.total_output_qty, 0) AS \"总产出量\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS \"人均产量\", ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS \"时均产量(件/小时)\", qs.inspection_count AS \"质检批次\", ROUND(qs.total_pass_qty, 0) AS \"合格数量\", ROUND(qs.total_fail_qty, 0) AS \"不合格数量\", ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS \"合格率(%)\", ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS \"不良率(%)\", scr.scrap_count AS \"报废记录数\", ar.receipt_count AS \"收款笔数\", ROUND(ar.total_receipt_amount, 2) AS \"收款总额(元)\", ap.payment_count AS \"付款笔数\", ROUND(ap.total_payment_amount, 2) AS \"付款总额(元)\", ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS \"净现金流(元)\", inv.invoice_count AS \"开票数量\", ROUND(inv.total_invoice_amount, 2) AS \"开票总额(元)\", sh.shipment_count AS \"发货单数\", ROUND(sh.total_shipment_amount, 2) AS \"发货总金额(元)\", pur.purchase_order_count AS \"采购订单数\", ret.return_count AS \"退货单数\", ROUND(ret.total_return_amount, 2) AS \"退货总金额(元)\", ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS \"退货率(%)\", 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 '优秀 - 各项指标健康' 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 '良好 - 部分指标需关注' ELSE '预警 - 需要立即改善' END AS \"经营健康度\" 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:38 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 00:15:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:58 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:58 - lzwcai_mcpskills_analyzeOrder.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 AS \"部门(产品类别)\", ls.worker_count AS \"人员数\", ROUND(ls.total_work_minutes / 60.0, 2) AS \"总工时(小时)\", ls.total_output_qty AS \"总产量\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS \"人均产量\", ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS \"时均产量(件/小时)\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS \"人效指数(产量/人/小时)\", ws.total_planned_qty AS \"计划产量\", ws.total_completed_qty AS \"完成产量\", ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS \"计划完成率(%)\", ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS \"估算产值(元)\", ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS \"人均产值(元)\", qs.total_pass_qty AS \"质检合格数\", qs.total_fail_qty AS \"质检不合格数\", ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS \"不良率(%)\", ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS \"生产损耗率(%)\", 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 \"综合绩效得分\", 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 '红色预警' 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 '黄色预警' ELSE '正常' END AS \"状态预警\" 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 \"综合绩效得分\" DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:59 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:59 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:16:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.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 '高风险' WHEN delivery_risk_score + quality_risk_score >= 50 THEN '中风险' ELSE '低风险' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN '交期异常+质量问题' WHEN delivery_risk_score >= 50 THEN '交期异常' WHEN quality_risk_score >= 30 THEN '质量问题' ELSE '正常' END AS risk_pattern FROM supplier_risk) SELECT supplier_name AS \"供应商名称\", supplier_category AS \"供应商类别\", order_count AS \"订单数\", receipt_count AS \"收货数\", avg_delivery_days AS \"平均交期(天)\", max_delivery_days AS \"最长交期(天)\", delivery_volatility AS \"交期波动\", total_receipts AS \"收货批次\", return_count AS \"退货次数\", quality_rate AS \"合格率(%)\", delivery_risk_score AS \"交期风险分\", quality_risk_score AS \"质量风险分\", total_risk_score AS \"综合风险分\", risk_level AS \"风险等级\", risk_pattern AS \"风险模式\" FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:16:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:16:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→待生产, STARTED→生产中, CLOSED→已完成),呈现执行进度与异常节点", - "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 '待生产' WHEN 'STARTED' THEN '生产中' WHEN 'CLOSED' THEN '已完成' ELSE '未知' 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 '进度严重滞后' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' 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 '质量异常' 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 '报工停滞' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number AS \"工单号\", product_name AS \"产品名称\", product_category AS \"产品类别\", status_name AS \"状态\", planned_qty AS \"计划数量\", completed_qty AS \"完成数量\", remaining_qty AS \"剩余数量\", completion_rate AS \"完成率(%)\", worker_count AS \"参与人数\", ROUND(total_work_minutes / 60.0, 1) AS \"累计工时(小时)\", elapsed_hours AS \"已用时间(小时)\", 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 \"异常标记\", CASE WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' WHEN efficiency_anomaly IS NOT NULL THEN '低' ELSE '-' END AS \"风险等级\" 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:16:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:22 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_analyzeOrder\lzwcai_mcpskills_analyzeOrder\logs -2026-01-08 00:30:22 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:30:26 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:30:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:30:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:27 - lzwcai_mcpskills_analyzeOrder.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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:27 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:27 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:32 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:32 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:30:32 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:32 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:32 - lzwcai_mcpskills_analyzeOrder.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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:32 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:32 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:32 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 00:30:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:34 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:34 - lzwcai_mcpskills_analyzeOrder.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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:34 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 00:30:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:35 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:35 - lzwcai_mcpskills_analyzeOrder.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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:35 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:30:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:37 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:37 - lzwcai_mcpskills_analyzeOrder.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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:38 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:39 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:39 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 00:30:39 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:39 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:39 - lzwcai_mcpskills_analyzeOrder.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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:39 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:39 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:44 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:30:44 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 00:30:44 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:44 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:30:44 - lzwcai_mcpskills_analyzeOrder.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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:30:44 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:30:44 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:30:44 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:38:55 - 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-08 00:38:55 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:38:57 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:38:59 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:38:59 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:38:59 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:52:34 - 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-08 00:52:34 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:52:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:52:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:52:37 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:52:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:53:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:53:21 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:53:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:56:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:56:21 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:56:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭 -2026-01-08 10:00:42 - 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-08 10:00:42 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 10:00:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 10:02:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:02:28 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:02:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:02:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:02:30 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:02:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:02:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:02:35 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:02:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:22:23 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:22:23 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:22:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:22:24 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:57:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:57:20 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:57:20 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:05:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:05:47 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:05:48 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:05:48 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:13:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:13:21 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:13:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:13:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:34:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:34:55 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:34:56 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:34:56 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:59:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:59:37 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:59:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:50:49 - 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-09 22:50:49 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 22:50:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 22:50:57 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:50:57 - 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-09 22:50:57 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:50:57 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:50:57 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:01 - 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-09 22:51:01 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:51: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 22:51:01 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:06 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:06 - 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-09 22:51:06 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:51:06 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:51:06 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:14 - 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-09 22:51:14 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:51: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 22:51:14 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:22 - 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-09 22:51:22 - 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-09 22:51:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:51:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:52:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:52:54 - 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-09 22:52:54 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:52:54 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:52:54 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 23:05:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 23:05:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-09 23:05:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 23:05:38 - 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-09 23:05:38 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 23:05:39 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 23:05:39 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 23:05:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 23:48:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 23:48:54 - 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-09 23:48:54 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 23:48:54 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 23:48:54 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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调用成功 -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调用成功 -2026-01-29 13:30:19 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs -2026-01-29 13:30:19 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-29 13:30:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-29 13:30:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "OrderDelayWarningAnalysis", - "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:30:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:30:25 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:30:25 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:26 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs -2026-01-29 13:36:26 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-29 13:36:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-29 13:36:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "OrderDelayWarningAnalysis", - "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC LIMIT 15 ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END LIMIT 15 ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC LIMIT 15 ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "SupplyChainRiskWarning", - "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", - "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 15 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 15 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 15 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 15 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs LIMIT 15 ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "EfficiencyOutputLossDashboard", - "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", - "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 15 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 15 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 15 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:41 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "MetricTrendAndTurningPointWarning", - "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", - "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 15 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 15 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs LIMIT 15 ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:41 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log deleted file mode 100644 index be7799b..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log +++ /dev/null @@ -1,143 +0,0 @@ -2026-01-29 13:30:19 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs -2026-01-29 13:30:19 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-29 13:30:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-29 13:30:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "OrderDelayWarningAnalysis", - "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:30:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:30:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:30:25 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:30:25 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:30:25 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:26 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs -2026-01-29 13:36:26 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-29 13:36:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-29 13:36:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "OrderDelayWarningAnalysis", - "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", - "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED'), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 5 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.5 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 10 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 80 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 15) * 2) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 4) + LEAST(25, gm.defect_rate_pct * 2) + LEAST(20, gm.lagging_wo_count * 8)) >= 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 5 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 10 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 30 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 20 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id LEFT JOIN global_metrics gm ON 1=1 LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 20 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 2 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC LIMIT 15 ; WITH status_summary AS (SELECT CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, status AS raw_status, COUNT(*) AS order_count, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order GROUP BY status) SELECT status_name, order_count, ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS pct, ROUND(total_planned, 0) AS total_planned, ROUND(total_completed, 0) AS total_completed, CASE WHEN total_planned > 0 THEN ROUND(total_completed * 100.0 / total_planned, 1) ELSE 0 END AS completion_rate FROM status_summary ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END LIMIT 15 ; WITH work_order_anomaly AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.planned_qty, wo.completed_qty, CASE WHEN wo.planned_qty > 0 THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1) ELSE 0 END AS completion_rate, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours, (SELECT MAX(lr.event_time_utc::timestamp) FROM fact_labor_report lr WHERE lr.work_order_number = wo.work_order_number) AS last_report_time FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' WHERE wo.status = 'STARTED') SELECT work_order_number, product_name, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, completion_rate, elapsed_hours, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'DELAYED' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'NO_LABOR_RECORD' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'LABOR_STALLED' ELSE 'NORMAL' END AS anomaly_type, CASE WHEN elapsed_hours > 96 AND completion_rate < 20 THEN 'IMMEDIATE_FOLLOWUP' WHEN elapsed_hours > 72 AND completion_rate < 40 THEN 'MONITOR_PROGRESS' WHEN last_report_time IS NULL AND elapsed_hours > 72 THEN 'CONFIRM_START' WHEN last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 72 THEN 'CHECK_LABOR' ELSE '-' END AS action FROM work_order_anomaly WHERE elapsed_hours > 36 OR last_report_time IS NULL OR (last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 36) ORDER BY CASE WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1 WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2 ELSE 3 END, completion_rate ASC LIMIT 15 ; SELECT p.product_category, COUNT(*) AS order_count, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS in_progress, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS completed, ROUND(SUM(wo.planned_qty), 0) AS total_planned, ROUND(SUM(wo.completed_qty), 0) AS total_completed, CASE WHEN SUM(wo.planned_qty) > 0 THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) ELSE 0 END AS completion_rate FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category ORDER BY order_count DESC LIMIT 15 ; WITH timeline AS (SELECT wo.work_order_number, p.product_name, wo.status, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, CASE WHEN wo.status = 'CLOSED' THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1) ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) END AS duration_hours, wo.planned_qty, wo.completed_qty FROM fact_work_order wo LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't') SELECT work_order_number, product_name, CASE status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' END AS status_name, TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS start_time, TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS last_update, duration_hours, ROUND(planned_qty, 0) AS planned_qty, ROUND(completed_qty, 0) AS completed_qty, CASE WHEN planned_qty > 0 THEN ROUND(completed_qty * 100.0 / planned_qty, 1) ELSE 0 END AS completion_rate FROM timeline ORDER BY start_time DESC LIMIT 15 ; WITH current_stats AS (SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(planned_qty) AS total_planned, SUM(completed_qty) AS total_completed FROM fact_work_order), anomaly_stats AS (SELECT COUNT(*) AS anomaly_count FROM fact_work_order wo WHERE wo.status = 'STARTED' AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 72), today_stats AS (SELECT COUNT(*) AS today_completed FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc::date = CURRENT_DATE) SELECT 'total_orders' AS metric, cs.total_orders::text AS value, '-' AS status FROM current_stats cs UNION ALL SELECT 'pending', cs.open_orders::text, CASE WHEN cs.open_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM current_stats cs UNION ALL SELECT 'in_progress', cs.started_orders::text, 'RUNNING' FROM current_stats cs UNION ALL SELECT 'completed', cs.closed_orders::text, 'NORMAL' FROM current_stats cs UNION ALL SELECT 'completion_rate', ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%', CASE WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 60 THEN 'GOOD' WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 30 THEN 'NORMAL' ELSE 'LOW' END FROM current_stats cs UNION ALL SELECT 'anomaly_count', ans.anomaly_count::text, CASE WHEN ans.anomaly_count > 15 THEN 'ATTENTION' ELSE 'NORMAL' END FROM anomaly_stats ans UNION ALL SELECT 'today_completed', ts.today_completed::text, '-' FROM today_stats ts LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:33 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:33 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "SupplyChainRiskWarning", - "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", - "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 120 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 90 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 40 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 60 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 75 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 88 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 90 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 60 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name LIMIT 15 ; WITH supplier_risk_info AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE((SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp)) FROM fact_purchase_order po2 LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id WHERE po2.supplier_id = s.supplier_id AND pr.doc_date_utc IS NOT NULL), 0) AS avg_delivery_days, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), order_risk AS (SELECT po.purchase_order_number, po.doc_date_utc::date AS order_date, sri.supplier_name, sri.supplier_category, ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days, sri.return_count, sri.receipt_count, CASE WHEN sri.receipt_count > 0 THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1) ELSE 100 END AS quality_rate, EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order, CASE WHEN sri.avg_delivery_days > 90 AND sri.return_count > 3 THEN 'HIGH' WHEN sri.avg_delivery_days > 75 OR sri.return_count > 2 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level FROM fact_purchase_order po JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id) SELECT purchase_order_number, order_date, supplier_name, supplier_category, supplier_avg_days, quality_rate, days_since_order, risk_level, CASE WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP' WHEN risk_level = 'MEDIUM' THEN 'MONITOR' ELSE 'NORMAL' END AS action_required FROM order_risk WHERE risk_level IN ('HIGH', 'MEDIUM') ORDER BY CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END, days_since_order DESC LIMIT 15 ; WITH supplier_stats AS (SELECT s.supplier_category, COUNT(DISTINCT s.supplier_id) AS supplier_count, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_category) SELECT supplier_category, supplier_count, order_count, receipt_count, return_count, CASE WHEN receipt_count > 0 THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1) ELSE 100 END AS quality_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 'HIGH' WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 12 THEN 'MEDIUM' ELSE 'LOW' END AS category_risk_level FROM supplier_stats ORDER BY return_count DESC LIMIT 15 ; WITH monthly_delivery AS (SELECT DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS receipt_count, AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days FROM fact_purchase_receipt pr JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)), monthly_returns AS (SELECT DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start, COUNT(*) AS return_count FROM fact_purchase_return pret GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)), monthly_combined AS (SELECT md.month_start, md.receipt_count, ROUND(md.avg_delivery_days, 1) AS avg_delivery_days, COALESCE(mr.return_count, 0) AS return_count FROM monthly_delivery md LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start), with_trend AS (SELECT *, LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days, LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count FROM monthly_combined) SELECT month_start, receipt_count, avg_delivery_days, return_count, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, CASE WHEN prev_avg_days IS NULL THEN 'NONE' WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING' WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING' ELSE 'STABLE' END AS delivery_trend, CASE WHEN prev_return_count IS NULL THEN 'NONE' WHEN return_count > prev_return_count THEN 'INCREASING' WHEN return_count < prev_return_count THEN 'DECREASING' ELSE 'STABLE' END AS return_trend FROM with_trend ORDER BY month_start DESC LIMIT 15 ; WITH risk_summary AS (SELECT s.supplier_id, s.supplier_name, COALESCE((SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0) AS return_count, COALESCE((SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0) AS receipt_count FROM dim_supplier s WHERE s.is_current = 't'), risk_counts AS (SELECT SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 20 THEN 1 ELSE 0 END) AS high_risk_suppliers, SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 12 AND 20 THEN 1 ELSE 0 END) AS medium_risk_suppliers, SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 12 THEN 1 ELSE 0 END) AS low_risk_suppliers, COUNT(*) AS total_suppliers FROM risk_summary), order_stats AS (SELECT COUNT(*) AS pending_orders, COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders FROM fact_purchase_order), return_stats AS (SELECT COUNT(*) AS total_returns, COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns FROM fact_purchase_return) SELECT 'high_risk_suppliers' AS metric_name, rc.high_risk_suppliers::text AS metric_value, CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status FROM risk_counts rc UNION ALL SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text, CASE WHEN rc.medium_risk_suppliers > 8 THEN 'MONITOR' ELSE 'NORMAL' END FROM risk_counts rc UNION ALL SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL' FROM risk_counts rc UNION ALL SELECT 'pending_orders', os.pending_orders::text, CASE WHEN os.pending_orders > 100 THEN 'BACKLOG' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'overdue_orders_30d', os.overdue_orders::text, CASE WHEN os.overdue_orders > 20 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END FROM order_stats os UNION ALL SELECT 'recent_returns_30d', rs.recent_returns::text, CASE WHEN rs.recent_returns > 15 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END FROM return_stats rs LIMIT 15 ; WITH supplier_metrics AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COUNT(DISTINCT po.purchase_order_id) AS order_count, COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count, COUNT(DISTINCT pret.purchase_return_id) AS return_count, SUM(pr.amount) AS total_amount FROM dim_supplier s LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id WHERE s.is_current = 't' GROUP BY s.supplier_id, s.supplier_name, s.supplier_category), ranked_suppliers AS (SELECT *, CASE WHEN receipt_count > 0 THEN ROUND(return_count * 100.0 / receipt_count, 1) ELSE 0 END AS return_rate, ROW_NUMBER() OVER (ORDER BY CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC, return_count DESC) AS risk_rank FROM supplier_metrics) SELECT risk_rank, supplier_name, supplier_category, order_count, receipt_count, return_count, return_rate, ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount, CASE WHEN return_rate > 20 THEN 'HIGH_RISK' WHEN return_rate > 12 THEN 'MEDIUM_RISK' WHEN return_count > 0 THEN 'LOW_RISK' ELSE 'EXCELLENT' END AS risk_assessment FROM ranked_suppliers ORDER BY risk_rank LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:34 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:34 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "EfficiencyOutputLossDashboard", - "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", - "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 20 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 30 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 12 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC LIMIT 15 ; SELECT lr.worker_name, p.product_category AS department, COUNT(DISTINCT lr.work_order_number) AS work_order_count, SUM(lr.duration_minutes) AS total_work_minutes, ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour, RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY lr.worker_name, p.product_category ORDER BY p.product_category, total_output DESC LIMIT 15 ; SELECT TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month, p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS active_worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output, ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker, ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category ORDER BY month DESC, p.product_category LIMIT 15 ; SELECT p.product_category AS department, p.product_name, p.product_code, COALESCE(SUM(qi.pass_qty), 0) AS pass_qty, COALESCE(SUM(qi.fail_qty), 0) AS fail_qty, ROUND(COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100, 2) AS defect_rate, CASE WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 20 THEN 'HIGH' WHEN COALESCE(SUM(qi.fail_qty), 0) / NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 12 THEN 'MEDIUM' ELSE 'LOW' END AS loss_level FROM dim_product p LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id WHERE p.is_current = 't' GROUP BY p.product_category, p.product_name, p.product_code ORDER BY defect_rate DESC NULLS LAST LIMIT 15 ; SELECT p.product_category AS department, wo.status, COUNT(*) AS order_count, SUM(wo.planned_qty) AS total_planned, SUM(wo.completed_qty) AS total_completed, ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate, ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category, wo.status ORDER BY p.product_category, wo.status LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:38 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:41 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema -2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "57", - "businessName": "MetricTrendAndTurningPointWarning", - "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", - "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 5 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -5 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.5 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.5 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.4 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.8 AND defect_rate > 8 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC LIMIT 15 ; WITH weekly_metrics AS (SELECT DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)), weekly_quality AS (SELECT DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)), weekly_combined AS (SELECT wm.week_start, wm.worker_count, wm.total_hours, wm.total_output, wm.hourly_efficiency, COALESCE(wq.defect_rate, 0) AS defect_rate FROM weekly_metrics wm LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start), weekly_with_lag AS (SELECT *, LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency, LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output, LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate FROM weekly_combined) SELECT week_start, worker_count, ROUND(total_hours, 1) AS total_hours, ROUND(total_output, 0) AS total_output, ROUND(hourly_efficiency, 2) AS hourly_output, ROUND(defect_rate, 2) AS defect_rate, CASE WHEN prev_efficiency IS NULL THEN 'NONE' WHEN hourly_efficiency > prev_efficiency * 1.15 THEN 'RISING' WHEN hourly_efficiency < prev_efficiency * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN prev_output IS NULL THEN 'NONE' WHEN total_output > prev_output * 1.15 THEN 'RISING' WHEN total_output < prev_output * 0.85 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN prev_defect_rate IS NULL THEN 'NONE' WHEN defect_rate > prev_defect_rate * 1.3 THEN 'RISING' WHEN defect_rate < prev_defect_rate * 0.7 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct FROM weekly_with_lag ORDER BY week_start DESC LIMIT 15 ; WITH recent_data AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency, SUM(lr.report_qty) AS total_output FROM fact_labor_report lr WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(lr.event_time_utc::timestamp)), recent_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate FROM fact_quality_inspection qi WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days' GROUP BY DATE(qi.event_time_utc::timestamp)), period_stats AS (SELECT AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d, AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d, AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d FROM recent_data rd), quality_stats AS (SELECT AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d, AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d FROM recent_quality rq) SELECT 'efficiency_per_hour' AS metric_name, ROUND(ps.avg_eff_7d, 2) AS last_7d_avg, ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg, ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct, CASE WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END AS trend, CASE WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END AS warning FROM period_stats ps UNION ALL SELECT 'daily_output', ROUND(ps.avg_output_7d, 0), ROUND(ps.avg_output_prev7d, 0), ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1), CASE WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.08 THEN 'RISING' WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.92 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.25 THEN 'ANOMALY' ELSE 'NORMAL' END FROM period_stats ps UNION ALL SELECT 'defect_rate_pct', ROUND(qs.avg_defect_7d, 2), ROUND(qs.avg_defect_prev7d, 2), ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1), CASE WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.15 THEN 'RISING' WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.85 THEN 'FALLING' ELSE 'STABLE' END, CASE WHEN qs.avg_defect_7d > 12 THEN 'THRESHOLD_EXCEEDED' WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.4 THEN 'ANOMALY_RISING' ELSE 'NORMAL' END FROM quality_stats qs LIMIT 15 ; WITH daily_eff AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), with_ma_step1 AS (SELECT metric_date, hourly_efficiency, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_eff), with_ma AS (SELECT metric_date, hourly_efficiency, ma7, LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7, LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7 FROM with_ma_step1), turning_points AS (SELECT metric_date, hourly_efficiency, ma7, prev_ma7, prev2_ma7, CASE WHEN prev2_ma7 IS NOT NULL AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD' WHEN prev2_ma7 IS NOT NULL AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD' ELSE NULL END AS turning_type FROM with_ma) SELECT metric_date, ROUND(hourly_efficiency, 2) AS daily_efficiency, ROUND(ma7, 2) AS ma7_line, turning_type, CASE WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES' WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE' ELSE 'NONE' END AS recommendation FROM turning_points WHERE turning_type IS NOT NULL ORDER BY metric_date DESC LIMIT 15", - "parameters": {}, - "testParams": {} -} -2026-01-29 13:36:41 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-29 13:36:41 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 deleted file mode 100644 index b3fb093..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 +++ /dev/null @@ -1,362 +0,0 @@ -2026-01-08 00:15:16 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_analyzeOrder\lzwcai_mcpskills_analyzeOrder\logs -2026-01-08 00:15:16 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:15:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:15:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.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 \"订单编号\", c.customer_name AS \"客户名称\", so.order_date_utc::DATE AS \"订单日期\", so.deal_amount AS \"订单金额\", so.payment_status AS \"付款状态\", ROUND(gm.avg_production_days::NUMERIC, 1) AS \"历史平均生产周期(天)\", ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS \"历史平均物流延迟(天)\", COALESCE(lds.delay_count, 0)::INT AS \"历史延误次数\", ROUND(gm.defect_rate_pct::NUMERIC, 2) AS \"产品不良率%\", ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS \"报废率%\", gm.active_wo_count::INT AS \"进行中工单数\", gm.lagging_wo_count::INT AS \"滞后工单数\", 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 \"延迟概率%\", 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 '红色' 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 '黄色' ELSE '绿色' END AS \"预警等级\", CASE WHEN gm.lagging_wo_count >= 2 THEN '生产严重滞后' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN '物流延误风险高' WHEN gm.avg_production_days > 15 THEN '生产周期过长' WHEN gm.defect_rate_pct > 10 THEN '质量问题突出' ELSE '正常' END AS \"主要风险因素\" 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 \"延迟概率%\" DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:19 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:15:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→待生产, STARTED→生产中, CLOSED→已完成),呈现执行进度与异常节点", - "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 '待生产' WHEN 'STARTED' THEN '生产中' WHEN 'CLOSED' THEN '已完成' ELSE '未知' 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 '进度严重滞后' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' 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 '质量异常' 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 '报工停滞' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number AS \"工单号\", product_name AS \"产品名称\", product_category AS \"产品类别\", status_name AS \"状态\", planned_qty AS \"计划数量\", completed_qty AS \"完成数量\", remaining_qty AS \"剩余数量\", completion_rate AS \"完成率(%)\", worker_count AS \"参与人数\", ROUND(total_work_minutes / 60.0, 1) AS \"累计工时(小时)\", elapsed_hours AS \"已用时间(小时)\", 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 \"异常标记\", CASE WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' WHEN efficiency_anomaly IS NOT NULL THEN '低' ELSE '-' END AS \"风险等级\" 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:28 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:15:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:37 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:37 - lzwcai_mcpskills_analyzeOrder.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 '经营决策简报' AS \"报告标题\", CURRENT_DATE AS \"报告日期\", ss.total_orders AS \"销售订单总数\", ROUND(ss.total_sales_amount, 2) AS \"销售总金额(元)\", ROUND(ss.avg_order_amount, 2) AS \"平均订单金额(元)\", ss.paid_orders AS \"已付款订单\", ss.partial_orders AS \"部分付款订单\", ss.unpaid_orders AS \"未付款订单\", ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS \"付款完成率(%)\", ROUND(ss.unpaid_amount, 2) AS \"待收款金额(元)\", ps.total_work_orders AS \"工单总数\", ps.closed_orders AS \"已完成工单\", ps.started_orders AS \"进行中工单\", ps.open_orders AS \"待开始工单\", ROUND(ps.total_planned_qty, 0) AS \"计划产量\", ROUND(ps.total_completed_qty, 0) AS \"完成产量\", ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS \"生产完成率(%)\", ls.worker_count AS \"活跃员工数\", ROUND(ls.total_work_minutes / 60.0, 1) AS \"总工时(小时)\", ROUND(ls.total_output_qty, 0) AS \"总产出量\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS \"人均产量\", ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS \"时均产量(件/小时)\", qs.inspection_count AS \"质检批次\", ROUND(qs.total_pass_qty, 0) AS \"合格数量\", ROUND(qs.total_fail_qty, 0) AS \"不合格数量\", ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS \"合格率(%)\", ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS \"不良率(%)\", scr.scrap_count AS \"报废记录数\", ar.receipt_count AS \"收款笔数\", ROUND(ar.total_receipt_amount, 2) AS \"收款总额(元)\", ap.payment_count AS \"付款笔数\", ROUND(ap.total_payment_amount, 2) AS \"付款总额(元)\", ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS \"净现金流(元)\", inv.invoice_count AS \"开票数量\", ROUND(inv.total_invoice_amount, 2) AS \"开票总额(元)\", sh.shipment_count AS \"发货单数\", ROUND(sh.total_shipment_amount, 2) AS \"发货总金额(元)\", pur.purchase_order_count AS \"采购订单数\", ret.return_count AS \"退货单数\", ROUND(ret.total_return_amount, 2) AS \"退货总金额(元)\", ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS \"退货率(%)\", 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 '优秀 - 各项指标健康' 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 '良好 - 部分指标需关注' ELSE '预警 - 需要立即改善' END AS \"经营健康度\" 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:38 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:15:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 00:15:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:58 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:15:58 - lzwcai_mcpskills_analyzeOrder.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 AS \"部门(产品类别)\", ls.worker_count AS \"人员数\", ROUND(ls.total_work_minutes / 60.0, 2) AS \"总工时(小时)\", ls.total_output_qty AS \"总产量\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS \"人均产量\", ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS \"时均产量(件/小时)\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS \"人效指数(产量/人/小时)\", ws.total_planned_qty AS \"计划产量\", ws.total_completed_qty AS \"完成产量\", ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS \"计划完成率(%)\", ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS \"估算产值(元)\", ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS \"人均产值(元)\", qs.total_pass_qty AS \"质检合格数\", qs.total_fail_qty AS \"质检不合格数\", ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS \"不良率(%)\", ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS \"生产损耗率(%)\", 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 \"综合绩效得分\", 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 '红色预警' 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 '黄色预警' ELSE '正常' END AS \"状态预警\" 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 \"综合绩效得分\" DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:15:59 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:15:59 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:15:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:16:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.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 '高风险' WHEN delivery_risk_score + quality_risk_score >= 50 THEN '中风险' ELSE '低风险' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN '交期异常+质量问题' WHEN delivery_risk_score >= 50 THEN '交期异常' WHEN quality_risk_score >= 30 THEN '质量问题' ELSE '正常' END AS risk_pattern FROM supplier_risk) SELECT supplier_name AS \"供应商名称\", supplier_category AS \"供应商类别\", order_count AS \"订单数\", receipt_count AS \"收货数\", avg_delivery_days AS \"平均交期(天)\", max_delivery_days AS \"最长交期(天)\", delivery_volatility AS \"交期波动\", total_receipts AS \"收货批次\", return_count AS \"退货次数\", quality_rate AS \"合格率(%)\", delivery_risk_score AS \"交期风险分\", quality_risk_score AS \"质量风险分\", total_risk_score AS \"综合风险分\", risk_level AS \"风险等级\", risk_pattern AS \"风险模式\" FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:16:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:16:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { - "datasourceId": "19", - "businessName": "WorkOrderProgressAndAnomalyNodes", - "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→待生产, STARTED→生产中, CLOSED→已完成),呈现执行进度与异常节点", - "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 '待生产' WHEN 'STARTED' THEN '生产中' WHEN 'CLOSED' THEN '已完成' ELSE '未知' 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 '进度严重滞后' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' 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 '质量异常' 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 '报工停滞' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number AS \"工单号\", product_name AS \"产品名称\", product_category AS \"产品类别\", status_name AS \"状态\", planned_qty AS \"计划数量\", completed_qty AS \"完成数量\", remaining_qty AS \"剩余数量\", completion_rate AS \"完成率(%)\", worker_count AS \"参与人数\", ROUND(total_work_minutes / 60.0, 1) AS \"累计工时(小时)\", elapsed_hours AS \"已用时间(小时)\", 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 \"异常标记\", CASE WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' WHEN efficiency_anomaly IS NOT NULL THEN '低' ELSE '-' END AS \"风险等级\" 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:16:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:38:55 - 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-08 00:38:55 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:38:57 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:38:59 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:38:59 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:38:59 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:52:34 - 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-08 00:52:34 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:52:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:52:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:52:37 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:52:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:53:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:53:21 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:53:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:56:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 00:56:21 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 00:56:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭 -2026-01-08 10:00:42 - 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-08 10:00:42 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 10:00:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 10:02:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:02:28 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:02:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:02:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:02:30 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:02:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:02:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:02:35 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:02:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:22:23 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:22:23 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:22:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:22:24 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:57:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 10:57:20 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 10:57:20 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:05:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:05:47 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:05:48 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:05:48 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:13:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:13:21 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:13:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:13:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:34:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:34:55 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:34:56 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:34:56 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:59:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-08 11:59:37 - 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", - "parameters": {}, - "testParams": {} -} -2026-01-08 11:59:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-08 11:59:37 - 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-09 b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 deleted file mode 100644 index 0976602..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 +++ /dev/null @@ -1,139 +0,0 @@ -2026-01-09 22:50:49 - 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-09 22:50:49 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 22:50:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 22:50:57 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:50:57 - 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-09 22:50:57 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:50:57 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:50:57 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:01 - 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-09 22:51:01 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:51: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 22:51:01 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:06 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:06 - 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-09 22:51:06 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:51:06 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:51:06 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:14 - 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-09 22:51:14 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:51: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 22:51:14 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:22 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:22 - 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-09 22:51:22 - 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-09 22:51:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:51:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:52:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:52:54 - 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-09 22:52:54 - 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 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", - "parameters": {}, - "testParams": {} -} -2026-01-09 22:52:54 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 22:52:54 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 23:05:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 23:05:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-09 23:05:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 23:05:38 - 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-09 23:05:38 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 23:05:39 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 23:05:39 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 23:05:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 23:48:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 23:48:54 - 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-09 23:48:54 - 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 ; 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", - "parameters": {}, - "testParams": {} -} -2026-01-09 23:48:54 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema "HTTP/1.1 200 " -2026-01-09 23:48:54 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 23:48:54 - 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 deleted file mode 100644 index 4466214..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-10 +++ /dev/null @@ -1,45 +0,0 @@ -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/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 deleted file mode 100644 index ae6a095..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-26 +++ /dev/null @@ -1,109 +0,0 @@ -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_error.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_error.log deleted file mode 100644 index e69de29..0000000 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 deleted file mode 100644 index 0e5d22a..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log +++ /dev/null @@ -1,295 +0,0 @@ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:15:16 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:15:17 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:15:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:15:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:15:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 00:15:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:15:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:16:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:16:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:30:22 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:30:26 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:30:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:30:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:27 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:32 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:30:32 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:32 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 00:30:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 00:30:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:30:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:39 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 00:30:39 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:30:44 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 00:30:44 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:30:44 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:38:55 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:38:57 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:38:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 00:52:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 00:52:36 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:52:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:53:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 22:50:49 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 22:50:56 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:50:57 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:51:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 22:52:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 23:05:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-09 23:05:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 23:05:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 23:48:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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调用成功 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088 -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning -2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/main.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/main.py deleted file mode 100644 index 71cb13f..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/main.py +++ /dev/null @@ -1,373 +0,0 @@ -from pathlib import Path -from typing import Any -import asyncio -import logging - -# 支持直接运行和模块导入两种方式 -try: - from .utils import load_json, generate_tool_name, generate_input_schema - from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema - from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config - from .utils.logger_config import logger_config -except ImportError: - from utils import load_json, generate_tool_name, generate_input_schema - from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema - from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config - from utils.logger_config import logger_config - -from mcp.server.models import InitializationOptions -from mcp.server import NotificationOptions, Server -import mcp.types as types - -# 初始化 MCP 专用日志器 -mcp_logger = logger_config.setup_mcp_logging() - -# ========== 数据源配置 ========== -# 数据源类型常量 -DATA_SOURCE_API = "api" # 仅使用API数据 -DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据 -DATA_SOURCE_BOTH = "both" # 合并本地和API数据 - -# 默认数据源(可修改) -DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL -# ================================ - - -def get_queries(): - """ - 获取业务查询配置 - - Returns: - list: 包含所有业务查询配置的列表 - """ - try: - # 获取当前文件所在目录 - current_dir = Path(__file__).parent - - # 构建 businessQueries.json 的路径 - json_path = current_dir / "businessQueries.json" - - mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}") - - # 使用 load_json 方法读取 JSON 文件 - queries = load_json(json_path) - - mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置") - - return queries - except Exception as e: - mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True) - raise - - -def generate_tool_schema_from_query(query: dict) -> types.Tool: - """ - 根据查询配置生成 MCP 工具模式 - - Args: - query: 单个查询配置字典 - - Returns: - types.Tool: MCP 工具对象 - """ - try: - # 获取参数定义并生成 inputSchema - parameters = query.get('parameters', {}) - input_schema = generate_input_schema(parameters) - - # 生成工具名称(格式: tool_拼音_id) - # tool_name = generate_tool_name(query['businessName'], query['id']) - tool_name = query['businessName'] - # 构建工具描述,包含业务名称和业务描述 - description = f"{query['businessName']}: {query['businessDescription']}" - - mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}") - - return types.Tool( - name=tool_name, - description=description, - inputSchema=input_schema - ) - except Exception as e: - mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True) - raise - - -# 创建 MCP 服务器实例 -server = Server("lzwcai-mcpskills-mfg-data-agent") - -# 缓存查询配置,避免重复加载 -_queries_cache = None - - -async def get_queries_cache(source: str = None): - """ - 获取或初始化查询配置缓存 - - Args: - source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE) - - "api": 仅使用API数据 - - "local": 仅使用本地JSON数据 - - "both": 合并本地和API数据 - - Returns: - 查询配置列表 - """ - global _queries_cache - if _queries_cache is None: - source = source or DEFAULT_DATA_SOURCE - mcp_logger.info(f"初始化查询配置(数据源: {source})...") - - if source == DATA_SOURCE_LOCAL: - _queries_cache = get_queries() - mcp_logger.info(f"本地配置: {len(_queries_cache)} 条") - - elif source == DATA_SOURCE_API: - try: - _queries_cache = await call_third_party_api() - mcp_logger.info(f"API配置: {len(_queries_cache)} 条") - mcp_logger.info(f"API配置数组: {_queries_cache}") - except Exception as e: - mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}") - _queries_cache = get_queries() - - else: # DATA_SOURCE_BOTH - local = get_queries() - try: - api = await call_third_party_api() - except Exception as e: - mcp_logger.warning(f"API获取失败: {e}") - api = [] - _queries_cache = local + api - mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})") - - return _queries_cache - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """ - 列出所有动态生成的 MCP 工具 - - Returns: - list[types.Tool]: 所有可用的工具列表 - """ - try: - mcp_logger.info("收到列出工具请求") - - queries = await get_queries_cache() - tools = [] - - for query in queries: - tool = generate_tool_schema_from_query(query) - tools.append(tool) - - mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具") - mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}") - - return tools - except Exception as e: - mcp_logger.error(f"列出工具失败: {e}", exc_info=True) - raise - - -@server.call_tool() -async def handle_call_tool( - name: str, - arguments: dict[str, Any] | None -) -> list[types.TextContent]: - """ - 处理工具调用请求 - - Args: - name: 工具名称 - arguments: 工具参数 - - Returns: - list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置) - """ - try: - mcp_logger.info(f"收到工具调用请求: {name}") - mcp_logger.debug(f"工具参数: {arguments}") - - # 获取查询配置缓存 - queries = await get_queries_cache() - - # 根据工具名称查找对应的 item(接口配置) - tool_item = None - for query in queries: - # tool_name = generate_tool_name(query['businessName'], query['id']) - tool_name = query['businessName'] - if tool_name == name: - tool_item = query - break - - # 构建返回结果 - import json - - if tool_item: - request_data = { - "datasourceId": get_datasource_id(), - "businessName": tool_item.get("businessName"), - "businessDescription": tool_item.get("businessDescription"), - "sqlTemplate": tool_item.get("sqlTemplate"), - "parameters": tool_item.get("parameters"), - "testParams": arguments or {} - } - - # 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data - if arguments and arguments.get("targetDatabaseName"): - request_data["targetDatabaseName"] = arguments["targetDatabaseName"] - mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}") - - # 调用测试SQL API - try: - mcp_logger.info("正在调用测试SQL API...") - api_response = test_sql_with_schema(request_data) - mcp_logger.info("测试SQL API调用成功") - - # 返回包含 data 字段的结果 - result = { - "success": True, - "data": api_response - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - - except Exception as e: - error_msg = f"调用测试SQL API失败: {str(e)}" - mcp_logger.error(error_msg, exc_info=True) - result = { - "success": False, - "error": error_msg, - "data": None - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - else: - error_msg = f"未找到工具 {name} 对应的配置" - result = { - "success": False, - "error": error_msg, - "data": None - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - - mcp_logger.debug(f"工具调用结果: {result_text}") - - return [ - types.TextContent( - type="text", - text=result_text - ) - ] - except Exception as e: - error_msg = f"工具调用失败: {name}, 错误: {e}" - mcp_logger.error(error_msg, exc_info=True) - return [ - types.TextContent( - type="text", - text=f"错误: {error_msg}" - ) - ] - - -async def call_third_party_api(skill_id: str = None) -> list: - """ - 调用第三方API获取技能信息并返回处理后的数据 - - Args: - skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178) - - Returns: - 处理后的查询配置列表(businessQueries格式) - - Example: - queries = await call_third_party_api() - # 返回: [{"id": "...", "businessName": "...", ...}, ...] - """ - try: - # 如果没有传入 skill_id,则从环境变量读取 - if skill_id is None: - skill_id = get_skill_id() - - mcp_logger.info(f"调用第三方API,skill_id: {skill_id}") - - # 获取原始数据 - raw_result = get_skill_by_id(skill_id) - - mcp_logger.info(f"成功{raw_result}") - - # 处理并返回 - processed_queries = process_skill_response(raw_result) - - mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据") - return processed_queries - - except Exception as e: - mcp_logger.error(f"API调用失败: {e}", exc_info=True) - raise - - -async def async_main(): - """MCP 服务器异步主函数""" - try: - mcp_logger.info("=" * 60) - mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder") - mcp_logger.info("版本: 0.1.0") - mcp_logger.info("=" * 60) - - # 输出环境配置信息 - env_config = get_env_config() - mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}") - mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}") - mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}") - mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}") - mcp_logger.info("=" * 60) - - from mcp.server.stdio import stdio_server - - async with stdio_server() as (read_stream, write_stream): - mcp_logger.info("MCP 服务器已启动,等待客户端连接...") - - await server.run( - read_stream, - write_stream, - InitializationOptions( - server_name="lzwcai-mcpskills-analyzeOrder", - server_version="0.1.0", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - mcp_logger.info("MCP 服务器已关闭") - - except Exception as e: - mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True) - raise - - -def main(): - """入口点函数(用于 console_scripts)""" - try: - # 初始化系统日志 - # MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信 - logger_config.setup_logging( - app_name="lzwcai_mcp_sqlexecutor", - log_level=logging.INFO, - console_output=False # 禁用控制台输出 - ) - - mcp_logger.info("开始运行 MCP SQL Executor 服务器") - asyncio.run(async_main()) - - except KeyboardInterrupt: - mcp_logger.info("收到中断信号,正在关闭服务器...") - except Exception as e: - mcp_logger.error(f"程序运行失败: {e}", exc_info=True) - raise - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/pyproject.toml b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/pyproject.toml deleted file mode 100644 index d6139e9..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcpskills-mfg-data-agent" -version = "0.1.1" -description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.13" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcpskills-mfg-data-agent = "lzwcai_mcpskills_mfg_data_agent.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_mfg_data_agent"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcpskills_mfg_data_agent/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agent/businessQueries.json" diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/sql11 b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/sql11 deleted file mode 100644 index 39b7319..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/sql11 +++ /dev/null @@ -1,169 +0,0 @@ --- ===================================================== --- 交付风险预测:延迟概率与红/黄/绿预警等级 --- 基于历史订单的生产周期、物流延误、设备故障等特征 --- ===================================================== - -WITH --- 1. 全局生产特征(汇总所有工单) -global_production AS ( - SELECT - COUNT(*) AS total_wo_count, - AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate, - SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count, - SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count - FROM fact_work_order -), - --- 2. 全局质检特征 -global_quality AS ( - SELECT - COUNT(*) AS total_inspection_count, - SUM(COALESCE(pass_qty, 0)) AS total_pass_qty, - SUM(COALESCE(fail_qty, 0)) AS total_fail_qty, - CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0 - THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) - ELSE 1 END AS qc_pass_rate - FROM fact_quality_inspection -), - --- 3. 全局工序不良特征 -global_operation AS ( - SELECT - COUNT(*) AS total_task_count, - SUM(COALESCE(good_qty, 0)) AS total_good_qty, - SUM(COALESCE(bad_qty, 0)) AS total_bad_qty, - CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0 - THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) - ELSE 0 END AS operation_defect_rate - FROM fact_operation_task -), - --- 4. 客户级别发货统计 -customer_shipment AS ( - SELECT - customer_id, - COUNT(*) AS shipment_count, - SUM(COALESCE(amount, 0)) AS total_shipment_amount - FROM fact_sales_shipment - GROUP BY customer_id -), - --- 5. 客户级别退货统计 -customer_return AS ( - SELECT - customer_id, - COUNT(*) AS return_count, - SUM(COALESCE(amount, 0)) AS total_return_amount - FROM fact_sales_return - GROUP BY customer_id -), - --- 6. 订单风险评估 -order_risk AS ( - SELECT - so.sales_order_id, - so.sales_order_number, - c.customer_name, - so.order_date_utc, - so.deal_amount, - so.payment_status, - - -- 全局生产指标 - gp.avg_completion_rate AS production_completion_rate, - gp.pending_wo_count, - gp.total_wo_count AS work_order_count, - - -- 全局质检指标 - gq.qc_pass_rate, - gq.total_fail_qty, - - -- 全局工序指标 - go.operation_defect_rate, - - -- 客户级别指标 - COALESCE(cs.shipment_count, 0) AS shipment_count, - COALESCE(cr.return_count, 0) AS return_count, - CASE WHEN COALESCE(cs.shipment_count, 0) > 0 - THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count - ELSE 0 END AS return_rate - - FROM fact_sales_order so - LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true - CROSS JOIN global_production gp - CROSS JOIN global_quality gq - CROSS JOIN global_operation go - LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id - LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id -) - --- 7. 最终输出 -SELECT - sales_order_id, - sales_order_number, - customer_name, - order_date_utc, - deal_amount, - payment_status, - - -- 风险特征 - work_order_count, - ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate, - pending_wo_count, - ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate, - ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate, - return_count, - ROUND(return_rate::NUMERIC, 4) AS return_rate, - - -- 延迟概率 - ROUND(( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 - WHEN production_completion_rate < 0.5 THEN 0.20 - WHEN production_completion_rate < 0.8 THEN 0.10 - ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 - WHEN qc_pass_rate < 0.9 THEN 0.15 - WHEN qc_pass_rate < 0.95 THEN 0.08 - ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 - WHEN operation_defect_rate > 0.05 THEN 0.12 - WHEN operation_defect_rate > 0.02 THEN 0.05 - ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 - WHEN return_rate > 0.05 THEN 0.08 - WHEN return_rate > 0.02 THEN 0.03 - ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 - WHEN payment_status = 'PARTIAL' THEN 0.05 - ELSE 0 END - )::NUMERIC, 2) AS delay_probability, - - -- 红/黄/绿预警 - CASE - WHEN ( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END - ) >= 0.50 THEN 'RED' - WHEN ( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END - ) >= 0.25 THEN 'YELLOW' - ELSE 'GREEN' - END AS risk_level, - - -- 风险原因 - CONCAT_WS(' | ', - CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END, - CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END, - CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END, - CASE WHEN return_rate > 0.05 THEN '历史退货率高' END, - CASE WHEN payment_status = 'UNPAID' THEN '未付款' END - ) AS risk_reasons - -FROM order_risk -ORDER BY delay_probability DESC, deal_amount DESC; diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/__init__.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/__init__.py deleted file mode 100644 index 3df2b44..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Utils package for lzwcai_mcp_sqlexecutor""" - -from .json_helper import load_json -from .name_helper import generate_tool_name -from .schema_helper import generate_input_schema, validate_input_schema -from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema -from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable - -__all__ = [ - 'load_json', - 'generate_tool_name', - 'generate_input_schema', - 'validate_input_schema', - 'DataSourceAPIClient', - 'get_skill_by_id', - 'process_skill_response', - 'test_sql_with_schema', - 'get_database_id', - 'get_datasource_id', - 'get_skill_id', - 'get_backend_base_url', - 'get_env_config', - 'set_env_variable' -] - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/api_client.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/api_client.py deleted file mode 100644 index 279d800..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/api_client.py +++ /dev/null @@ -1,332 +0,0 @@ -""" -第三方API调用客户端 -用于调用外部数据源接口 -""" - -import httpx -import logging -import json -from typing import Dict, Any, Optional, List - -# 支持直接运行和模块导入两种方式 -try: - from .env_config import get_backend_base_url -except ImportError: - from env_config import get_backend_base_url - -# 获取日志记录器 -logger = logging.getLogger(__name__) - - -class DataSourceAPIClient: - """数据源API客户端""" - - def __init__( - self, - base_url: Optional[str] = None, - token: Optional[str] = None - ): - """ - 初始化API客户端 - - Args: - base_url: API基础URL(默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088) - token: 认证令牌(Bearer Token) - """ - # 如果没有传入 base_url,则从环境变量读取 - if base_url is None: - base_url = get_backend_base_url() - - self.base_url = base_url.rstrip('/') - self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw" - self.client = httpx.Client(timeout=30.0) - - def _get_headers(self) -> Dict[str, str]: - """ - 获取请求头 - - Returns: - 请求头字典 - """ - return { - 'Authorization': f'Bearer {self.token}', - } - - def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]: - """ - 根据技能ID获取技能信息 - - Args: - skill_id: 技能ID - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - try: - url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}" - - logger.info(f"正在调用API: {url}") - logger.info(f"请求参数 - skill_id: {skill_id}") - - response = self.client.get( - url, - headers=self._get_headers() - ) - - # 检查HTTP状态码 - response.raise_for_status() - - # 解析JSON响应 - data = response.json() - - logger.info(f"API调用成功: {url}") - logger.debug(f"响应数据: {data}") - - return data - - except httpx.TimeoutException: - error_msg = f"API请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - - except httpx.RequestError as e: - error_msg = f"API请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理API响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]: - """ - 测试SQL语句并返回执行结果 - - Args: - request_data: 请求数据,包含以下字段: - - datasourceId: 数据源ID - - businessName: 业务名称 - - businessDescription: 业务描述 - - sqlTemplate: SQL模板 - - parameters: 参数定义 - - testParams: 测试参数 - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - try: - url = f"{self.base_url}/datasource/sqlExecutionLog/testBatchSqlWithSchema" - - # 构建请求头(包含Content-Type) - headers = self._get_headers() - headers['Content-Type'] = 'application/json' - headers['Accept'] = '*/*' - - logger.info(f"正在调用测试SQL API: {url}") - logger.info(f"请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - - # 发送POST请求 - response = self.client.post( - url, - headers=headers, - json=request_data - ) - - # 检查HTTP状态码 - response.raise_for_status() - - # 解析JSON响应 - result = response.json() - - logger.info(f"测试SQL API调用成功") - logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") - - # 处理返回数据结构: {code, data: [{errorMessage, data}, ...], msg} - # 检查外层 code - if result.get("code") != 200: - error_msg = result.get("msg", "接口返回错误") - logger.error(f"接口返回错误: code={result.get('code')}, msg={error_msg}") - raise Exception(error_msg) - - # data 是一个数组,每个元素包含 errorMessage 和 data - data_list = result.get("data", []) - - # 如果不是数组,兼容旧格式 - if isinstance(data_list, dict): - if data_list.get("errorMessage"): - error_msg = data_list.get("errorMessage") - logger.error(f"接口业务错误: {error_msg}") - raise Exception(error_msg) - return data_list.get("data") - - # 处理数组格式,提取每个 item 的 data 并组合 - combined_data = [] - for item in data_list: - if item.get("errorMessage"): - error_msg = item.get("errorMessage") - logger.error(f"接口业务错误: {error_msg}") - raise Exception(error_msg) - item_data = item.get("data", []) - combined_data.append(item_data) - - # 返回组合后的数据 [data1, data2, ...] - return combined_data - - except httpx.TimeoutException: - error_msg = f"测试SQL API请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - - except httpx.RequestError as e: - error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理测试SQL API响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def close(self): - """关闭HTTP客户端""" - self.client.close() - - -# 创建默认客户端实例 -default_client = DataSourceAPIClient() - - -def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:根据技能ID获取技能信息 - - Args: - skill_id: 技能ID - base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) - token: 认证令牌(可选,使用默认值) - - Returns: - API响应数据 - """ - if base_url or token: - client = DataSourceAPIClient( - base_url=base_url, - token=token - ) - return client.get_skill_by_id(skill_id) - else: - return default_client.get_skill_by_id(skill_id) - - -def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:测试SQL语句并返回执行结果 - - Args: - request_data: 请求数据,包含以下字段: - - datasourceId: 数据源ID - - businessName: 业务名称 - - businessDescription: 业务描述 - - sqlTemplate: SQL模板 - - parameters: 参数定义 - - testParams: 测试参数 - base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) - token: 认证令牌(可选,使用默认值) - - Returns: - API响应数据 - """ - if base_url or token: - client = DataSourceAPIClient( - base_url=base_url, - token=token - ) - return client.test_sql_with_schema(request_data) - else: - return default_client.test_sql_with_schema(request_data) - - -def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: - """ - 处理API响应数据,映射为businessQueries格式 - - Args: - response: API原始响应数据 - - Returns: - 处理后的查询配置列表 - """ - try: - # 提取data数组 - data_list = response.get("data", []) - - # 默认的员工ID参数schema - default_employee_schema = { - "type": "object", - "required": ["employeeId"], - "properties": { - "employeeId": { - "type": "number", - "description": "员工ID,用于标识员工的唯一数字标识符", - "examples": [1001, 2002] - } - } - } - - # 映射每个skill为businessQuery格式 - queries = [] - for skill in data_list: - # 解析sqlParams字符串为JSON对象 - sql_params = json.loads(skill.get("sqlParams", "{}")) - - # 判断sqlParams是否为空对象 - is_empty_params = ( - not sql_params.get("properties") or - len(sql_params.get("properties", {})) == 0 - ) and ( - not sql_params.get("required") or - len(sql_params.get("required", [])) == 0 - ) - - # 如果是空对象,使用默认的员工ID参数 - if is_empty_params: - logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空,使用默认员工ID参数") - sql_params = default_employee_schema - - # 映射字段 - query = { - "id": skill.get("id"), - "businessName": skill.get("name"), - "businessDescription": skill.get("description"), - "sqlTemplate": skill.get("sqlTemplate"), - "parameters": sql_params, - "datasourceId": skill.get("datasourceId") - } - queries.append(query) - - logger.info(f"成功处理 {len(queries)} 条技能数据") - return queries - - except Exception as e: - logger.error(f"处理API响应数据失败: {e}", exc_info=True) - raise - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/env_config.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/env_config.py deleted file mode 100644 index 585b0a6..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/env_config.py +++ /dev/null @@ -1,106 +0,0 @@ -"""环境变量配置模块""" - -import os -from typing import Optional - - -def get_database_id(default: str = "29") -> str: - """ - 获取数据库ID环境变量 - - Args: - default: 默认值(默认为 "29") - - Returns: - str: 数据库ID - - Environment Variables: - databaseId: 数据库ID - """ - return os.environ.get("databaseId", default) - - -def get_datasource_id(default: str = "") -> str: - """ - 获取数据源ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 数据源ID - - Environment Variables: - datasourceId: 数据源ID - """ - return os.environ.get("datasourceId", default) - - -def get_skill_id(default: str = "") -> str: - """ - 获取技能ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 技能ID - - Environment Variables: - skillId: 技能ID - """ - return os.environ.get("skillId", default) - - -def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: - """ - 获取后端API基础URL环境变量 - - Args: - default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086") - - Returns: - str: 后端API基础URL - - Environment Variables: - backendBaseUrl: 后端API基础URL - """ - return os.environ.get("backendBaseUrl", default) - - -def get_env_config() -> dict: - """ - 获取所有环境配置 - - Returns: - dict: 包含所有配置的字典 - - Example: - config = get_env_config() - print(config['database_id']) # 输出: "29" - print(config['datasource_id']) # 输出: "" - print(config['skill_id']) # 输出: "" - print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086" - """ - return { - "database_id": get_database_id(), - "datasource_id": get_datasource_id(), - "skill_id": get_skill_id(), - "backend_base_url": get_backend_base_url() - } - - -def set_env_variable(key: str, value: str) -> None: - """ - 设置环境变量(仅在当前进程中有效) - - Args: - key: 环境变量名 - value: 环境变量值 - - Example: - set_env_variable("databaseId", "30") - set_env_variable("skillId", "1234567890") - """ - os.environ[key] = value - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/json_helper.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/json_helper.py deleted file mode 100644 index 1a6fc95..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/json_helper.py +++ /dev/null @@ -1,60 +0,0 @@ -"""JSON 文件读取工具""" - -import json -from pathlib import Path -from typing import Any, Union - - -def load_json(json_path: Union[str, Path]) -> Any: - """ - 读取 JSON 文件并返回其内容 - - Args: - json_path: JSON 文件的路径(支持字符串或 Path 对象) - - Returns: - JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型) - - Raises: - FileNotFoundError: 当文件不存在时 - json.JSONDecodeError: 当 JSON 格式无效时 - Exception: 其他读取错误 - - Example: - >>> data = load_json('config.json') - >>> print(data) - {'key': 'value'} - - >>> data = load_json(Path('data/users.json')) - >>> print(data) - [{'id': 1, 'name': 'Alice'}] - """ - try: - # 转换为 Path 对象 - path = Path(json_path) - - # 检查文件是否存在 - if not path.exists(): - raise FileNotFoundError(f"JSON 文件不存在: {json_path}") - - # 检查是否为文件 - if not path.is_file(): - raise ValueError(f"路径不是一个文件: {json_path}") - - # 读取并解析 JSON 文件 - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - return data - - except json.JSONDecodeError as e: - raise json.JSONDecodeError( - f"JSON 格式错误: {e.msg}", - e.doc, - e.pos - ) - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/logger_config.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/logger_config.py deleted file mode 100644 index 290964f..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/logger_config.py +++ /dev/null @@ -1,489 +0,0 @@ -# -*- coding: utf-8 -*- -""" -统一日志配置模块 -提供系统级别的日志配置和管理 -""" - -import os -import sys -import logging -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from datetime import datetime -from pathlib import Path - - -class LoggerConfig: - """日志配置管理类""" - - def __init__(self, logs_dir: str = None): - """初始化日志配置 - - Args: - logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹 - """ - # 确定日志目录 - if logs_dir: - self.logs_dir = Path(logs_dir) - else: - # 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录) - project_root = Path(__file__).parent.parent - self.logs_dir = project_root / "logs" - - # 创建日志目录 - self.logs_dir.mkdir(exist_ok=True) - - # 日志格式 - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' - self.date_format = '%Y-%m-%d %H:%M:%S' - - # 从环境变量获取日志级别,默认为INFO - self.log_level = self._get_log_level_from_env() - - # 是否已初始化 - self._initialized = False - - def _get_log_level_from_env(self) -> int: - """从环境变量获取日志级别 - - Returns: - int: 日志级别 - """ - log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() - - # 日志级别映射 - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'WARN': logging.WARNING, # 兼容性别名 - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, - 'FATAL': logging.CRITICAL # 兼容性别名 - } - - return level_mapping.get(log_level_str, logging.INFO) - - def setup_logging(self, - app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO, - max_file_size: int = 10 * 1024 * 1024, # 10MB - backup_count: int = 5, - console_output: bool = True) -> logging.Logger: - """设置系统日志配置 - - Args: - app_name: 应用名称,用于日志文件命名 - log_level: 日志级别 - max_file_size: 单个日志文件最大大小(字节) - backup_count: 保留的备份文件数量 - console_output: 是否输出到控制台 - - Returns: - logging.Logger: 配置好的根日志器 - """ - if self._initialized: - return logging.getLogger() - - # 设置根日志器 - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # 清除现有的处理器 - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - # 创建格式化器 - formatter = logging.Formatter(self.log_format, self.date_format) - - # 1. 主日志文件 - 按大小滚动 - main_log_file = self.logs_dir / f"{app_name}.log" - file_handler = RotatingFileHandler( - main_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # 2. 错误日志文件 - 只记录ERROR及以上级别 - error_log_file = self.logs_dir / f"{app_name}_error.log" - error_handler = RotatingFileHandler( - error_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - error_handler.setLevel(logging.ERROR) - error_handler.setFormatter(formatter) - root_logger.addHandler(error_handler) - - # 3. 按日期滚动的日志文件 - daily_log_file = self.logs_dir / f"{app_name}_daily.log" - daily_handler = TimedRotatingFileHandler( - daily_log_file, - when='midnight', - interval=1, - backupCount=30, # 保留30天 - encoding='utf-8' - ) - daily_handler.setLevel(log_level) - daily_handler.setFormatter(formatter) - daily_handler.suffix = "%Y-%m-%d" - root_logger.addHandler(daily_handler) - - # 4. 控制台输出 - # 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信 - if console_output: - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setLevel(log_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - self.date_format - ) - console_handler.setFormatter(console_formatter) - root_logger.addHandler(console_handler) - - self._initialized = True - - # 记录初始化信息 - root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") - root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}") - - return root_logger - - def get_module_logger(self, module_name: str) -> logging.Logger: - """获取模块专用日志器 - - Args: - module_name: 模块名称 - - Returns: - logging.Logger: 模块日志器 - """ - return logging.getLogger(module_name) - - def create_component_logger(self, - component_name: str, - log_file: str = None, - log_level: int = None) -> logging.Logger: - """为特定组件创建独立的日志器 - - Args: - component_name: 组件名称 - log_file: 独立日志文件名(可选) - log_level: 日志级别(可选) - - Returns: - logging.Logger: 组件日志器 - """ - logger = logging.getLogger(component_name) - - if log_file: - # 为组件创建独立的日志文件 - component_log_file = self.logs_dir / log_file - handler = RotatingFileHandler( - component_log_file, - maxBytes=5 * 1024 * 1024, # 5MB - backupCount=3, - encoding='utf-8' - ) - - formatter = logging.Formatter(self.log_format, self.date_format) - handler.setFormatter(formatter) - - if log_level: - handler.setLevel(log_level) - - logger.addHandler(handler) - logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}") - - return logger - - def setup_mqtt_logging(self) -> logging.Logger: - """设置MQTT专用日志 - - Returns: - logging.Logger: MQTT日志器 - """ - return self.create_component_logger( - "mqtt_communication", - "mqtt_communication.log", - logging.DEBUG - ) - - def setup_mcp_logging(self) -> logging.Logger: - """设置MCP专用日志 - - Returns: - logging.Logger: MCP日志器 - """ - return self.create_component_logger( - "mcp_services", - "mcp_services.log", - logging.DEBUG - ) - - def setup_api_logging(self) -> logging.Logger: - """设置API专用日志 - - Returns: - logging.Logger: API日志器 - """ - return self.create_component_logger( - "api_requests", - "api_requests.log", - logging.INFO - ) - - def get_logs_info(self) -> dict: - """获取日志系统信息 - - Returns: - dict: 日志系统信息 - """ - log_files = [] - if self.logs_dir.exists(): - for log_file in self.logs_dir.glob("*.log*"): - stat = log_file.stat() - log_files.append({ - "name": log_file.name, - "size": stat.st_size, - "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() - }) - - return { - "logs_directory": str(self.logs_dir), - "initialized": self._initialized, - "log_files": log_files, - "total_files": len(log_files) - } - - def cleanup_old_logs(self, days: int = 30): - """清理旧日志文件 - - Args: - days: 保留天数 - """ - if not self.logs_dir.exists(): - return - - from datetime import timedelta - cutoff_time = datetime.now() - timedelta(days=days) - - cleaned_files = [] - for log_file in self.logs_dir.glob("*.log*"): - if log_file.stat().st_mtime < cutoff_time.timestamp(): - try: - log_file.unlink() - cleaned_files.append(log_file.name) - except Exception as e: - logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}") - - if cleaned_files: - logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}") - - def set_log_level(self, level: int, logger_name: str = None): - """动态调整日志级别 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - old_level = logger.level - logger.setLevel(level) - - # 同时调整所有处理器的级别 - for handler in logger.handlers: - if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr): - # 不调整控制台处理器的级别,保持原有设置 - handler.setLevel(level) - - level_name = logging.getLevelName(level) - old_level_name = logging.getLevelName(old_level) - target = logger_name or "根日志器" - - logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}") - - def set_temporary_log_level(self, level: int, logger_name: str = None): - """临时调整日志级别(会保存原始级别用于恢复) - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if not hasattr(self, '_original_levels'): - self._original_levels = {} - - target_name = logger_name or 'root' - - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - # 保存原始级别 - if target_name not in self._original_levels: - self._original_levels[target_name] = logger.level - - # 设置新级别 - self.set_log_level(level, logger_name) - - level_name = logging.getLevelName(level) - target = logger_name or "根日志器" - logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)") - - def restore_log_level(self, logger_name: str = None): - """恢复日志级别到调整前的状态 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - if not hasattr(self, '_original_levels'): - logging.warning("没有找到保存的原始日志级别") - return - - target_name = logger_name or 'root' - - if target_name not in self._original_levels: - logging.warning(f"没有找到 {target_name} 的原始日志级别") - return - - original_level = self._original_levels[target_name] - self.set_log_level(original_level, logger_name) - - # 清除保存的级别 - del self._original_levels[target_name] - - target = logger_name or "根日志器" - level_name = logging.getLevelName(original_level) - logging.info(f"已恢复日志级别: {target} -> {level_name}") - - def get_current_log_levels(self) -> dict: - """获取当前所有日志器的级别信息 - - Returns: - dict: 日志器级别信息 - """ - levels_info = {} - - # 根日志器 - root_logger = logging.getLogger() - levels_info['root'] = { - 'level': root_logger.level, - 'level_name': logging.getLevelName(root_logger.level), - 'handlers_count': len(root_logger.handlers) - } - - # 其他已创建的日志器 - for name, logger in logging.Logger.manager.loggerDict.items(): - if isinstance(logger, logging.Logger): - levels_info[name] = { - 'level': logger.level, - 'level_name': logging.getLevelName(logger.level), - 'handlers_count': len(logger.handlers) - } - - return levels_info - - -# 全局日志配置实例 -logger_config = LoggerConfig() - - -def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO) -> logging.Logger: - """系统日志初始化快捷函数 - - Args: - app_name: 应用名称 - log_level: 日志级别 - - Returns: - logging.Logger: 根日志器 - """ - return logger_config.setup_logging(app_name, log_level) - - -def get_logger(name: str) -> logging.Logger: - """获取日志器的快捷函数 - - Args: - name: 日志器名称 - - Returns: - logging.Logger: 日志器实例 - """ - return logger_config.get_module_logger(name) - - -def set_log_level(level: int, logger_name: str = None): - """动态调整日志级别的快捷函数 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 调整根日志器为DEBUG级别 - set_log_level(logging.DEBUG) - - # 调整特定模块日志器为WARNING级别 - set_log_level(logging.WARNING, "agent_ontology.core") - """ - logger_config.set_log_level(level, logger_name) - - -def set_temporary_log_level(level: int, logger_name: str = None): - """临时调整日志级别的快捷函数 - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 临时调整为DEBUG级别进行调试 - set_temporary_log_level(logging.DEBUG) - # ... 进行调试 ... - # 恢复原始级别 - restore_log_level() - """ - logger_config.set_temporary_log_level(level, logger_name) - - -def restore_log_level(logger_name: str = None): - """恢复日志级别的快捷函数 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - logger_config.restore_log_level(logger_name) - - -def get_current_log_levels() -> dict: - """获取当前日志级别信息的快捷函数 - - Returns: - dict: 日志器级别信息 - - Examples: - levels = get_current_log_levels() - print(f"根日志器级别: {levels['root']['level_name']}") - """ - return logger_config.get_current_log_levels() - - -# 便捷的日志级别常量 -class LogLevel: - """日志级别常量类""" - DEBUG = logging.DEBUG - INFO = logging.INFO - WARNING = logging.WARNING - ERROR = logging.ERROR - CRITICAL = logging.CRITICAL \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/name_helper.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/name_helper.py deleted file mode 100644 index d66cf32..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/name_helper.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" -名称生成工具模块 -""" - -from pypinyin import lazy_pinyin, Style -import logging - -logger = logging.getLogger(__name__) - - -def generate_tool_name(business_name: str, tool_id: str) -> str: - """ - 根据业务名称和ID生成工具名称 - 格式: tool_拼音_id - - Args: - business_name: 业务名称(中文) - tool_id: 工具ID - - Returns: - str: 格式化的工具名称 - """ - try: - # 将中文转换为拼音(无音调,小写) - pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL) - # 拼接拼音 - pinyin_str = ''.join(pinyin_list) - - # 将 ID 中的 '-' 替换为 '_' - formatted_id = tool_id.replace('-', '_') - - # 组合成最终的工具名称 - tool_name = f"tool_{pinyin_str}_{formatted_id}" - - return tool_name - except Exception as e: - logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True) - # 降级处理:如果拼音转换失败,使用 ID - return f"tool_{tool_id.replace('-', '_')}" - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/schema_helper.py b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/schema_helper.py deleted file mode 100644 index 626559f..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/utils/schema_helper.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Schema 生成工具模块 -""" - -from typing import Any, Dict, List - - -def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]: - """ - 从查询配置的参数定义生成 MCP 工具的 inputSchema - - 此函数会保留完整的 JSON Schema 信息,包括: - - type: Schema 类型(通常是 "object") - - required: 必填字段列表 - - properties: 属性定义(包括每个属性的 type, description, format, examples 等) - - description: Schema 的整体描述(如果有) - - 以及其他任何 JSON Schema 标准字段 - - 此函数还会自动添加以下字段(如果原始 parameters 中未定义): - - targetDatabaseName: 目标数据库名称(非必填,默认为空字符串) - - Args: - parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象 - - Returns: - Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象 - - Example: - >>> params = { - ... "type": "object", - ... "required": ["userId", "startTime"], - ... "properties": { - ... "userId": { - ... "type": "integer", - ... "description": "用户的唯一标识符", - ... "examples": [10086] - ... }, - ... "startTime": { - ... "type": "string", - ... "format": "date-time", - ... "description": "查询的起始时间", - ... "examples": ["2023-01-01 00:00:00"] - ... } - ... } - ... } - >>> schema = generate_input_schema(params) - >>> # schema 将包含所有原始信息,包括 format 和 examples - >>> # 同时会自动添加 targetDatabaseName 字段 - """ - # 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用 - # 但确保至少包含 type 和 properties - if not parameters: - # 如果 parameters 为空,返回一个空的 object schema - return { - "type": "object", - "properties": {}, - "required": [] - } - - # 检查 parameters 是否已经是完整的 JSON Schema 格式 - # 完整格式应该有 "type": "object" 和 "properties" 字段 - if "type" in parameters and parameters.get("type") == "object" and "properties" in parameters: - # 已经是完整的 JSON Schema 格式,直接使用 - input_schema = dict(parameters) - else: - # parameters 是简化格式(直接是参数定义),需要转换为 JSON Schema 格式 - # 例如: {"workOrderNumber": {"type": "string", "description": "...", "required": true}} - properties = {} - required = [] - - for param_name, param_def in parameters.items(): - if isinstance(param_def, dict): - # 提取 required 标记 - if param_def.get("required", False): - required.append(param_name) - - # 复制参数定义,但移除 required 字段(它应该在顶层的 required 数组中) - prop_def = {k: v for k, v in param_def.items() if k != "required"} - properties[param_name] = prop_def - - input_schema = { - "type": "object", - "properties": properties, - "required": required - } - - # 确保必需的字段存在 - if "type" not in input_schema: - input_schema["type"] = "object" - - if "properties" not in input_schema: - input_schema["properties"] = {} - - if "required" not in input_schema: - input_schema["required"] = [] - - # 添加 targetDatabaseName 字段(如果不存在) - if "targetDatabaseName" not in input_schema["properties"]: - input_schema["properties"]["targetDatabaseName"] = { - "type": "string", - "description": "目标数据库名称", - "default": "" - } - - # 保留所有其他字段,如 description, examples, format 等 - # JSON Schema 标准支持的字段都会被保留: - # - additionalProperties - # - patternProperties - # - minProperties / maxProperties - # - dependencies - # - 等等 - - return input_schema - - -def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: - """ - 验证 inputSchema 是否符合基本的 JSON Schema 规范 - - Args: - schema: 要验证的 schema 对象 - - Returns: - tuple[bool, str]: (是否有效, 错误消息或成功消息) - - Example: - >>> schema = {"type": "object", "properties": {"id": {"type": "string"}}} - >>> is_valid, msg = validate_input_schema(schema) - >>> print(is_valid, msg) - True, "Schema 验证通过" - """ - if not isinstance(schema, dict): - return False, "Schema 必须是一个字典对象" - - if schema.get("type") != "object": - return False, "Schema 的 type 字段必须是 'object'" - - if "properties" not in schema: - return False, "Schema 必须包含 properties 字段" - - if not isinstance(schema.get("properties"), dict): - return False, "Schema 的 properties 字段必须是一个字典对象" - - # 验证 required 字段(如果存在) - if "required" in schema: - required = schema["required"] - if not isinstance(required, list): - return False, "Schema 的 required 字段必须是一个列表" - - # 验证所有 required 的字段都在 properties 中定义 - properties = schema["properties"] - for field in required: - if field not in properties: - return False, f"必填字段 '{field}' 未在 properties 中定义" - - # 验证 properties 中每个字段的定义 - for prop_name, prop_def in schema["properties"].items(): - if not isinstance(prop_def, dict): - return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" - - if "type" not in prop_def: - return False, f"属性 '{prop_name}' 必须包含 type 字段" - - return True, "Schema 验证通过" - diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/uv.lock b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/uv.lock deleted file mode 100644 index 017ec10..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/uv.lock +++ /dev/null @@ -1,497 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, -] - -[[package]] -name = "anyio" -version = "4.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, -] - -[[package]] -name = "certifi" -version = "2025.10.5" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" }, -] - -[[package]] -name = "click" -version = "8.3.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, -] - -[[package]] -name = "httpx-sse" -version = "0.4.3" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" }, -] - -[[package]] -name = "lzwcai-mcpskills-analyzeWorkOrder" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "httpx" }, - { name = "mcp", extra = ["cli"] }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.10.1" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" }, -] - -[[package]] -name = "mcp" -version = "1.10.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "jsonschema" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-multipart" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" }, -] - -[package.optional-dependencies] -cli = [ - { name = "python-dotenv" }, - { name = "typer" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, -] - -[[package]] -name = "pydantic" -version = "2.12.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, -] - -[[package]] -name = "python-dotenv" -version = "1.1.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.20" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" }, -] - -[[package]] -name = "rpds-py" -version = "0.27.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" }, -] - -[[package]] -name = "sse-starlette" -version = "3.0.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" }, -] - -[[package]] -name = "starlette" -version = "0.48.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" }, -] - -[[package]] -name = "typer" -version = "0.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, -] - -[[package]] -name = "uvicorn" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" }, -] diff --git a/lzwcai_mcpskills_mfg_data_agent/main.py b/lzwcai_mcpskills_mfg_data_agent/main.py deleted file mode 100644 index 6a240d9..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/main.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Entry point for lzwcai-mcpskills-mfg-data-agent (制造业数据智能体) -Runs the MCP server for manufacturing data intelligence -""" -import os - -os.environ["databaseId"] = "57" -os.environ["datasourceId"] = "57" -os.environ["backendBaseUrl"] = "http://192.167.30.2:8088" -if __name__ == "__main__": - # Import and run the actual MCP server - from lzwcai_mcpskills_mfg_data_agent.main import main - main() diff --git a/lzwcai_mcpskills_mfg_data_agent/manufacturing_data_model_v1.0.0.md b/lzwcai_mcpskills_mfg_data_agent/manufacturing_data_model_v1.0.0.md deleted file mode 100644 index 02b9ec4..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/manufacturing_data_model_v1.0.0.md +++ /dev/null @@ -1,1094 +0,0 @@ -# manufacturing_data_model_v1.0.0 - -## 需求概述 - -本模型基于 `module/` 目录下 52 份业务导出 Excel,面向制造企业端到端经营与生产分析(线索→商机→合同→订单→采购/生产→入出库→质检→开票/收付款/核销→资金流→分佣),抽象形成可扩展的分析型数据模型。 - -设计目标: - -- 兼容 ISA-95 / IEC 62264:区分 Level 3(生产执行)与 Level 4(经营管理)数据边界 -- 分析型优先:星型模型 + 事实星座(Conformed Dimensions) -- 主数据一致性:维度表按 SCD Type 2 管理(`RowValidFrom/RowValidTo/IsCurrent`) -- 事务可追溯:事实表以 `EventTimeUTC` 作为事件时间分区/水印字段 - -范围说明: - -- 当前 Excel 以“编号/名称”作为主要业务键(例如 `产品编码`、`合同编号`、`工单编号`)。分析层统一引入代理主键 `{Entity}Id`。 -- 多数单据缺少“行明细”粒度(例如销售订单/采购订单仅见单头字段),模型保留单头事实为主,后续如补充明细可平滑扩展。 - -## 术语表 - -| 术语 | 含义 | -|---|---| -| 业务键(Business Key) | 来自源系统的编码/编号/单号等唯一标识,如 `ProductCode`、`ContractNumber` | -| 代理键(Surrogate Key) | 分析层自增/雪花主键,如 `ProductId` | -| SCD Type 2 | 维度历史保留:通过 `RowValidFrom/RowValidTo/IsCurrent` 记录有效期 | -| 退化维度(Degenerate Dimension) | 直接保存在事实表中的单据号、状态等维度属性 | -| EventTimeUTC | 统一事件时间(UTC),作为事实表分区/增量水印字段 | - -## 主题域总览(ISA-95 视角) - -| 主题域 | ISA-95 层级 | 覆盖 Excel | -|---|---|---| -| 主数据(产品/BOM/工艺/资源) | L3/L4 共用 | 产品列表、物料清单、工艺路线、工序、仓库、供应商、客户、设备、质检原因 | -| 生产执行 | Level 3 | 工单、任务、报工、装配工单 | -| 质量 | Level 3 | 质检记录、质检不通过原因 | -| 库存与仓储作业 | Level 3 | 出入库明细、盘点、调拨、其他出入库、报废 | -| 销售与履约 | Level 4 | 销售报价、销售订单、销售出库、销售退货 | -| 采购与供应 | Level 4 | 采购申请、采购订单、采购入库、采购退货 | -| 财务与资金 | Level 4 | 发票、回款、收款/付款、预收/预付、核销、资金流向、资金转账、其他收支、分佣 | -| CRM(市场到合同) | Level 4 | 线索、商机、合同 | - -## 实体清单(维度/事实) - -维度(SCD2): - -- `DimPerson`(人员维度) -- `DimProduct`(产品维度) -- `DimCustomer`(客户维度) -- `DimSupplier`(供应商维度) -- `DimWarehouse`(仓库维度) -- `DimBom`(物料清单维度) -- `DimRouting`(工艺路线维度) -- `DimOperation`(工序维度) -- `DimEquipment`(设备维度) -- `DimQcReason`(质检不通过原因维度) -- `DimContract`(合同维度,可选:合同也可作为事实) - -事实(按 `EventTimeUTC` 分区): - -- `FactInventoryMovement`(出入库流水事实) -- `FactWorkOrder`(工单事实) -- `FactOperationTask`(工序任务事实) -- `FactLaborReport`(报工事实) -- `FactQualityInspection`(质检事实) -- `FactSalesOrder`(销售订单事实) -- `FactSalesShipment`(销售出库事实) -- `FactSalesReturn`(销售退货事实) -- `FactPurchaseRequest`(采购申请事实) -- `FactPurchaseOrder`(采购订单事实) -- `FactPurchaseReceipt`(采购入库事实) -- `FactPurchaseReturn`(采购退货事实) -- `FactInventoryTransfer`(库存调拨事实) -- `FactInventoryCount`(库存盘点事实) -- `FactScrap`(报废事实) -- `FactArReceipt`(应收收款事实) -- `FactApPayment`(应付付款事实) -- `FactInvoice`(开票申请事实) -- `FactWriteOff`(核销事实) -- `FactCashFlow`(资金流水事实) -- `FactCommission`(分佣事实) -- `FactLead`(线索事实) -- `FactOpportunity`(商机事实) -- `FactProductionPlan`(生产计划/装配工单汇总事实,可选) - -## ER 图(事实星座) - -```mermaid -erDiagram - DimProduct ||--o{ FactInventoryMovement : "by_product (关联产品)" - DimWarehouse ||--o{ FactInventoryMovement : "by_warehouse (关联仓库)" - DimBom ||--o{ FactInventoryMovement : "by_bom (关联BOM)" - DimPerson ||--o{ FactInventoryMovement : "operated_by (操作人)" - - DimProduct ||--o{ FactWorkOrder : "produces (生产产品)" - DimSupplier ||--o{ FactWorkOrder : "outsourced_to (委外供应商)" - DimRouting ||--o{ FactWorkOrder : "uses_routing (使用工艺)" - DimPerson ||--o{ FactWorkOrder : "managed_by (生产主管)" - - DimOperation ||--o{ FactOperationTask : "executes (执行工序)" - DimProduct ||--o{ FactOperationTask : "for_product (关联产品)" - DimPerson ||--o{ FactOperationTask : "worker (执行工人)" - - DimProduct ||--o{ FactLaborReport : "reports_on (报工产品)" - DimOperation ||--o{ FactLaborReport : "at_operation (报工工序)" - DimPerson ||--o{ FactLaborReport : "reported_by (报工人员)" - - DimQcReason ||--o{ FactQualityInspection : "fail_reason (不合格原因)" - DimProduct ||--o{ FactQualityInspection : "inspects (质检产品)" - DimPerson ||--o{ FactQualityInspection : "inspected_by (质检员)" - - DimCustomer ||--o{ FactSalesOrder : "buys (购买)" - DimPerson ||--o{ FactSalesOrder : "sold_by (销售员)" - DimCustomer ||--o{ FactSalesShipment : "receives (收货)" - DimPerson ||--o{ FactSalesShipment : "shipped_by (发货员)" - DimCustomer ||--o{ FactSalesReturn : "returns (退货)" - DimPerson ||--o{ FactSalesReturn : "handled_by (处理人)" - - DimSupplier ||--o{ FactPurchaseOrder : "supplies (供应)" - DimPerson ||--o{ FactPurchaseOrder : "purchased_by (采购员)" - DimSupplier ||--o{ FactPurchaseReceipt : "delivers (送货)" - DimPerson ||--o{ FactPurchaseReceipt : "received_by (收货员)" - DimSupplier ||--o{ FactPurchaseReturn : "takes_back (退回)" - - DimContract ||--o{ FactInvoice : "invoiced_for (开票合同)" - DimContract ||--o{ FactArReceipt : "cash_in (收款合同)" - DimContract ||--o{ FactApPayment : "cash_out (付款合同)" - DimContract ||--o{ FactWriteOff : "writeoff (核销合同)" - DimPerson ||--o{ FactContract : "owned_by (合同负责人)" - DimPerson ||--o{ FactLead : "owned_by (线索负责人)" - DimPerson ||--o{ FactOpportunity : "owned_by (商机负责人)" -``` - -## 业务数据流转全景图 - -本流程图展示了从商机线索到最终产品交付的端到端数据流转过程,以及各环节涉及的关键数据实体。 - -```mermaid -flowchart TD - %% 节点样式定义 - classDef process fill:#e1f5fe,stroke:#01579b,stroke-width:2px; - classDef decision fill:#fff9c4,stroke:#fbc02d,stroke-width:2px; - classDef table fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px,stroke-dasharray: 5 5; - - %% 1. CRM阶段 - subgraph Stage_CRM [CRM与销售阶段] - direction TB - Node_Lead[("线索处理
In: FactLead
Logic: 线索清洗/转化")]:::process - Node_Opp[("商机确认
In: FactLead
Out: FactOpportunity")]:::process - Decision_Win{商机赢单?}:::decision - Node_Contract[("合同签订
In: FactOpportunity
Out: DimContract")]:::process - Node_SO[("销售订单生成
In: DimContract
Out: FactSalesOrder")]:::process - - Table_Lead[("FactLead")]:::table - Table_Opp[("FactOpportunity")]:::table - Table_SO[("FactSalesOrder")]:::table - end - - %% 2. 计划阶段 - subgraph Stage_Plan [计划与排程阶段] - direction TB - Node_MPS[("主生产计划(MPS)
In: FactSalesOrder
Logic: 需求合并/产能平衡
Out: FactProductionPlan")]:::process - Node_MRP[("MRP运算/BOM展开
In: FactProductionPlan, DimBom
Logic: 净需求计算
Out: FactWorkOrder(Planned)")]:::process - Decision_MakeBuy{自制/外购?}:::decision - Node_PR[("采购申请生成
Out: FactPurchaseRequest")]:::process - - Table_Plan[("FactProductionPlan")]:::table - Table_Bom[("DimBom")]:::table - Table_Stock[("FactInventoryMovement
(当前库存)")]:::table - end - - %% 新增:3. 采购与供应阶段 - subgraph Stage_Procure [采购与供应阶段] - direction TB - Node_PO[("采购订单(PO)
In: FactPurchaseRequest
Out: FactPurchaseOrder")]:::process - Node_Receive[("收货与IQC
Logic: 验收/质检
Out: FactQualityInspection")]:::process - Decision_IQC{IQC合格?}:::decision - Node_Return[("采购退货
Out: FactPurchaseReturn")]:::process - Node_Mat_In[("原材料入库
Logic: 批次录入/更新库存
Out: FactPurchaseReceipt
FactInventoryMovement")]:::process - Node_AP[("应付挂账(AP)
Logic: 三单匹配
Out: FactApPayment")]:::process - - Table_PO[("FactPurchaseOrder")]:::table - Table_Receipt[("FactPurchaseReceipt")]:::table - end - - %% 4. 生产执行阶段 (原 Stage_Make) - subgraph Stage_Make [生产执行阶段] - direction TB - Node_WO_Release[("工单下达
In: FactWorkOrder(Planned)
Logic: 物料齐套/锁定资源
Out: FactWorkOrder(Released)")]:::process - Node_Task[("工序任务派工
In: FactWorkOrder, DimRouting
Out: FactOperationTask")]:::process - Node_Labor[("生产报工
In: FactOperationTask
Logic: 投入/产出/工时记录
Out: FactLaborReport")]:::process - - Table_WO[("FactWorkOrder")]:::table - Table_Task[("FactOperationTask")]:::table - Table_Labor[("FactLaborReport")]:::table - end - - %% 5. 质量与入库阶段 (原 Stage_QC) - subgraph Stage_QC [质量与仓储阶段] - direction TB - Node_QC[("成品检验(FQC)
In: FactLaborReport
Logic: 检验标准比对
Out: FactQualityInspection")]:::process - Decision_Pass{质检合格?}:::decision - Node_Scrap[("报废处理
Logic: 记录不良原因
Out: FactScrap")]:::process - Node_FG_In[("成品入库
In: FactWorkOrder(Closed)
Logic: 更新库存余额
Out: FactInventoryMovement(IN)")]:::process - - Table_QC[("FactQualityInspection")]:::table - Table_Inv[("FactInventoryMovement")]:::table - end - - %% 6. 交付阶段 (原 Stage_Deliver) - subgraph Stage_Deliver [交付阶段] - direction TB - Node_Ship[("销售发货
In: FactSalesOrder, FactInventoryMovement
Logic: 拣货/出库扣减
Out: FactSalesShipment")]:::process - Node_End((交付完成)) - - Table_Ship[("FactSalesShipment")]:::table - end - - %% 新增:7. 财务与资金阶段 - subgraph Stage_Finance [财务与资金阶段] - direction TB - %% AR Flow - Node_Invoice[("销售开票
In: FactSalesShipment
Out: FactInvoice")]:::process - Node_ArRec[("销售收款
Out: FactArReceipt")]:::process - Node_ArOff[("应收核销
Logic: 核销发票与收款
Out: FactWriteOff")]:::process - - %% AP Flow - Node_ApPay[("采购付款
Out: FactApPayment")]:::process - Node_ApOff[("应付核销
Logic: 核销应付与付款
Out: FactWriteOff")]:::process - - %% Cash Flow - Node_Cash[("资金流水
Logic: 账户变动记录
Out: FactCashFlow")]:::process - - Table_Inv[("FactInvoice")]:::table - Table_Ar[("FactArReceipt")]:::table - Table_Ap[("FactApPayment")]:::table - Table_Cash[("FactCashFlow")]:::table - end - - %% 关系连线 - Node_Lead --> Node_Opp - Node_Opp --> Decision_Win - Decision_Win -- Yes --> Node_Contract - Node_Contract --> Node_SO - Node_SO --> Node_MPS - - Node_MPS --> Node_MRP - Node_MRP --> Decision_MakeBuy - Decision_MakeBuy -- 外购 --> Node_PR - Decision_MakeBuy -- 自制 --> Node_WO_Release - - %% 采购流程连线 - Node_PR --> Node_PO - Node_PO --> Node_Receive - Node_Receive --> Decision_IQC - Decision_IQC -- No --> Node_Return - Decision_IQC -- Yes --> Node_Mat_In - Node_Mat_In --> Node_AP - Node_Mat_In --> Node_WO_Release - - Node_WO_Release --> Node_Task - Node_Task --> Node_Labor - Node_Labor --> Node_QC - - Node_QC --> Decision_Pass - Decision_Pass -- No --> Node_Scrap - Decision_Pass -- Yes --> Node_FG_In - - Node_FG_In --> Node_Ship - Node_Ship --> Node_End - - %% 财务流程连线 - Node_Ship --> Node_Invoice - Node_Invoice --> Node_ArRec - Node_ArRec --> Node_ArOff - - Node_AP --> Node_ApPay - Node_ApPay --> Node_ApOff - - Node_ArRec --> Node_Cash - Node_ApPay --> Node_Cash - - %% 数据表关联(虚线) - Table_Lead -.-> Node_Lead - Node_Opp -.-> Table_Opp - Node_Contract -.-> Table_SO - Table_SO -.-> Node_MPS - Node_MPS -.-> Table_Plan - Table_Plan -.-> Node_MRP - Table_Bom -.-> Node_MRP - Table_Stock -.-> Node_MRP - - Node_PR -.-> Table_PO - Node_PO -.-> Table_PO - Node_Mat_In -.-> Table_Receipt - Node_Mat_In -.-> Table_Inv - - Node_WO_Release -.-> Table_WO - Node_Task -.-> Table_Task - Node_Labor -.-> Table_Labor - Node_QC -.-> Table_QC - Node_Receive -.-> Table_QC - Node_FG_In -.-> Table_Inv - Node_Ship -.-> Table_Ship - - Node_Invoice -.-> Table_Inv - Node_ArRec -.-> Table_Ar - Node_ApPay -.-> Table_Ap - Node_Cash -.-> Table_Cash -``` - -### 关键业务规则说明 - -1. **商机转订单**: - * 当 `FactOpportunity` 状态确认为“赢单”且 `DimContract` 签订后,系统生成 `FactSalesOrder`。 - * 此时确立销售对象(Customer)、产品(Product)及交付日期(DeliveryDate)。 - -2. **计划运算 (MPS/MRP)**: - * `FactProductionPlan` 汇总销售订单需求,结合 `FactInventoryMovement` 计算出的当前库存快照,进行净需求计算。 - * 利用 `DimBom` 展开物料清单,区分自制件(生成 `FactWorkOrder`)与采购件(生成 `FactPurchaseRequest`)。 - -3. **采购闭环管理**: - * **采购订单 (PO)**:`FactPurchaseOrder` 经审批后生效,明确供应商、价格、交期。 - * **入库验收 (IQC)**:物料到达后必须进行 IQC(`FactQualityInspection`),合格品方可办理入库(`FactPurchaseReceipt`)并增加库存(`FactInventoryMovement`)。 - * **财务对接 (AP)**:系统自动匹配 PO(采购单)、Receipt(入库单)和供应商发票,生成应付账款(`FactApPayment`),确保“三单一致”。 - * **供应商管理**:IQC 合格率与交期准确率回写至 `DimSupplier`,用于季度评估与优选名录维护。 - -4. **生产执行闭环**: - * 工单下达前需校验物料齐套性(依赖采购入库的 `FactInventoryMovement`)。 - * 现场工人通过 `FactLaborReport` 上报实际投入工时与产出数量,该数据是成本核算的核心依据。 - -5. **质量控制 (FQC)**: - * `FactQualityInspection` 记录成品检验结果。 - * 质检合格触发成品入库,不合格则根据原因(`DimQcReason`)触发返工或报废(`FactScrap`)。 - -6. **库存与交付**: - * 所有实物库存变动(入库、出库、报废)统一写入 `FactInventoryMovement`,确保库存台账的一致性。 - * `FactSalesShipment` 记录发货动作,作为收入确认(Revenue Recognition)的前置事件。 - -7. **财务资金流转**: - * **应收 (AR)**:销售发货后生成发票申请(`FactInvoice`),回款后(`FactArReceipt`)进行核销(`FactWriteOff`)。 - * **应付 (AP)**:采购入库确认应付后(`FactApPayment` 中的挂账状态),执行付款(`FactApPayment` 中的实付)并核销。 - * **资金 (Cash)**:所有收付款动作最终汇总至 `FactCashFlow`,提供实时的资金日记账分析。 - -## 表结构详设 - -类型说明(可按目标数据库微调): - -- `bigint`:代理主键/外键 -- `varchar(n)`:业务键、名称、状态等 -- `decimal(18,4)`:数量与金额(金额可按需要改 `decimal(18,2)`) -- `datetime2`:统一时间字段(UTC) - -### DimPerson(人员维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PersonId | bigint | N | autoincr | 主键 | | - | PersonCode | varchar(64) | N | | 人员编码(业务键) | 来源:用户ID/工号 | - | PersonName | varchar(255) | N | | 姓名 | | - | Department | varchar(100) | Y | | 部门 | | - | Position | varchar(100) | Y | | 职位/岗位 | | - | Phone | varchar(32) | Y | | 电话 | | - | Email | varchar(100) | Y | | 邮箱 | | - | Status | varchar(20) | Y | | 状态 | ACTIVE, INACTIVE | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | -| Birthday | date | Y | | 出生日期 | | -| Employment_day | date | Y | | 入职日期 | | -| Job_description | varchar(255) | Y | | 职务描述 | | - -### DimProduct(产品维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ProductId | bigint | N | autoincr | 主键 | | - | ProductCode | varchar(64) | N | | 产品编码(业务键) | 来源:产品列表.产品编码 | - | ProductName | varchar(255) | N | | 产品名称 | | - | ProductSpec | varchar(255) | Y | | 产品规格 | | - | ProductCategory | varchar(100) | Y | | 产品类别 | | - | ProductType | varchar(100) | Y | | 产品类型 | | - | UomCode | varchar(16) | Y | | 主单位 | kg,pcs,L,m | - | Brand | varchar(100) | Y | | 品牌 | | - | Origin | varchar(100) | Y | | 产地 | | - | TransportCondition | varchar(100) | Y | | 运输条件 | | - | Barcode | varchar(64) | Y | | 条形码 | | - | DrawingNumber | varchar(64) | Y | | 图号 | | - | Material | varchar(100) | Y | | 材料 | | - | SurfaceTreatment | varchar(100) | Y | | 表面处理 | | - | MinStockQty | decimal(18,4) | Y | | 最低库存数量 | | - | SafetyStockQty | decimal(18,4) | Y | | 安全库存数量 | | - | MaxStockQty | decimal(18,4) | Y | | 最高库存数量 | | - | SourceSystem | varchar(50) | Y | | 数据来源 | 来源:数据来源 | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimCustomer(客户维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| CustomerId | bigint | N | autoincr | 主键 | | - | CustomerName | varchar(255) | N | | 客户名称(业务键) | 来源:客户列表.客户名称 | - | CustomerSource | varchar(100) | Y | | 客户来源 | | - | Phone | varchar(32) | Y | | 手机 | | - | Telephone | varchar(32) | Y | | 电话 | | - | Email | varchar(100) | Y | | 邮箱 | | - | CustomerLevel | varchar(50) | Y | | 客户级别 | | - | Industry | varchar(100) | Y | | 客户行业 | | - | Address | varchar(500) | Y | | 详细地址 | | - | IsLocked | bit | Y | | 锁定状态 | 0,1 | - | DealStatus | varchar(50) | Y | | 成交状态 | | - | LastFollowUpTimeUTC | datetime2 | Y | | 最后跟进时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimSupplier(供应商维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SupplierId | bigint | N | autoincr | 主键 | | - | SupplierCode | varchar(64) | N | | 供应商编码(业务键) | 来源:供应商列表.供应商编码 | - | SupplierName | varchar(255) | N | | 供应商名称 | | - | SupplierCategory | varchar(100) | Y | | 供应商分类 | | - | SupplierLevel | varchar(50) | Y | | 供应商级别 | | - | Address | varchar(500) | Y | | 地址 | | - | DefaultCurrencyCode | varchar(16) | Y | | 默认币别 | CNY,USD,EUR | - | SettlementTerm | varchar(50) | Y | | 结算期限 | | - | ContractExpiryDateUTC | datetime2 | Y | | 合同到期日(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimWarehouse(仓库维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| WarehouseId | bigint | N | autoincr | 主键 | | - | WarehouseCode | varchar(64) | N | | 仓库编码(业务键) | 来源:仓库列表.仓库编码 | - | WarehouseName | varchar(255) | N | | 仓库名称 | | - | WarehouseType | varchar(50) | Y | | 仓库类型 | | - | Status | varchar(20) | Y | | 仓库状态 | ENABLED,DISABLED | - | Address | varchar(500) | Y | | 仓库地址 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimBom(物料清单维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| BomId | bigint | N | autoincr | 主键 | | - | BomNumber | varchar(64) | N | | 清单编号(业务键) | 来源:物料清单.清单编号 | - | BomTopic | varchar(100) | Y | | 清单主题 | 来源:清单主题 | -| IsPrimary | bit | Y | | 主辅清单标识 | 0,1 | -| LevelNumber | int | Y | | 层级 | | - | ProductCode | varchar(64) | Y | | 关联产品编码 | 来源:产品编号 | - | UnitUsageQty | decimal(18,4) | Y | | 单位用量 | | - | UomCode | varchar(16) | Y | | 产品单位 | | - | SupplierName | varchar(255) | Y | | 默认供应商(退化属性) | 来源:供应商 | -| Remark | varchar(500) | Y | | 备注 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimRouting(工艺路线维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| RoutingId | bigint | N | autoincr | 主键 | | - | RoutingNumber | varchar(64) | N | | 工艺路线编号(业务键) | 来源:工艺路线.工艺路线编号 | - | RoutingName | varchar(255) | N | | 工艺路线名称 | | - | OperationListText | varchar(2000) | Y | | 工序/工艺路线列表(原文) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimOperation(工序维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| OperationId | bigint | N | autoincr | 主键 | | - | OperationNumber | varchar(64) | N | | 工序编号(业务键) | 来源:工序.工序编号 | - | OperationName | varchar(255) | N | | 工序名称 | | - | ReportPermission | varchar(50) | Y | | 报工权限 | | - | ReportRatio | decimal(18,6) | Y | | 报工数配比 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimEquipment(设备维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| EquipmentId | bigint | N | autoincr | 主键 | | - | EquipmentNumber | varchar(64) | N | | 设备编号(业务键) | 来源:设备.设备编号 | - | EquipmentName | varchar(255) | N | | 设备名称 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimQcReason(质检不通过原因维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| QcReasonId | bigint | N | autoincr | 主键 | | - | QcReasonNumber | varchar(64) | N | | 不通过原因编号(业务键) | 来源:质检原因.不通过原因编号 | - | QcReasonName | varchar(255) | N | | 不通过原因 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimContract(合同维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ContractId | bigint | N | autoincr | 主键 | | - | ContractNumber | varchar(64) | N | | 合同编号(业务键) | 来源:合同.合同编号 | - | ContractName | varchar(255) | Y | | 合同名称 | | - | OwnerId | bigint | Y | | 负责人 (外键 → DimPerson) | | - | CustomerName | varchar(255) | Y | | 客户名称(退化) | | - | CustomerId | bigint | Y | | 关联客户(外键 → DimCustomer) | | - | ContractAmount | decimal(18,4) | Y | | 合同金额 | | - | OrderTimeUTC | datetime2 | Y | | 下单时间(UTC) | | -| PaidAmount | decimal(18,4) | Y | | 回款金额 | | -| UnpaidAmount | decimal(18,4) | Y | | 未回款金额 | | -| Stage | varchar(50) | Y | | 当前阶段 | | -| Result | varchar(50) | Y | | 结果 | | - | ContractType | varchar(50) | Y | | 合同类型 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInventoryMovement(出入库流水事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InventoryMovementId | bigint | N | autoincr | 主键 | | - | EventTimeUTC | datetime2 | N | | 出入库事件时间(UTC) | 来源:出入库日期/入库时间/出库时间 | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | - | WarehouseId | bigint | Y | | 关联DimWarehouse (外键 → DimWarehouse) | | - | BomId | bigint | Y | | 关联DimBom (外键 → DimBom) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(操作人) | | -|SalesOrderId | bigint | Y | | 关联FactSalesOrder (外键 → FactSalesOrder) | | -|PurchaseOrderId | bigint | Y | | 关联FactPurchaseOrder (外键 → FactPurchaseOrder) | | - | DocNumber | varchar(64) | N | | 出入库单号 | 来源:出入库单号/入库单编号/出库单编号 | - | DocType | varchar(30) | N | | 单据类型 | IN,OUT,TRANSFER,OTHER_IN,OTHER_OUT | - | MovementQty | decimal(18,4) | N | 0 | 出入库数量(入为正,出为负) | | -| UomCode | varchar(16) | Y | | 单位 | | -| UnitPrice | decimal(18,4) | Y | | 单价 | | -| DiscountUnitPrice | decimal(18,4) | Y | | 折后单价 | | -| Amount | decimal(18,4) | Y | | 总价 | | -| DiscountAmount | decimal(18,4) | Y | | 折后总价 | | -| OnhandQtyAfter | decimal(18,4) | Y | | 发生后库存量(源字段) | | -| ResponsiblePerson | varchar(100) | Y | | 负责人 | | -| AuditStatus | varchar(20) | Y | | 审核状态 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactWorkOrder(工单事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| WorkOrderId | bigint | N | autoincr | 主键 | | - | WorkOrderNumber | varchar(64) | N | | 工单编号(退化维度) | 来源:工单编号 | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| SupplierId | bigint | Y | | 外键 → DimSupplier(委外时) | | -| RoutingId | bigint | Y | | 外键 → DimRouting | | -| ProductionManagerId | bigint | Y | | 外键 → DimPerson(生产主管) | | - | PlannedQty | decimal(18,4) | Y | | 计划生产数 | | - | CompletedQty | decimal(18,4) | Y | | 完成数 | | -| OperationGoodQty | decimal(18,4) | Y | | 工序良品数 | | -| OperationBadQty | decimal(18,4) | Y | | 工序不良品数 | | -| QcGoodQty | decimal(18,4) | Y | | 工单质检良品数 | | -| QcBadQty | decimal(18,4) | Y | | 工单质检不良品数 | | - | Status | varchar(30) | Y | | 工单状态 | OPEN,STARTED,CLOSED,SCRAP | - | EventTimeUTC | datetime2 | N | | 工单状态变更/抽取时间(UTC) | | -| SourceDocNumber | varchar(64) | Y | | 单据编号(退化) | 来源:单据编号 | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactOperationTask(工序任务事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| OperationTaskId | bigint | N | autoincr | 主键 | | -| WorkOrderNumber | varchar(64) | N | | 工单编号(退化维度) | 来源:工单编号 | -| OperationId | bigint | Y | | 关联DimOperation (外键 → DimOperation) | | -| ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| PlannedStartTimeUTC | datetime2 | Y | | 计划开始时间(UTC) | | -| PlannedEndTimeUTC | datetime2 | Y | | 计划结束时间(UTC) | | -| ActualStartTimeUTC | datetime2 | Y | | 实际开始时间(UTC) | | -| ActualEndTimeUTC | datetime2 | Y | | 实际结束时间(UTC) | | -| PlannedQty | decimal(18,4) | Y | | 计划数 | | -| GoodQty | decimal(18,4) | Y | | 良品数 | | -| BadQty | decimal(18,4) | Y | | 不良品数 | | -| BadReasonText | varchar(500) | Y | | 不良品项(原文) | | -| EventTimeUTC | datetime2 | N | | 任务事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactLaborReport(报工事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| LaborReportId | bigint | N | autoincr | 主键 | | - | WorkOrderNumber | varchar(64) | N | | 工单(退化维度) | 来源:报工列表.工单 | -| TaskName | varchar(255) | Y | | 任务(退化维度) | 来源:报工列表.任务 | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| OperationStatus | varchar(30) | Y | | 工序状态 | | - | WorkerName | varchar(100) | Y | | 生产人员 | | - | ReportQty | decimal(18,4) | N | 0 | 报工数 | | - | GoodQty | decimal(18,4) | Y | | 良品数 | | - | BadQty | decimal(18,4) | Y | | 良品数 | | -| DurationMinutes | decimal(18,2) | Y | | 报工时长(分钟) | | -| UomCode | varchar(16) | Y | | 单位 | | -| BadReasonText | varchar(500) | Y | | 不良品项(原文) | | - | EventTimeUTC | datetime2 | N | | 报工事件时间(UTC) | | -| SupplierId | bigint | Y | | 外键 → DimSupplier(委外时) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactQualityInspection(质检事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| QualityInspectionId | bigint | N | autoincr | 主键 | | - | InspectionNumber | varchar(64) | N | | 质检记录编号(退化维度) | 来源:质检记录编号 | -| InspectionBatchNumber | varchar(64) | Y | | 质检批次号 | | - | WorkOrderNumber | varchar(64) | Y | | 工单(退化维度) | | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| SupplierId | bigint | Y | | 外键 → DimSupplier | | -| QcReasonId | bigint | Y | | 外键 → DimQcReason(不通过原因) | | - | InspectorId | bigint | Y | | 质检员 (外键 → DimPerson) | | -| QcType | varchar(30) | Y | | 质检类型 | IN_PROCESS,FINAL,INCOMING | - | PassQty | decimal(18,4) | Y | | 通过数 | | - | FailQty | decimal(18,4) | Y | | 不通过数 | | -| SalesOrderNumber | varchar(64) | Y | | 销售订单(退化维度) | | -| CustomerName | varchar(255) | Y | | 客户(退化维度) | | -| ShipQty | decimal(18,4) | Y | | 发货数 | | - | EventTimeUTC | datetime2 | N | | 质检事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactSalesOrder(销售订单事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SalesOrderId | bigint | N | autoincr | 主键 | | - | SalesOrderNumber | varchar(64) | N | | 订单编号(退化维度) | 来源:销售订单.订单编号 | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SalesPersonId | bigint | Y | | 关联销售员 (外键 → DimPerson) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | - | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | OrderDateUTC | datetime2 | Y | | 订单单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | - | DealAmount | decimal(18,4) | Y | | 成交金额 | | -| PaymentStatus | varchar(30) | Y | | 收款状态/付款状态 | | -| TransportMode | varchar(50) | Y | | 运输方式 | | -| Packaging | varchar(100) | Y | | 包材 | | -| CustomsNumber | varchar(64) | Y | | 报关单号 | | -| ProductionDocNumber | varchar(64) | Y | | 生产单据编号 | | -| Stage | varchar(50) | Y | | 当前阶段 | | - | Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 订单事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactSalesShipment(销售出库事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SalesShipmentId | bigint | N | autoincr | 主键 | | - | ShipmentNumber | varchar(64) | N | | 出库单编号(退化维度) | 来源:销售出库单.出库单编号 | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SalesPersonId | bigint | Y | | 关联销售员 (外键 → DimPerson) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(发货员) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | - | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 出库单单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | -| TransportMode | varchar(50) | Y | | 运输方式 | | -| CustomsNumber | varchar(64) | Y | | 报关单号 | | - | Amount | decimal(18,4) | Y | | 成交金额/单据金额 | | - | EventTimeUTC | datetime2 | N | | 出库事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactSalesReturn(销售退货事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SalesReturnId | bigint | N | autoincr | 主键 | | - | SalesReturnNumber | varchar(64) | N | | 销售退货单编号(退化维度) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SalesPersonId | bigint | Y | | 销售员 (外键 → DimPerson) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(处理人) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | -| ReturnReason | varchar(255) | Y | | 退货原因 | | -| IsReship | bit | Y | | 是否退货重发 | 0,1 | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | Amount | decimal(18,4) | Y | | 单据金额 | | - | EventTimeUTC | datetime2 | N | | 退货事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseRequest(采购申请事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseRequestId | bigint | N | autoincr | 主键 | | - | PurchaseRequestNumber | varchar(64) | N | | 采购申请单编号(退化维度) | | - | SupplierId | bigint | Y | | 建议供应商 (外键 → DimSupplier) | | - | ApplicantId | bigint | Y | | 申请人 (外键 → DimPerson) | | - | CurrencyCode | varchar(16) | Y | | 币别 | | - | FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | - | TotalAmount | decimal(18,4) | Y | | 采购总价 | | - | Status | varchar(30) | Y | | 采购状态 | | - | RelatedOrderNumber | varchar(64) | Y | | 订单编号(退化) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | EventTimeUTC | datetime2 | N | | 采购申请事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseOrder(采购订单事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseOrderId | bigint | N | autoincr | 主键 | | - | PurchaseOrderNumber | varchar(64) | N | | 采购订单编号(退化维度) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | BuyerId | bigint | Y | | 关联采购员 (外键 → DimPerson) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | -| DeliveryNoteNumber | varchar(64) | Y | | 送货单号 | | -| SettlementDateUTC | datetime2 | Y | | 结算日期(UTC) | | -| SettlementTerm | varchar(50) | Y | | 结算期限 | | - | EventTimeUTC | datetime2 | N | | 采购订单事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseReceipt(采购入库事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseReceiptId | bigint | N | autoincr | 主键 | | - | PurchaseReceiptNumber | varchar(64) | N | | 采购入库单编号(退化维度) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | -| BuyerId | bigint | Y | | 外键 → DimPerson(采购员) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(收货员) | | - | PurchaseOrderNumber | varchar(64) | Y | | 采购订单(退化维度) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | -| ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | Amount | decimal(18,4) | Y | | 成交金额 | | -| EstimatedCost | decimal(18,4) | Y | | 预计采购费用 | | -| ReceiptQtyTotal | decimal(18,4) | Y | | 入库总数 | | -| PaidAmount | decimal(18,4) | Y | | 已付款金额 | | -| UnpaidAmount | decimal(18,4) | Y | | 未付款金额 | | -| PaymentStatus | varchar(30) | Y | | 付款状态 | | -| ReturnedQty | decimal(18,4) | Y | | 已退货数 | | -| NotReturnedQty | decimal(18,4) | Y | | 未退货数 | | -| ReturnStatus | varchar(30) | Y | | 退货状态 | | - | EventTimeUTC | datetime2 | N | | 入库事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseReturn(采购退货事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseReturnId | bigint | N | autoincr | 主键 | | - | PurchaseReturnNumber | varchar(64) | N | | 采购退货单编号(退化维度) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | BuyerId | bigint | Y | | 采购员 (外键 → DimPerson) | | - | OperatorId | bigint | Y | | 处理人 (外键 → DimPerson) | | - | CurrencyCode | varchar(16) | Y | | 币别 | | - | FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | ReturnReason | varchar(255) | Y | | 退货原因 | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | RelatedOrderNumber | varchar(64) | Y | | 订单号(退化) | | - | EventTimeUTC | datetime2 | N | | 退货事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInventoryTransfer(库存调拨事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InventoryTransferId | bigint | N | autoincr | 主键 | | - | TransferNumber | varchar(64) | N | | 调拨单编号(退化维度) | | - | OperatorId | bigint | Y | | 操作人 (外键 → DimPerson) | | - | TransferName | varchar(255) | Y | | 调拨名称 | | - | Tag | varchar(100) | Y | | 单据标签 | | - | TransferTimeUTC | datetime2 | Y | | 调拨时间(UTC) | | - | PlateNumber | varchar(32) | Y | | 车牌号 | | - | DeliveryMode | varchar(50) | Y | | 交货方式 | | - | EventTimeUTC | datetime2 | N | | 调拨事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInventoryCount(库存盘点事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InventoryCountId | bigint | N | autoincr | 主键 | | - | CountNumber | varchar(64) | N | | 盘点编号(退化维度) | | - | OperatorId | bigint | Y | | 盘点人 (外键 → DimPerson) | | - | WarehouseName | varchar(255) | Y | | 盘点仓库(退化) | | - | CountName | varchar(255) | Y | | 盘点名称 | | - | CountTimeUTC | datetime2 | Y | | 盘点时间(UTC) | | - | EventTimeUTC | datetime2 | N | | 盘点事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactScrap(报废事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ScrapId | bigint | N | autoincr | 主键 | | - | ScrapNumber | varchar(64) | N | | 报废编号(退化维度) | | - | OperatorId | bigint | Y | | 操作人 (外键 → DimPerson) | | - | WarehouseName | varchar(255) | Y | | 仓库(退化) | | - | ScrapTimeUTC | datetime2 | Y | | 报废日期(UTC) | | - | EventTimeUTC | datetime2 | N | | 报废事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactArReceipt(应收收款事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ArReceiptId | bigint | N | autoincr | 主键 | | - | DocNumber | varchar(64) | N | | 单据编号(退化维度) | 来源:收款单/回款/预收/其他收入等 | - | DocType | varchar(30) | N | | 单据类型 | RECEIPT,REFUND,ADVANCE,ADVANCE_REFUND,OTHER_INCOME | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | Amount | decimal(18,4) | Y | | 收款总金额/回款金额/应收金额 | | -| AmountBase | decimal(18,4) | Y | | 本位币金额 | 来源:本位币字段 | -| FeeAmount | decimal(18,4) | Y | | 手续费 | | -| DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | -| WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | -| WrittenOffAmount | decimal(18,4) | Y | | 已核销金额 | | -| UnwrittenOffAmount | decimal(18,4) | Y | | 未核销金额 | | -| RelatedDocNumber | varchar(64) | Y | | 关联单编号/销售订单编号 | | - | EventTimeUTC | datetime2 | N | | 收款事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactApPayment(应付付款事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ApPaymentId | bigint | N | autoincr | 主键 | | - | DocNumber | varchar(64) | N | | 单据编号(退化维度) | 来源:付款单/预付款/其他支出等 | - | DocType | varchar(30) | N | | 单据类型 | PAYMENT,REFUND,ADVANCE,ADVANCE_REFUND,OTHER_EXPENSE | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | Amount | decimal(18,4) | Y | | 付款总金额/应付金额 | | -| AmountBase | decimal(18,4) | Y | | 本位币金额 | | -| FeeAmount | decimal(18,4) | Y | | 手续费 | | -| DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | -| WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | -| WrittenOffAmount | decimal(18,4) | Y | | 已核销金额 | | -| UnwrittenOffAmount | decimal(18,4) | Y | | 未核销金额 | | -| RelatedDocNumber | varchar(64) | Y | | 关联单编号/采购订单编号 | | - | EventTimeUTC | datetime2 | N | | 付款事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInvoice(开票申请事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InvoiceId | bigint | N | autoincr | 主键 | | - | InvoiceRequestNumber | varchar(64) | N | | 发票申请编号(退化维度) | 来源:发票申请编号 | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | -| HandlerId | bigint | Y | | 外键 → DimPerson(经办人) | | -| CustomerId | bigint | Y | | 外键 → DimCustomer | | - | InvoiceAmount | decimal(18,4) | Y | | 开票金额 | | -| ContractAmount | decimal(18,4) | Y | | 合同金额 | | - | InvoiceDateUTC | datetime2 | Y | | 开票日期(UTC) | | - | ActualInvoiceDateUTC | datetime2 | Y | | 开票日期(UTC) | | -| InvoiceType | varchar(50) | Y | | 开票类型 | | -| Stage | varchar(50) | Y | | 当前阶段 | | -| Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 开票事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactWriteOff(核销事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| WriteOffId | bigint | N | autoincr | 主键 | | - | WriteOffNumber | varchar(64) | N | | 核销单编号(退化维度) | 来源:核销单编号 | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | - | HandlerId | bigint | Y | | 经办人 (外键 → DimPerson) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | WriteOffType | varchar(30) | Y | | 核销类型 | AR,AP | - | CurrencyCode | varchar(16) | Y | | 币别 | | - | FxRate | decimal(18,6) | Y | | 汇率 | | - | DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | - | WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | - | EventTimeUTC | datetime2 | N | | 核销事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactCashFlow(资金流水事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| CashFlowId | bigint | N | autoincr | 主键 | | - | DocNumber | varchar(64) | Y | | 单据编号(退化维度) | 来源:资金流向.单据编号/转账单编号 | - | DocType | varchar(30) | N | | 单据类型 | CASHFLOW,TRANSFER | - | AccountName | varchar(255) | Y | | 账户名称 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | BusinessType | varchar(100) | Y | | 业务类型 | | - | IncomeAmount | decimal(18,4) | Y | | 收入 | | - | ExpenseAmount | decimal(18,4) | Y | | 支出 | | - | BalanceAmount | decimal(18,4) | Y | | 余额 | | - | Counterparty | varchar(255) | Y | | 往来单位 | | - | Remark | varchar(500) | Y | | 单据备注 | | - | EventTimeUTC | datetime2 | N | | 资金事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactCommission(分佣事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| CommissionId | bigint | N | autoincr | 主键 | | - | CommissionPerson | varchar(100) | N | | 分佣人 | | - | Department | varchar(100) | Y | | 部门 | | - | CommissionRatePct | decimal(9,4) | Y | | 提成比例(%) | | - | Coefficient | decimal(18,6) | Y | | 系数 | | - | ReceivableCommission | decimal(18,4) | Y | | 应收佣金 | | - | ReceivedCommission | decimal(18,4) | Y | | 实收佣金 | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | ProductName | varchar(255) | Y | | 产品名称(退化) | | - | OpportunityName | varchar(255) | Y | | 商机名称(退化) | | - | ContractAmount | decimal(18,4) | Y | | 合同金额 | | - | DealTimeUTC | datetime2 | Y | | 成交时间(UTC) | | - | IsInvoiced | bit | Y | | 是否开票 | 0,1 | - | EventTimeUTC | datetime2 | N | | 分佣事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactLead(线索事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| LeadId | bigint | N | autoincr | 主键 | | - | LeadName | varchar(255) | N | | 线索名称 | | - | OwnerId | bigint | Y | | 负责人 (外键 → DimPerson) | | - | LeadSource | varchar(100) | Y | | 线索来源 | | -| Phone | varchar(32) | Y | | 手机 | | -| Telephone | varchar(32) | Y | | 电话 | | -| Email | varchar(100) | Y | | 邮箱 | | -| Address | varchar(500) | Y | | 地址 | | -| Industry | varchar(100) | Y | | 客户行业 | | -| CustomerLevel | varchar(50) | Y | | 客户级别 | | -| NextContactTimeUTC | datetime2 | Y | | 下次联系时间(UTC) | | -| FollowUpStatus | varchar(50) | Y | | 跟进状态 | | -| IsConverted | bit | Y | | 是否已转化 | 0,1 | -| Stage | varchar(50) | Y | | 当前阶段 | | -| Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 线索事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactOpportunity(商机事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| OpportunityId | bigint | N | autoincr | 主键 | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | OpportunityAmount | decimal(18,4) | Y | | 商机金额 | | - | ExpectedDealDateUTC | datetime2 | Y | | 预计成交日期(UTC) | | -| NextContactTimeUTC | datetime2 | Y | | 下次联系时间(UTC) | | -| StatusGroup | varchar(50) | Y | | 商机状态组 | | -| Stage | varchar(50) | Y | | 当前阶段 | | - | Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 商机事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactProductionPlan(生产计划_装配工单汇总事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ProductionPlanId | bigint | N | autoincr | 主键 | | - | AssemblyWorkOrderNumber | varchar(64) | N | | 装配工单编号(退化维度) | 来源:装配工单编号 | - | WorkOrderCount | int | Y | | 工单数 | | - | FinishedCount | int | Y | | 已结束数 | | - | PlannedQty | decimal(18,4) | Y | | 计划数 | | - | CompletedQty | decimal(18,4) | Y | | 完工数 | | - | PlanStatus | varchar(50) | Y | | 生产计划状态 | | - | ProgressText | varchar(100) | Y | | 工单生成/用料生成/单据进度(原文) | | - | CustomerName | varchar(255) | Y | | 客户(退化) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | - | SalesOrderId | bigint | Y | | 关联FactSalesOrder (外键 → FactSalesOrder) | | - | EventTimeUTC | datetime2 | N | | 计划事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -## KPI 映射(示例) - -| KPI | 口径 | 表/字段 | -|---|---|---| -| 报工良率 | `sum(GoodQty) / nullif(sum(ReportQty),0)` | `FactLaborReport.GoodQty/ReportQty` | -| 质检良率 | `sum(PassQty) / nullif(sum(PassQty+FailQty),0)` | `FactQualityInspection.PassQty/FailQty` | -| 采购未付款 | `sum(UnpaidAmount)` | `FactPurchaseReceipt.UnpaidAmount` | -| AR 未核销 | `sum(UnwrittenOffAmount)` | `FactArReceipt.UnwrittenOffAmount` | -| AP 未核销 | `sum(UnwrittenOffAmount)` | `FactApPayment.UnwrittenOffAmount` | - - -## 数据字典 - -以下枚举值仅供参考,实际实施时应根据业务系统配置进行调整。 - -### 1. 通用类 (Common) - -| 字典代码 | 字典名称 | 枚举值 (Code: Name) | 适用字段 | -|---|---|---|---| -| **CurrencyCode** | 币种 | CNY: 人民币
USD: 美元
EUR: 欧元
JPY: 日元
HKD: 港币 | `FactSalesOrder.CurrencyCode`
`FactPurchaseOrder.CurrencyCode`
`FactArReceipt.CurrencyCode`
... | -| **UomCode** | 计量单位 | pcs: 个/件
kg: 千克
m: 米
L: 升
box: 箱
set: 套 | `DimProduct.UomCode`
`DimBom.UomCode`
`FactInventoryMovement.UomCode` | -| **Boolean** | 布尔标识 | 0: 否 (No/False)
1: 是 (Yes/True) | `IsCurrent`, `IsLocked`, `IsPrimary`, `IsReship`, `IsConverted`, `IsInvoiced` | - -### 2. 人员与组织 (Person & Org) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **PersonStatus** | 人员状态 | ACTIVE: 在职
INACTIVE: 离职
LEAVE: 休假 | `DimPerson.Status` | -| **Department** | 部门 | SALES: 销售部
MFG: 生产部
PUR: 采购部
WH: 仓储部
FIN: 财务部
QC: 质检部 | `DimPerson.Department` | -| **SupplierLevel** | 供应商等级 | STRATEGIC: 战略供应商
PREFERRED: 优选供应商
APPROVED: 合格供应商
PROBATION: 考察供应商
EXIT: 淘汰供应商 | `DimSupplier.SupplierLevel` | -| **CustomerLevel** | 客户等级 | KA: 重点客户
VIP: 重要客户
NORMAL: 普通客户
POTENTIAL: 潜在客户 | `DimCustomer.CustomerLevel`
`FactLead.CustomerLevel` | -| **Industry** | 行业 | MFG: 制造
RETAIL: 零售
IT: 互联网
FIN: 金融
EDU: 教育 | `DimCustomer.Industry`
`FactLead.Industry` | - -### 3. 生产与库存 (Production & Inventory) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **WorkOrderStatus** | 工单状态 | PLANNED: 计划中
RELEASED: 已下达
STARTED: 生产中
PAUSED: 暂停
COMPLETED: 已完工
CLOSED: 已结案
SCRAP: 报废 | `FactWorkOrder.Status` | -| **MovementType** | 出入库类型 | IN_PURCHASE: 采购入库
IN_FG: 完工入库
IN_RETURN: 销售退货入库
IN_OTHER: 其他入库
OUT_SALES: 销售出库
OUT_MATERIAL: 生产领料
OUT_RETURN: 采购退货出库
OUT_SCRAP: 报废出库
TRANSFER: 调拨 | `FactInventoryMovement.DocType` | -| **WarehouseType** | 仓库类型 | RAW: 原材料仓
FG: 成品仓
WIP: 线边仓/半成品仓
SCRAP: 废品仓
VMI: 供应商库存仓 | `DimWarehouse.WarehouseType` | -| **QcType** | 质检类型 | IQC: 来料检验
IPQC: 制程检验
FQC: 终检
OQC: 出货检验 | `FactQualityInspection.QcType` | -| **ProductType** | 产品类型 | FINISHED: 成品
SEMI: 半成品
RAW: 原材料
SERVICE: 服务 | `DimProduct.ProductType` | -| **TransportCondition** | 运输条件 | NORMAL: 常温
COLD: 冷链
FRAGILE: 易碎
HAZARDOUS: 危险品 | `DimProduct.TransportCondition` | -| **PlanStatus** | 计划状态 | DRAFT: 草稿
CONFIRMED: 已确认
EXECUTING: 执行中
COMPLETED: 已完成 | `FactProductionPlan.PlanStatus` | - -### 4. 销售与采购 (Sales & Purchase) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **LeadSource** | 线索来源 | WEB: 官网
REFERRAL: 推荐
EXHIBITION: 展会
COLD_CALL: 陌拜
OTHER: 其他 | `FactLead.LeadSource` | -| **SalesStage** | 销售阶段 | NEW: 新建
QUOTED: 已报价
NEGOTIATING: 谈判中
WON: 赢单
LOST: 输单 | `FactOpportunity.Stage`
`FactLead.Stage` | -| **OppStatusGroup** | 商机状态组 | OPEN: 开启
WON: 赢单
LOST: 输单 | `FactOpportunity.StatusGroup` | -| **PurchaseStatus** | 采购状态 | DRAFT: 草稿
APPROVED: 已审批
SENT: 已发送
PARTIAL_RECEIVED: 部分收货
RECEIVED: 全部收货
CLOSED: 关闭 | `FactPurchaseRequest.Status` | -| **PaymentStatus** | 支付状态 | UNPAID: 未付款
PARTIAL: 部分付款
PAID: 已付款
OVERDUE: 逾期 | `FactSalesOrder.PaymentStatus`
`FactPurchaseReceipt.PaymentStatus` | -| **TransportMode** | 运输方式 | LAND: 陆运
SEA: 海运
AIR: 空运
RAIL: 铁运
EXPRESS: 快递 | `FactSalesOrder.TransportMode`
`FactSalesShipment.TransportMode` | - -### 5. 财务 (Finance) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **FinDocType** | 财务单据类型 | RECEIPT: 收款单
PAYMENT: 付款单
INVOICE: 发票
REFUND: 退款
ADVANCE: 预收/预付
WRITEOFF: 核销 | `FactArReceipt.DocType`
`FactApPayment.DocType` | -| **WriteOffType** | 核销类型 | AR: 应收核销
AP: 应付核销 | `FactWriteOff.WriteOffType` | -| **InvoiceType** | 发票类型 | VAT_SPECIAL: 增值税专票
VAT_NORMAL: 增值税普票
ELECTRONIC: 电子发票 | `FactInvoice.InvoiceType` | - - -## 变更记录 - -| 版本 | 日期 | 作者 | 变更描述 | 脚本影响 | -|---|---|---|---|---| -| v1.0.0 | 2025-12-25 | GPT-5.2 | 首次输出:维度SCD2 + 事实星座 + ER图 + 字段清单 | 未生成 | -| v1.1.0 | 2025-12-28 | Trae | 新增 DimPerson(人员维度)并关联销售、采购、生产、审批等事实表 | 待生成 | -| v1.2.0 | 2025-12-29 | Trae | 新增「业务数据流转全景图」,包含商机-订单-计划-生产-交付全链路 Mermaid 流程图 | 无 | -| v1.3.0 | 2025-12-29 | Trae | 升级全景图,新增外部采购闭环(PO管理、入库验收IQC、三单匹配AP、供应商管理) | 无 | -| v1.4.0 | 2025-12-29 | Trae | 升级全景图,新增财务与资金阶段(开票Invoice、收付款AP/AR、核销WriteOff、资金CashFlow) | 无 | -| v1.5.0 | 2025-12-30 | Trae | 新增「数据字典」章节,规范化通用类、人员组织、生产库存、销售采购及财务类枚举值 | 无 | diff --git a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml b/lzwcai_mcpskills_mfg_data_agent/pyproject.toml deleted file mode 100644 index 2b6d240..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcpskills-mfg-data-agent" -version = "0.1.6" -description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcpskills-mfg-data-agent = "lzwcai_mcpskills_mfg_data_agent.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_mfg_data_agent"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcpskills_mfg_data_agent/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agent/businessQueries.json" diff --git a/lzwcai_mcpskills_mfg_data_agent/sql/一页式决策简报.sql b/lzwcai_mcpskills_mfg_data_agent/sql/一页式决策简报.sql deleted file mode 100644 index 156d3f6..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/sql/一页式决策简报.sql +++ /dev/null @@ -1,317 +0,0 @@ --- ===================================================== --- 一页式决策简报SQL --- One-Page Decision Brief --- 数据库: PostgreSQL --- ===================================================== - --- ===================================================== --- 主查询:一页式决策简报 --- ===================================================== -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 AS invoice_count, - ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, - - -- 物流板块 - sh.shipment_count AS shipment_count, - ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, - pur.purchase_order_count AS purchase_order_count, - - -- 售后板块 - ret.return_count AS 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; - - --- ===================================================== --- 补充查询1:本月 vs 上月对比分析 --- ===================================================== -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; - - --- ===================================================== --- 补充查询2:客户贡献度TOP10 --- ===================================================== -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; - - --- ===================================================== --- 补充查询3:产品类别业绩分析 --- ===================================================== -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; - - --- ===================================================== --- 补充查询4:关键预警指标 --- ===================================================== -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; - - --- ===================================================== --- 补充查询5:月度趋势汇总 --- ===================================================== -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; diff --git a/lzwcai_mcpskills_mfg_data_agent/sql/人效产值损耗三维模型仪表盘.sql b/lzwcai_mcpskills_mfg_data_agent/sql/人效产值损耗三维模型仪表盘.sql deleted file mode 100644 index b6f98b9..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/sql/人效产值损耗三维模型仪表盘.sql +++ /dev/null @@ -1,193 +0,0 @@ --- ===================================================== --- 人效-产值-损耗三维模型仪表盘SQL --- Efficiency-Output-Loss 3D Model Dashboard --- 数据库: PostgreSQL --- 维度: 产品类别(化工/机械/电子)作为部门维度 --- ===================================================== - --- 1. 部门级综合仪表盘(主查询) -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; - - --- ===================================================== --- 2. 人员级明细报表 --- ===================================================== -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; - - --- ===================================================== --- 3. 月度趋势分析 --- ===================================================== -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; - - --- ===================================================== --- 4. 产品级损耗分析 --- ===================================================== -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; - - --- ===================================================== --- 5. 工单完成率分析 --- ===================================================== -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; diff --git a/lzwcai_mcpskills_mfg_data_agent/sql/供应链风险预警.sql b/lzwcai_mcpskills_mfg_data_agent/sql/供应链风险预警.sql deleted file mode 100644 index 857c7ee..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/sql/供应链风险预警.sql +++ /dev/null @@ -1,416 +0,0 @@ --- ===================================================== --- 供应链风险预警SQL --- Supply Chain Risk Warning --- 数据库: PostgreSQL --- ===================================================== - --- ===================================================== --- 1. 供应商历史交期表现分析 --- ===================================================== -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; - - --- ===================================================== --- 补充查询1:高风险订单预警清单 --- ===================================================== -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; - - --- ===================================================== --- 补充查询2:供应商类别风险分布 --- ===================================================== -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; - - --- ===================================================== --- 补充查询3:近期交期异常趋势 --- ===================================================== -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; - - --- ===================================================== --- 补充查询4:风险预警汇总看板 --- ===================================================== -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; - - --- ===================================================== --- 补充查询5:供应商风险排行榜 --- ===================================================== -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; diff --git a/lzwcai_mcpskills_mfg_data_agent/sql/工单执行进度与异常节点.sql b/lzwcai_mcpskills_mfg_data_agent/sql/工单执行进度与异常节点.sql deleted file mode 100644 index f7d3770..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/sql/工单执行进度与异常节点.sql +++ /dev/null @@ -1,409 +0,0 @@ --- ===================================================== --- 工单执行进度与异常节点SQL --- 数据库: PostgreSQL --- 实时拉取工单数据,动态映射订单各环节状态 --- ===================================================== - --- ===================================================== --- 1. 工单执行进度主视图 --- ===================================================== -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 -), - --- ===================================================== --- 2. 工单进度计算与状态映射 --- ===================================================== -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 '待生产' - WHEN 'STARTED' THEN '生产中' - WHEN 'CLOSED' THEN '已完成' - ELSE '未知' - 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 -), - --- ===================================================== --- 3. 异常节点检测 --- ===================================================== -anomaly_detection AS ( - SELECT - *, - - -- 进度异常:进行中但完成率过低 - CASE - WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN '进度严重滞后' - WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' - 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 '质量异常' - 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 '报工停滞' - WHEN raw_status = 'STARTED' - AND last_report_time IS NULL - AND elapsed_hours > 24 THEN '无报工记录' - ELSE NULL - END AS labor_anomaly, - - -- 效率异常:人效过低 - CASE - WHEN total_work_minutes > 0 - AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' - ELSE NULL - END AS efficiency_anomaly - - FROM work_order_progress -) - --- ===================================================== --- 输出:工单执行进度明细 --- ===================================================== -SELECT - work_order_number AS "工单号", - product_name AS "产品名称", - product_category AS "产品类别", - status_name AS "状态", - planned_qty AS "计划数量", - completed_qty AS "完成数量", - remaining_qty AS "剩余数量", - completion_rate AS "完成率(%)", - worker_count AS "参与人数", - ROUND(total_work_minutes / 60.0, 1) AS "累计工时(小时)", - elapsed_hours AS "已用时间(小时)", - - -- 异常标记 - 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 "异常标记", - - -- 风险等级 - CASE - WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' - WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' - WHEN efficiency_anomaly IS NOT NULL THEN '低' - ELSE '-' - END AS "风险等级" - -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; - - --- ===================================================== --- 补充查询1:工单状态分布汇总 --- ===================================================== -WITH status_summary AS ( - SELECT - CASE status - WHEN 'OPEN' THEN '待生产' - WHEN 'STARTED' THEN '生产中' - WHEN 'CLOSED' THEN '已完成' - ELSE '未知' - 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 AS "状态", - order_count AS "工单数", - ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS "占比(%)", - ROUND(total_planned, 0) AS "计划总量", - ROUND(total_completed, 0) AS "完成总量", - CASE WHEN total_planned > 0 - THEN ROUND(total_completed * 100.0 / total_planned, 1) - ELSE 0 END AS "完成率(%)" -FROM status_summary -ORDER BY - CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END; - - --- ===================================================== --- 补充查询2:异常工单预警清单 --- ===================================================== -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 AS "工单号", - product_name AS "产品", - ROUND(planned_qty, 0) AS "计划数量", - ROUND(completed_qty, 0) AS "完成数量", - completion_rate AS "完成率(%)", - elapsed_hours AS "已用时间(小时)", - CASE - WHEN elapsed_hours > 48 AND completion_rate < 20 THEN '进度严重滞后' - WHEN elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' - WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' - WHEN last_report_time IS NOT NULL - AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '报工停滞' - ELSE '正常' - END AS "异常类型", - CASE - WHEN elapsed_hours > 48 AND completion_rate < 20 THEN '立即跟进,排查生产瓶颈' - WHEN elapsed_hours > 24 AND completion_rate < 30 THEN '关注进度,协调资源' - WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN '确认工单是否已开工' - WHEN last_report_time IS NOT NULL - AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '跟进报工情况' - ELSE '-' - END AS "处理建议" -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; - - --- ===================================================== --- 补充查询3:产品类别执行进度汇总 --- ===================================================== -SELECT - p.product_category AS "产品类别", - COUNT(*) AS "工单数", - SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS "待生产", - SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS "生产中", - SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS "已完成", - ROUND(SUM(wo.planned_qty), 0) AS "计划总量", - ROUND(SUM(wo.completed_qty), 0) AS "完成总量", - CASE WHEN SUM(wo.planned_qty) > 0 - THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1) - ELSE 0 END AS "整体完成率(%)" -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 "工单数" DESC; - - --- ===================================================== --- 补充查询4:工单执行时间线 --- ===================================================== -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 AS "工单号", - product_name AS "产品", - CASE status - WHEN 'OPEN' THEN '待生产' - WHEN 'STARTED' THEN '生产中' - WHEN 'CLOSED' THEN '已完成' - END AS "状态", - TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS "开始时间", - TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS "最后更新", - duration_hours AS "持续时间(小时)", - ROUND(planned_qty, 0) AS "计划数量", - ROUND(completed_qty, 0) AS "完成数量", - CASE WHEN planned_qty > 0 - THEN ROUND(completed_qty * 100.0 / planned_qty, 1) - ELSE 0 END AS "完成率(%)" -FROM timeline -ORDER BY start_time DESC -LIMIT 50; - - --- ===================================================== --- 补充查询5:实时生产看板汇总 --- ===================================================== -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 - '总工单数' AS "指标", - cs.total_orders::text AS "数值", - '-' AS "状态" -FROM current_stats cs -UNION ALL -SELECT - '待生产', - cs.open_orders::text, - CASE WHEN cs.open_orders > 20 THEN '积压' ELSE '正常' END -FROM current_stats cs -UNION ALL -SELECT - '生产中', - cs.started_orders::text, - '进行中' -FROM current_stats cs -UNION ALL -SELECT - '已完成', - cs.closed_orders::text, - '正常' -FROM current_stats cs -UNION ALL -SELECT - '整体完成率', - 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 '良好' - WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 50 THEN '正常' - ELSE '偏低' - END -FROM current_stats cs -UNION ALL -SELECT - '异常工单数', - ans.anomaly_count::text, - CASE WHEN ans.anomaly_count > 5 THEN '需关注' ELSE '正常' END -FROM anomaly_stats ans -UNION ALL -SELECT - '今日完成', - ts.today_completed::text, - '-' -FROM today_stats ts; diff --git a/lzwcai_mcpskills_mfg_data_agent/sql/指标趋势分析与拐点预警.sql b/lzwcai_mcpskills_mfg_data_agent/sql/指标趋势分析与拐点预警.sql deleted file mode 100644 index 45f978d..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/sql/指标趋势分析与拐点预警.sql +++ /dev/null @@ -1,426 +0,0 @@ --- ===================================================== --- 指标趋势分析与拐点预警SQL --- Metric Trend Analysis and Turning Point Warning --- 数据库: PostgreSQL --- ===================================================== - --- ===================================================== --- 1. 日度指标基础数据 --- ===================================================== -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 -), - --- ===================================================== --- 2. 移动平均计算 (7日移动平均) --- ===================================================== -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 -), - --- ===================================================== --- 3. 简化的趋势斜率计算 --- ===================================================== -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 -), - --- ===================================================== --- 4. 趋势判断与异常检测 --- ===================================================== -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; - - --- ===================================================== --- 补充查询1:周度趋势汇总 --- ===================================================== -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; - - --- ===================================================== --- 补充查询2:指标趋势汇总报告 --- ===================================================== -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; - - --- ===================================================== --- 补充查询3:拐点预警汇总 --- ===================================================== -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; diff --git a/lzwcai_mcpskills_mfg_data_agent/sql/订单延迟预警分析.sql b/lzwcai_mcpskills_mfg_data_agent/sql/订单延迟预警分析.sql deleted file mode 100644 index 77fc5b5..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/sql/订单延迟预警分析.sql +++ /dev/null @@ -1,154 +0,0 @@ --- 订单延迟预警分析(最终优化版) --- Order Delay Warning Analysis -WITH --- 1. 历史生产周期统计(全局平均,仅统计已完成工单) -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 -), - --- 2. 物流延误统计(按客户维度) -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 -), - --- 3. 质量问题统计(全局平均) -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 -), - --- 4. 报废率(全局) -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 -), - --- 5. 当前生产滞后风险(全局) -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') -), - --- 6. 合并全局指标为单行 -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 AS customer_name, - so.order_date_utc::DATE AS order_date, - so.deal_amount AS order_amount, - so.payment_status AS 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; diff --git a/lzwcai_mcpskills_mfg_data_agent/test.py b/lzwcai_mcpskills_mfg_data_agent/test.py deleted file mode 100644 index 25ca3b2..0000000 --- a/lzwcai_mcpskills_mfg_data_agent/test.py +++ /dev/null @@ -1,111 +0,0 @@ -import requests -import json - -def main(**kwargs) -> dict: - """ - 函数节点的入口函数 - - Args: - **kwargs: 从前端配置的参数传入,可通过变量引用获取工作流上下文 - - Returns: - dict: 返回结果将作为节点输出,可被后续节点引用 - """ - # 从 kwargs 获取参数,如果没有提供则使用默认值 - url = kwargs.get('url', 'http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema') - - # 请求头(从kwargs获取或使用默认值) - authorization_token = kwargs.get('authorization_token', 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjAxNzZiNjEyLTc2YWItNDdhMS1iYTRiLTdjNWU2ZTMxNDlmZCJ9.w7aOfDJDHtA4bwNKIvUVK2cf1yO_2F27d_eYuos-p1-XGrSQOX0D4ny0b8Js36MhXwBnF4GDcy8V1VobEN6zBA') - headers = { - 'Authorization': authorization_token - } - - # 请求体参数(从kwargs获取或使用默认值) - payload_id = kwargs.get('id', '2006300000000000001') - business_name = kwargs.get('businessName', 'OrderDelayWarningAnalysis') - business_description = kwargs.get('businessDescription', '订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级') - datasource_id = kwargs.get('datasourceId', '57') - sql_template = kwargs.get('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) 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 = kwargs.get('parameters', {}) - - # 请求体(从kwargs获取或使用默认值) - payload = { - "id": payload_id, - "businessName": business_name, - "businessDescription": business_description, - "datasourceId": datasource_id, - "sqlTemplate": sql_template, - "parameters": parameters - } - - # 超时时间(从kwargs获取或使用默认值) - timeout = kwargs.get('timeout', 30) - - try: - # 发送POST请求 - response = requests.post( - url=url, - headers=headers, - json=payload, - timeout=timeout - ) - - # 检查HTTP响应状态码 - response.raise_for_status() - - # 解析响应结果 - result = response.json() - - # 返回接口调用结果 - return { - "status": "success", - "status_code": response.status_code, - "result": result - } - - except requests.exceptions.Timeout: - return { - "status": "error", - "error_type": "timeout", - "message": f"接口调用超时({timeout}秒)", - "result": None - } - - except requests.exceptions.ConnectionError: - return { - "status": "error", - "error_type": "connection_error", - "message": "无法连接到目标服务器", - "result": None - } - - except requests.exceptions.HTTPError as e: - return { - "status": "error", - "error_type": "http_error", - "status_code": response.status_code if 'response' in locals() else None, - "message": f"HTTP请求错误: {str(e)}", - "response_text": response.text if 'response' in locals() else "", - "result": None - } - - except json.JSONDecodeError: - return { - "status": "error", - "error_type": "json_decode_error", - "message": "接口返回的不是有效的JSON格式", - "response_text": response.text if 'response' in locals() else "", - "result": None - } - - except Exception as e: - return { - "status": "error", - "error_type": "unknown_error", - "message": f"接口调用失败: {str(e)}", - "result": None - } - -if __name__ == "__main__": - result = main() - print(json.dumps(result, ensure_ascii=False, indent=2)) \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/README.md b/lzwcai_mcpskills_mfg_data_agentv2/README.md deleted file mode 100644 index 46ac7be..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/README.md +++ /dev/null @@ -1,138 +0,0 @@ -# lzwcai-mcpskills-mfg-data-agent (制造业数据智能体) - -一个基于 MCP (Model Context Protocol) 的制造业数据智能体服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录 -- 🌐 中文支持:工具名称自动转换为拼音 -- 🏭 制造业场景:专注于制造业数据分析与智能决策 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcpskills-mfg-data-agent -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcpskills_mfg_data_agent -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcpskills-mfg-data-agent -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcpskills-mfg-data-agent -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcpskills_mfg_data_agent.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "mfg-data-agent": { - "command": "lzwcai-mcpskills-mfg-data-agent" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "生产订单查询", - "businessDescription": "根据订单ID查询生产订单信息", - "sqlTemplate": "SELECT * FROM production_orders WHERE order_id = {{orderId}}", - "parameters": { - "type": "object", - "required": ["orderId"], - "properties": { - "orderId": { - "type": "string", - "description": "生产订单的唯一标识符", - "examples": ["PO-2024-001"] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcpskills_mfg_data_agent - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcpskills_mfg_data_agent.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 许可证 - -MIT License - -## 作者 - -lzwcai diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore deleted file mode 100644 index 505a3b1..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Python-generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info - -# Virtual environments -.venv diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md deleted file mode 100644 index 0ec9671..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# lzwcai-mcpskills-analyzeWorkOrder - -一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。 - -## 功能特性 - -- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 -- 🔧 灵活配置:支持自定义业务查询和参数验证 -- 📝 完整日志:详细的操作日志记录(仅输出到文件,不干扰MCP通信) -- 🌐 中文支持:工具名称自动转换为拼音 - -## 安装 - -### 使用 pip 安装 - -```bash -pip install lzwcai-mcpskills-analyzeWorkOrder -``` - -### 从源码安装 - -```bash -git clone -cd lzwcai_mcp_sqlexecutor -pip install -e . -``` - -### 使用 uv 安装(推荐) - -```bash -uv pip install lzwcai-mcpskills-analyzeWorkOrder -``` - -## 使用方法 - -### 命令行启动 - -安装后,可以直接通过命令启动: - -```bash -lzwcai-mcpskills-analyzeWorkOrder -``` - -### 作为 Python 模块运行 - -```bash -python -m lzwcai_mcp_sqlexecutor.main -``` - -### 配置到 MCP 客户端 - -在你的 MCP 客户端配置文件中添加: - -```json -{ - "mcpServers": { - "lzwcai-sqlexecutor": { - "command": "lzwcai-mcpskills-analyzeWorkOrder" - } - } -} -``` - -## 配置说明 - -### businessQueries.json - -在 `businessQueries.json` 中定义你的业务查询: - -```json -[ - { - "id": "query-001", - "businessName": "用户订单查询", - "businessDescription": "根据用户ID查询订单信息", - "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", - "parameters": { - "type": "object", - "required": ["userId"], - "properties": { - "userId": { - "type": "integer", - "description": "用户的唯一标识符", - "examples": [10086] - } - } - } - } -] -``` - -## 开发 - -### 依赖项 - -- Python >= 3.13 -- httpx >= 0.28.1 -- mcp[cli] >= 1.10.1 -- pypinyin >= 0.53.0 - -### 本地开发 - -```bash -# 克隆仓库 -git clone -cd lzwcai_mcp_sqlexecutor - -# 安装开发依赖 -pip install -e . - -# 运行服务器 -python -m lzwcai_mcp_sqlexecutor.main -``` - -## 构建与发布 - -### 使用 build 构建 - -```bash -pip install build -python -m build -``` - -### 发布到 PyPI - -```bash -pip install twine -twine upload dist/* -``` - -## 常见问题 - -### MCP Inspector 显示 JSON 解析错误 - -如果在使用 MCP Inspector 测试时遇到 `SyntaxError: Unexpected non-whitespace character after JSON` 错误,这是因为: - -1. **原因**:MCP 协议使用 stdio(标准输入输出)进行 JSON-RPC 通信,任何输出到 stdout 的内容(如 print 语句或控制台日志)都会破坏 JSON 格式。 - -2. **解决方案**:本服务器已将所有日志输出配置为仅写入文件(位于 `logs/` 目录),不输出到控制台。日志文件包括: - - `lzwcai_mcp_sqlexecutor.log` - 主日志文件 - - `lzwcai_mcp_sqlexecutor_error.log` - 错误日志 - - `lzwcai_mcp_sqlexecutor_daily.log` - 按日期滚动的日志 - - `mcp_services.log` - MCP 服务专用日志 - -3. **查看日志**:如果需要调试,请查看 `logs/` 目录下的日志文件。 - -## 许可证 - -MIT License - -## 作者 - -lzwcai - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py deleted file mode 100644 index 790ee8d..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -lzwcai-mcpskills-mfg-data-agentv2 - MCP server for manufacturing data intelligence -""" - -__version__ = "0.1.2" -__author__ = "lzwcai" - -__all__ = [] - 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 deleted file mode 100644 index b1c6f8e..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "id": "2006300000000000002", - "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 = '标准产品' LIMIT 15 ; WITH lead_time AS ( SELECT po.supplier_id, (DATE(pr.doc_date_utc) - DATE(po.doc_date_utc)) AS lead_days, CASE WHEN po.delivery_date_utc IS NOT NULL AND pr.doc_date_utc IS NOT NULL THEN CASE WHEN DATE(pr.doc_date_utc) <= DATE(po.delivery_date_utc) THEN 1 ELSE 0 END ELSE NULL END AS is_ontime FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL ), quality AS ( SELECT supplier_id, SUM(COALESCE(pass_qty, 0)) AS pass_qty, SUM(COALESCE(fail_qty, 0)) AS fail_qty FROM fact_quality_inspection WHERE qc_type = 'INCOMING' AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY supplier_id ), agg AS ( SELECT supplier_id, AVG(lead_days) AS avg_lead_days, STDDEV_POP(lead_days) AS std_lead_days, AVG(is_ontime) AS ontime_rate FROM lead_time WHERE lead_days IS NOT NULL AND lead_days >= 0 GROUP BY supplier_id ) SELECT s.supplier_id, s.supplier_name, a.avg_lead_days, a.std_lead_days, a.ontime_rate, CASE WHEN (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) > 0 THEN COALESCE(q.pass_qty, 0) / (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) ELSE NULL END AS incoming_pass_rate FROM agg a LEFT JOIN dim_supplier s ON s.supplier_id = a.supplier_id AND s.is_current = TRUE LEFT JOIN quality q ON q.supplier_id = a.supplier_id ORDER BY s.supplier_name LIMIT 15 ; WITH params AS ( SELECT 1.65::numeric AS z, 7::int AS review_days ), consume_daily AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), consume_stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM consume_daily GROUP BY product_id ), lead_time AS ( SELECT pr.supplier_id, AVG((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS avg_lead_days, STDDEV_POP((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS std_lead_days FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL GROUP BY pr.supplier_id ), latest_onhand_row AS ( SELECT m.product_id, m.warehouse_id, m.onhand_qty_after, ROW_NUMBER() OVER (PARTITION BY m.product_id, m.warehouse_id ORDER BY m.event_time_utc DESC, m.inventory_movement_id DESC) AS rn FROM fact_inventory_movement m WHERE m.product_id IS NOT NULL ), onhand AS ( SELECT product_id, SUM(COALESCE(onhand_qty_after, 0)) AS onhand_qty FROM latest_onhand_row WHERE rn = 1 GROUP BY product_id ), product_supplier AS ( SELECT p.product_id, p.product_code, p.product_name, b.supplier_name FROM dim_product p LEFT JOIN dim_bom b ON b.product_code = p.product_code AND b.is_current = TRUE WHERE p.is_current = TRUE AND p.product_type = '标准产品' ), supplier_id_map AS ( SELECT supplier_id, supplier_name FROM dim_supplier WHERE is_current = TRUE ), merged AS ( SELECT ps.product_id, ps.product_code, ps.product_name, sm.supplier_id, COALESCE(cs.avg_daily_consume, 0) AS avg_daily_consume, COALESCE(cs.std_daily_consume, 0) AS std_daily_consume, COALESCE(lt.avg_lead_days, 0) AS avg_lead_days, COALESCE(lt.std_lead_days, 0) AS std_lead_days, COALESCE(oh.onhand_qty, 0) AS onhand_qty FROM product_supplier ps LEFT JOIN supplier_id_map sm ON sm.supplier_name IS NOT DISTINCT FROM ps.supplier_name LEFT JOIN consume_stats cs ON cs.product_id = ps.product_id LEFT JOIN lead_time lt ON lt.supplier_id = sm.supplier_id LEFT JOIN onhand oh ON oh.product_id = ps.product_id ) SELECT product_code, product_name, onhand_qty, avg_daily_consume, std_daily_consume, avg_lead_days, (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS safety_stock_qty, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS reorder_point_qty, GREATEST( 0, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) - onhand_qty ) AS suggest_order_qty, supplier_id FROM merged CROSS JOIN params ORDER BY suggest_order_qty DESC, product_code LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000003", - "businessName": "SalesBIIntelligentAnalyticsPlatform", - "businessDescription": "销售BI智能分析平台:面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察。", - "datasourceId": "19", - "sqlTemplate": "SELECT DATE(so.order_date_utc) AS order_date, so.customer_id, c.customer_name, c.customer_level, c.industry, c.address, so.sales_person_id, p.person_name AS sales_person_name, COUNT(DISTINCT so.sales_order_id) AS order_count, SUM(COALESCE(so.deal_amount, 0)) AS order_amount FROM fact_sales_order so LEFT JOIN dim_customer c ON c.customer_id = so.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = so.sales_person_id AND p.is_current = TRUE GROUP BY DATE(so.order_date_utc), so.customer_id, c.customer_name, c.customer_level, c.industry, c.address, so.sales_person_id, p.person_name LIMIT 15 ; WITH ship AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, customer_id, sales_person_id, SUM(COALESCE(amount, 0)) AS ship_amount FROM fact_sales_shipment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM'), customer_id, sales_person_id ), ret AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, customer_id, sales_person_id, SUM(COALESCE(amount, 0)) AS return_amount FROM fact_sales_return WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM'), customer_id, sales_person_id ), keys AS ( SELECT ym, customer_id, sales_person_id FROM ship UNION SELECT ym, customer_id, sales_person_id FROM ret ) SELECT k.ym, k.customer_id, c.customer_name, k.sales_person_id, p.person_name AS sales_person_name, COALESCE(s.ship_amount, 0) AS ship_amount, COALESCE(r.return_amount, 0) AS return_amount, (COALESCE(s.ship_amount, 0) - COALESCE(r.return_amount, 0)) AS net_amount FROM keys k LEFT JOIN ship s ON s.ym = k.ym AND s.customer_id IS NOT DISTINCT FROM k.customer_id AND s.sales_person_id IS NOT DISTINCT FROM k.sales_person_id LEFT JOIN ret r ON r.ym = k.ym AND r.customer_id IS NOT DISTINCT FROM k.customer_id AND r.sales_person_id IS NOT DISTINCT FROM k.sales_person_id LEFT JOIN dim_customer c ON c.customer_id = k.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = k.sales_person_id AND p.is_current = TRUE LIMIT 15 ; WITH orders AS ( SELECT so.sales_person_id, so.customer_id, COUNT(DISTINCT so.sales_order_id) AS order_cnt FROM fact_sales_order so WHERE so.order_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.sales_person_id, so.customer_id ), rate AS ( SELECT sales_person_id, COUNT(DISTINCT customer_id) AS customer_cnt, SUM(CASE WHEN order_cnt >= 2 THEN 1 ELSE 0 END) AS repeat_customer_cnt, CASE WHEN COUNT(DISTINCT customer_id) > 0 THEN SUM(CASE WHEN order_cnt >= 2 THEN 1 ELSE 0 END) / COUNT(DISTINCT customer_id) ELSE NULL END AS repurchase_rate FROM orders GROUP BY sales_person_id ), avg_rate AS ( SELECT AVG(repurchase_rate) AS avg_repurchase_rate FROM rate WHERE repurchase_rate IS NOT NULL ) SELECT r.sales_person_id, p.person_name AS sales_person_name, r.customer_cnt, r.repeat_customer_cnt, r.repurchase_rate, a.avg_repurchase_rate FROM rate r CROSS JOIN avg_rate a LEFT JOIN dim_person p ON p.person_id = r.sales_person_id AND p.is_current = TRUE WHERE r.repurchase_rate IS NOT NULL AND r.repurchase_rate < a.avg_repurchase_rate ORDER BY (a.avg_repurchase_rate - r.repurchase_rate) DESC LIMIT 15 ; WITH revenue AS ( SELECT so.customer_id, SUM(COALESCE(so.deal_amount, 0)) AS revenue_amount FROM fact_sales_order so WHERE so.order_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.customer_id ), cost AS ( SELECT so.customer_id, SUM(COALESCE(ABS(m.movement_qty) * m.unit_price, 0)) AS cost_amount FROM fact_inventory_movement m JOIN fact_sales_order so ON so.sales_order_id = m.sales_order_id WHERE m.sales_order_id IS NOT NULL AND COALESCE(m.movement_qty, 0) < 0 AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY so.customer_id ) SELECT r.customer_id, c.customer_name, r.revenue_amount, COALESCE(k.cost_amount, 0) AS cost_amount, (r.revenue_amount - COALESCE(k.cost_amount, 0)) AS gross_profit_amount, CASE WHEN r.revenue_amount > 0 THEN (r.revenue_amount - COALESCE(k.cost_amount, 0)) / r.revenue_amount ELSE NULL END AS gross_margin_rate FROM revenue r LEFT JOIN cost k ON k.customer_id = r.customer_id LEFT JOIN dim_customer c ON c.customer_id = r.customer_id AND c.is_current = TRUE ORDER BY gross_profit_amount DESC LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000004", - "businessName": "SalesPerformanceIntelligentStatistics", - "businessDescription": "销售业绩智能统计系统:基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名。", - "datasourceId": "19", - "sqlTemplate": "WITH deal AS ( SELECT so.sales_order_id AS deal_id, so.contract_id, so.customer_id, so.sales_person_id, so.deal_amount, so.order_date_utc AS deal_time_utc FROM fact_sales_order so ), role_first AS ( SELECT d.deal_id, COALESCE(dc.owner_id, d.sales_person_id) AS person_id, 'FIRST_TOUCH' AS role, 0.30 AS role_weight FROM deal d LEFT JOIN dim_contract dc ON dc.contract_id = d.contract_id AND dc.is_current = TRUE ), role_follow AS ( SELECT d.deal_id, d.sales_person_id AS person_id, 'FOLLOW_UP' AS role, 0.50 AS role_weight FROM deal d ), collab_people AS ( SELECT DISTINCT s.contract_id, s.operator_id AS person_id FROM fact_sales_shipment s WHERE s.operator_id IS NOT NULL UNION DISTINCT SELECT DISTINCT i.contract_id, i.handler_id AS person_id FROM fact_invoice i WHERE i.handler_id IS NOT NULL UNION DISTINCT SELECT DISTINCT w.contract_id, w.handler_id AS person_id FROM fact_write_off w WHERE w.handler_id IS NOT NULL ), role_collab AS ( SELECT d.deal_id, cp.person_id, 'COLLAB' AS role, 0.20 AS role_weight FROM deal d JOIN collab_people cp ON cp.contract_id = d.contract_id ), roles_union AS ( SELECT * FROM role_first UNION ALL SELECT * FROM role_follow UNION ALL SELECT * FROM role_collab ), collab_cnt AS ( SELECT deal_id, COUNT(DISTINCT person_id) AS collab_person_cnt FROM role_collab GROUP BY deal_id ), normalized AS ( SELECT ru.deal_id, ru.person_id, ru.role, CASE WHEN ru.role = 'COLLAB' THEN ru.role_weight / NULLIF(cc.collab_person_cnt, 0) ELSE ru.role_weight END AS contribution_pct FROM roles_union ru LEFT JOIN collab_cnt cc ON cc.deal_id = ru.deal_id ) SELECT n.deal_id, n.person_id, n.role, n.contribution_pct FROM normalized n LIMIT 15 ; WITH deal AS ( SELECT so.sales_order_id AS deal_id, so.contract_id, so.customer_id, so.deal_amount, so.order_date_utc AS deal_time_utc FROM fact_sales_order so ), contrib AS ( WITH role_first AS ( SELECT d.deal_id, COALESCE(dc.owner_id, so.sales_person_id) AS person_id, 'FIRST_TOUCH' AS role, 0.30 AS role_weight FROM deal d JOIN fact_sales_order so ON so.sales_order_id = d.deal_id LEFT JOIN dim_contract dc ON dc.contract_id = d.contract_id AND dc.is_current = TRUE ), role_follow AS ( SELECT d.deal_id, so.sales_person_id AS person_id, 'FOLLOW_UP' AS role, 0.50 AS role_weight FROM deal d JOIN fact_sales_order so ON so.sales_order_id = d.deal_id ), collab_people AS ( SELECT DISTINCT s.contract_id, s.operator_id AS person_id FROM fact_sales_shipment s WHERE s.operator_id IS NOT NULL UNION DISTINCT SELECT DISTINCT i.contract_id, i.handler_id AS person_id FROM fact_invoice i WHERE i.handler_id IS NOT NULL UNION DISTINCT SELECT DISTINCT w.contract_id, w.handler_id AS person_id FROM fact_write_off w WHERE w.handler_id IS NOT NULL ), role_collab AS ( SELECT d.deal_id, cp.person_id, 'COLLAB' AS role, 0.20 AS role_weight FROM deal d JOIN collab_people cp ON cp.contract_id = d.contract_id ), roles_union AS ( SELECT * FROM role_first UNION ALL SELECT * FROM role_follow UNION ALL SELECT * FROM role_collab ), collab_cnt AS ( SELECT deal_id, COUNT(DISTINCT person_id) AS collab_person_cnt FROM role_collab GROUP BY deal_id ) SELECT ru.deal_id, ru.person_id, ru.role, CASE WHEN ru.role = 'COLLAB' THEN ru.role_weight / NULLIF(cc.collab_person_cnt, 0) ELSE ru.role_weight END AS contribution_pct FROM roles_union ru LEFT JOIN collab_cnt cc ON cc.deal_id = ru.deal_id ) SELECT d.deal_id, d.deal_time_utc, d.contract_id, d.customer_id, c.customer_name, co.person_id, p.person_name, co.role, co.contribution_pct, d.deal_amount, (d.deal_amount * 0.05 * co.contribution_pct) AS recommend_commission_amount FROM deal d JOIN contrib co ON co.deal_id = d.deal_id LEFT JOIN dim_customer c ON c.customer_id = d.customer_id AND c.is_current = TRUE LEFT JOIN dim_person p ON p.person_id = co.person_id AND p.is_current = TRUE ORDER BY d.deal_time_utc DESC, d.deal_id, co.contribution_pct DESC LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000005", - "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 LIMIT 15 ; SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ap_amount, SUM(COALESCE(written_off_amount, 0)) AS ap_written_off_amount, SUM(COALESCE(unwritten_off_amount, 0)) AS ap_unwritten_off_amount FROM fact_ap_payment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ORDER BY ym LIMIT 15 ; SELECT DATE(doc_date_utc) AS d, SUM(COALESCE(income_amount, 0)) AS cash_in, SUM(COALESCE(expense_amount, 0)) AS cash_out, SUM(COALESCE(income_amount, 0)) - SUM(COALESCE(expense_amount, 0)) AS net_cash FROM fact_cash_flow WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '60 days' GROUP BY DATE(doc_date_utc) ORDER BY d LIMIT 15 ; SELECT r.customer_id, c.customer_name, SUM(COALESCE(r.unwritten_off_amount, 0)) AS ar_overdue_balance, MIN(DATE(r.doc_date_utc)) AS oldest_doc_date FROM fact_ar_receipt r LEFT JOIN dim_customer c ON c.customer_id = r.customer_id AND c.is_current = TRUE WHERE COALESCE(r.unwritten_off_amount, 0) > 0 AND (CURRENT_DATE - DATE(r.doc_date_utc)) > 60 GROUP BY r.customer_id, c.customer_name ORDER BY ar_overdue_balance DESC LIMIT 15 ; WITH revenue AS ( SELECT to_char(doc_date_utc, 'YYYY-MM') AS ym, SUM(COALESCE(amount, 0)) AS ship_amount FROM fact_sales_shipment WHERE doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(doc_date_utc, 'YYYY-MM') ), cost AS ( SELECT to_char(event_time_utc, 'YYYY-MM') AS ym, SUM(COALESCE(ABS(movement_qty) * unit_price, 0)) AS cost_amount FROM fact_inventory_movement WHERE sales_order_id IS NOT NULL AND COALESCE(movement_qty, 0) < 0 AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(event_time_utc, 'YYYY-MM') ) SELECT r.ym, r.ship_amount, c.cost_amount, (r.ship_amount - COALESCE(c.cost_amount, 0)) AS gross_profit_amount, CASE WHEN r.ship_amount > 0 THEN (r.ship_amount - COALESCE(c.cost_amount, 0)) / r.ship_amount ELSE NULL END AS gross_margin_rate FROM revenue r LEFT JOIN cost c ON c.ym = r.ym ORDER BY r.ym LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000006", - "businessName": "SmartCostPredictionModel", - "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", - "datasourceId": "19", - "sqlTemplate": "WITH raw_in AS ( SELECT m.product_id, SUM(COALESCE(m.movement_qty, 0)) AS in_qty, SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) AS in_amount FROM fact_inventory_movement m WHERE m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 GROUP BY m.product_id ) SELECT p.product_code, p.product_name, CASE WHEN ri.in_qty > 0 THEN ri.in_amount / ri.in_qty ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN raw_in ri ON ri.product_id = p.product_id WHERE p.is_current = TRUE AND p.product_type = '标准产品' LIMIT 15 ; WITH bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ) SELECT p.bom_number, p.parent_product_code, c.component_product_code, c.unit_usage_qty FROM parent p JOIN component c ON c.bom_number = p.bom_number ORDER BY p.parent_product_code, c.component_product_code LIMIT 15 ; WITH raw_price AS ( SELECT p.product_code, CASE WHEN SUM(COALESCE(m.movement_qty, 0)) > 0 THEN SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) / SUM(COALESCE(m.movement_qty, 0)) ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN fact_inventory_movement m ON m.product_id = p.product_id AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 WHERE p.is_current = TRUE AND p.product_type = '标准产品' GROUP BY p.product_code ), bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ), line_cost AS ( SELECT p.parent_product_code, c.component_product_code, c.unit_usage_qty, rp.base_unit_cost, (c.unit_usage_qty * rp.base_unit_cost) AS component_cost FROM parent p JOIN component c ON c.bom_number = p.bom_number LEFT JOIN raw_price rp ON rp.product_code = c.component_product_code ), total_cost AS ( SELECT parent_product_code, SUM(COALESCE(component_cost, 0)) AS standard_cost FROM line_cost GROUP BY parent_product_code ) SELECT lc.parent_product_code, lc.component_product_code, lc.unit_usage_qty, lc.base_unit_cost, lc.component_cost, tc.standard_cost, CASE WHEN tc.standard_cost > 0 THEN lc.component_cost / tc.standard_cost ELSE NULL END AS cost_share FROM line_cost lc JOIN total_cost tc ON tc.parent_product_code = lc.parent_product_code ORDER BY lc.parent_product_code, cost_share DESC LIMIT 15 ; WITH scenario AS ( SELECT 'RAW-001' AS material_product_code, 0.10 AS delta_pct ), raw_price AS ( SELECT p.product_code, CASE WHEN SUM(COALESCE(m.movement_qty, 0)) > 0 THEN SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) / SUM(COALESCE(m.movement_qty, 0)) ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN fact_inventory_movement m ON m.product_id = p.product_id AND m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 WHERE p.is_current = TRUE AND p.product_type = '标准产品' GROUP BY p.product_code ), bom_min_level AS ( SELECT bom_number, MIN(COALESCE(level_number, 0)) AS min_level FROM dim_bom WHERE is_current = TRUE GROUP BY bom_number ), parent AS ( SELECT b.bom_number, b.product_code AS parent_product_code FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) = ml.min_level WHERE b.is_current = TRUE ), component AS ( SELECT b.bom_number, b.product_code AS component_product_code, COALESCE(b.unit_usage_qty, 0) AS unit_usage_qty FROM dim_bom b JOIN bom_min_level ml ON ml.bom_number = b.bom_number AND COALESCE(b.level_number, 0) > ml.min_level WHERE b.is_current = TRUE ), line_cost AS ( SELECT p.parent_product_code, c.component_product_code, c.unit_usage_qty, rp.base_unit_cost, (c.unit_usage_qty * rp.base_unit_cost) AS component_cost FROM parent p JOIN component c ON c.bom_number = p.bom_number LEFT JOIN raw_price rp ON rp.product_code = c.component_product_code ), total_cost AS ( SELECT parent_product_code, SUM(COALESCE(component_cost, 0)) AS standard_cost FROM line_cost GROUP BY parent_product_code ), delta_cost AS ( SELECT lc.parent_product_code, SUM( CASE WHEN lc.component_product_code = s.material_product_code THEN COALESCE(lc.component_cost, 0) * s.delta_pct ELSE 0 END ) AS scenario_cost_delta FROM line_cost lc CROSS JOIN scenario s GROUP BY lc.parent_product_code ) SELECT tc.parent_product_code, tc.standard_cost AS base_standard_cost, dc.scenario_cost_delta, (tc.standard_cost + COALESCE(dc.scenario_cost_delta, 0)) AS new_standard_cost FROM total_cost tc LEFT JOIN delta_cost dc ON dc.parent_product_code = tc.parent_product_code ORDER BY dc.scenario_cost_delta DESC LIMIT 15", - "parameters": {} - }, - { - "id": "2006300000000000007", - "businessName": "HumanResourcesAnalytics", - "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", - "datasourceId": "19", - "sqlTemplate": "WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ) SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, p.position, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department, p.position ORDER BY ym, p.department, p.position LIMIT 15 ; SELECT to_char(employment_day, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS hire_count FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL AND employment_day >= (CURRENT_DATE - INTERVAL '12 months') GROUP BY to_char(employment_day, 'YYYY-MM'), department, position ORDER BY ym, department, position LIMIT 15 ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ) SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department, position ORDER BY ym, department, position LIMIT 15 ; WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ), headcount AS ( SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department ), ordered AS ( SELECT person_id, department, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), exit_cnt AS ( SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department ), rate AS ( SELECT h.ym, h.department, COALESCE(e.exit_count, 0) AS exit_count, h.headcount_active, CASE WHEN h.headcount_active > 0 THEN COALESCE(e.exit_count, 0)::numeric / h.headcount_active ELSE NULL END AS monthly_turnover_rate FROM headcount h LEFT JOIN exit_cnt e ON e.ym = h.ym AND e.department IS NOT DISTINCT FROM h.department ), flag AS ( SELECT r.*, LAG(monthly_turnover_rate, 1) OVER (PARTITION BY department ORDER BY ym) AS r_m1, LAG(monthly_turnover_rate, 2) OVER (PARTITION BY department ORDER BY ym) AS r_m2 FROM rate r ) SELECT ym, department, exit_count, headcount_active, monthly_turnover_rate FROM flag WHERE monthly_turnover_rate IS NOT NULL AND r_m1 IS NOT NULL AND r_m2 IS NOT NULL AND monthly_turnover_rate > r_m1 AND r_m1 > r_m2 ORDER BY department, ym LIMIT 15 ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), hire_event AS ( SELECT department, position, employment_day AS hire_date, person_id FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL ), matched AS ( SELECT e.department, e.position, e.exit_time, ( SELECT MIN(h.hire_date) FROM hire_event h WHERE h.department IS NOT DISTINCT FROM e.department AND h.position IS NOT DISTINCT FROM e.position AND h.hire_date > DATE(e.exit_time) ) AS next_hire_date FROM exit_event e ) SELECT department, position, DATE(exit_time) AS exit_date, next_hire_date, (next_hire_date - DATE(exit_time)) AS vacancy_days FROM matched WHERE next_hire_date IS NOT NULL ORDER BY vacancy_days DESC LIMIT 15", - "parameters": {} - } -] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/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 deleted file mode 100644 index 04d9750..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log +++ /dev/null @@ -1,496 +0,0 @@ -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调用成功 -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 deleted file mode 100644 index b786195..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log +++ /dev/null @@ -1,94 +0,0 @@ -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.2026-01-09 b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 deleted file mode 100644 index 2d05afb..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 +++ /dev/null @@ -1,402 +0,0 @@ -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/lzwcai_mcp_sqlexecutor_error.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_error.log deleted file mode 100644 index e69de29..0000000 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 deleted file mode 100644 index 886fc14..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log +++ /dev/null @@ -1,148 +0,0 @@ -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_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_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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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_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_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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 -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_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_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/lzwcai_mcpskills_mfg_data_agentv2/main.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py deleted file mode 100644 index 3a4efb3..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py +++ /dev/null @@ -1,373 +0,0 @@ -from pathlib import Path -from typing import Any -import asyncio -import logging - -# 支持直接运行和模块导入两种方式 -try: - from .utils import load_json, generate_tool_name, generate_input_schema - from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema - from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config - from .utils.logger_config import logger_config -except ImportError: - from utils import load_json, generate_tool_name, generate_input_schema - from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema - from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config - from utils.logger_config import logger_config - -from mcp.server.models import InitializationOptions -from mcp.server import NotificationOptions, Server -import mcp.types as types - -# 初始化 MCP 专用日志器 -mcp_logger = logger_config.setup_mcp_logging() - -# ========== 数据源配置 ========== -# 数据源类型常量 -DATA_SOURCE_API = "api" # 仅使用API数据 -DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据 -DATA_SOURCE_BOTH = "both" # 合并本地和API数据 - -# 默认数据源(可修改) -DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL -# ================================ - - -def get_queries(): - """ - 获取业务查询配置 - - Returns: - list: 包含所有业务查询配置的列表 - """ - try: - # 获取当前文件所在目录 - current_dir = Path(__file__).parent - - # 构建 businessQueries.json 的路径 - json_path = current_dir / "businessQueries.json" - - mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}") - - # 使用 load_json 方法读取 JSON 文件 - queries = load_json(json_path) - - mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置") - - return queries - except Exception as e: - mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True) - raise - - -def generate_tool_schema_from_query(query: dict) -> types.Tool: - """ - 根据查询配置生成 MCP 工具模式 - - Args: - query: 单个查询配置字典 - - Returns: - types.Tool: MCP 工具对象 - """ - try: - # 获取参数定义并生成 inputSchema - parameters = query.get('parameters', {}) - input_schema = generate_input_schema(parameters) - - # 生成工具名称(格式: tool_拼音_id) - # tool_name = generate_tool_name(query['businessName'], query['id']) - tool_name = query['businessName'] - # 构建工具描述,包含业务名称和业务描述 - description = f"{query['businessName']}: {query['businessDescription']}" - - mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}") - - return types.Tool( - name=tool_name, - description=description, - inputSchema=input_schema - ) - except Exception as e: - mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True) - raise - - -# 创建 MCP 服务器实例 -server = Server("lzwcai-mcpskills-mfg-data-agentv2") - -# 缓存查询配置,避免重复加载 -_queries_cache = None - - -async def get_queries_cache(source: str = None): - """ - 获取或初始化查询配置缓存 - - Args: - source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE) - - "api": 仅使用API数据 - - "local": 仅使用本地JSON数据 - - "both": 合并本地和API数据 - - Returns: - 查询配置列表 - """ - global _queries_cache - if _queries_cache is None: - source = source or DEFAULT_DATA_SOURCE - mcp_logger.info(f"初始化查询配置(数据源: {source})...") - - if source == DATA_SOURCE_LOCAL: - _queries_cache = get_queries() - mcp_logger.info(f"本地配置: {len(_queries_cache)} 条") - - elif source == DATA_SOURCE_API: - try: - _queries_cache = await call_third_party_api() - mcp_logger.info(f"API配置: {len(_queries_cache)} 条") - mcp_logger.info(f"API配置数组: {_queries_cache}") - except Exception as e: - mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}") - _queries_cache = get_queries() - - else: # DATA_SOURCE_BOTH - local = get_queries() - try: - api = await call_third_party_api() - except Exception as e: - mcp_logger.warning(f"API获取失败: {e}") - api = [] - _queries_cache = local + api - mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})") - - return _queries_cache - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """ - 列出所有动态生成的 MCP 工具 - - Returns: - list[types.Tool]: 所有可用的工具列表 - """ - try: - mcp_logger.info("收到列出工具请求") - - queries = await get_queries_cache() - tools = [] - - for query in queries: - tool = generate_tool_schema_from_query(query) - tools.append(tool) - - mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具") - mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}") - - return tools - except Exception as e: - mcp_logger.error(f"列出工具失败: {e}", exc_info=True) - raise - - -@server.call_tool() -async def handle_call_tool( - name: str, - arguments: dict[str, Any] | None -) -> list[types.TextContent]: - """ - 处理工具调用请求 - - Args: - name: 工具名称 - arguments: 工具参数 - - Returns: - list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置) - """ - try: - mcp_logger.info(f"收到工具调用请求: {name}") - mcp_logger.debug(f"工具参数: {arguments}") - - # 获取查询配置缓存 - queries = await get_queries_cache() - - # 根据工具名称查找对应的 item(接口配置) - tool_item = None - for query in queries: - # tool_name = generate_tool_name(query['businessName'], query['id']) - tool_name = query['businessName'] - if tool_name == name: - tool_item = query - break - - # 构建返回结果 - import json - - if tool_item: - request_data = { - "datasourceId": get_datasource_id(), - "businessName": tool_item.get("businessName"), - "businessDescription": tool_item.get("businessDescription"), - "sqlTemplate": tool_item.get("sqlTemplate"), - "parameters": tool_item.get("parameters"), - "testParams": arguments or {} - } - - # 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data - if arguments and arguments.get("targetDatabaseName"): - request_data["targetDatabaseName"] = arguments["targetDatabaseName"] - mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}") - - # 调用测试SQL API - try: - mcp_logger.info("正在调用测试SQL API...") - api_response = test_sql_with_schema(request_data) - mcp_logger.info("测试SQL API调用成功") - - # 返回包含 data 字段的结果 - result = { - "success": True, - "data": api_response - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - - except Exception as e: - error_msg = f"调用测试SQL API失败: {str(e)}" - mcp_logger.error(error_msg, exc_info=True) - result = { - "success": False, - "error": error_msg, - "data": None - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - else: - error_msg = f"未找到工具 {name} 对应的配置" - result = { - "success": False, - "error": error_msg, - "data": None - } - result_text = json.dumps(result, ensure_ascii=False, indent=2) - - mcp_logger.debug(f"工具调用结果: {result_text}") - - return [ - types.TextContent( - type="text", - text=result_text - ) - ] - except Exception as e: - error_msg = f"工具调用失败: {name}, 错误: {e}" - mcp_logger.error(error_msg, exc_info=True) - return [ - types.TextContent( - type="text", - text=f"错误: {error_msg}" - ) - ] - - -async def call_third_party_api(skill_id: str = None) -> list: - """ - 调用第三方API获取技能信息并返回处理后的数据 - - Args: - skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178) - - Returns: - 处理后的查询配置列表(businessQueries格式) - - Example: - queries = await call_third_party_api() - # 返回: [{"id": "...", "businessName": "...", ...}, ...] - """ - try: - # 如果没有传入 skill_id,则从环境变量读取 - if skill_id is None: - skill_id = get_skill_id() - - mcp_logger.info(f"调用第三方API,skill_id: {skill_id}") - - # 获取原始数据 - raw_result = get_skill_by_id(skill_id) - - mcp_logger.info(f"成功{raw_result}") - - # 处理并返回 - processed_queries = process_skill_response(raw_result) - - mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据") - return processed_queries - - except Exception as e: - mcp_logger.error(f"API调用失败: {e}", exc_info=True) - raise - - -async def async_main(): - """MCP 服务器异步主函数""" - try: - mcp_logger.info("=" * 60) - mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder") - mcp_logger.info("版本: 0.1.0") - mcp_logger.info("=" * 60) - - # 输出环境配置信息 - env_config = get_env_config() - mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}") - mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}") - mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}") - mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}") - mcp_logger.info("=" * 60) - - from mcp.server.stdio import stdio_server - - async with stdio_server() as (read_stream, write_stream): - mcp_logger.info("MCP 服务器已启动,等待客户端连接...") - - await server.run( - read_stream, - write_stream, - InitializationOptions( - server_name="lzwcai-mcpskills-analyzeOrder", - server_version="0.1.0", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - mcp_logger.info("MCP 服务器已关闭") - - except Exception as e: - mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True) - raise - - -def main(): - """入口点函数(用于 console_scripts)""" - try: - # 初始化系统日志 - # MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信 - logger_config.setup_logging( - app_name="lzwcai_mcp_sqlexecutor", - log_level=logging.INFO, - console_output=False # 禁用控制台输出 - ) - - mcp_logger.info("开始运行 MCP SQL Executor 服务器") - asyncio.run(async_main()) - - except KeyboardInterrupt: - mcp_logger.info("收到中断信号,正在关闭服务器...") - except Exception as e: - mcp_logger.error(f"程序运行失败: {e}", exc_info=True) - raise - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml deleted file mode 100644 index ecfcf7e..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcpskills-mfg-data-agentv2" -version = "0.1.1" -description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.13" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcpskills-mfg-data-agentv2 = "lzwcai_mcpskills_mfg_data_agentv2.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_mfg_data_agentv2"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 deleted file mode 100644 index 39b7319..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 +++ /dev/null @@ -1,169 +0,0 @@ --- ===================================================== --- 交付风险预测:延迟概率与红/黄/绿预警等级 --- 基于历史订单的生产周期、物流延误、设备故障等特征 --- ===================================================== - -WITH --- 1. 全局生产特征(汇总所有工单) -global_production AS ( - SELECT - COUNT(*) AS total_wo_count, - AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate, - SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count, - SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count - FROM fact_work_order -), - --- 2. 全局质检特征 -global_quality AS ( - SELECT - COUNT(*) AS total_inspection_count, - SUM(COALESCE(pass_qty, 0)) AS total_pass_qty, - SUM(COALESCE(fail_qty, 0)) AS total_fail_qty, - CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0 - THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) - ELSE 1 END AS qc_pass_rate - FROM fact_quality_inspection -), - --- 3. 全局工序不良特征 -global_operation AS ( - SELECT - COUNT(*) AS total_task_count, - SUM(COALESCE(good_qty, 0)) AS total_good_qty, - SUM(COALESCE(bad_qty, 0)) AS total_bad_qty, - CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0 - THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) - ELSE 0 END AS operation_defect_rate - FROM fact_operation_task -), - --- 4. 客户级别发货统计 -customer_shipment AS ( - SELECT - customer_id, - COUNT(*) AS shipment_count, - SUM(COALESCE(amount, 0)) AS total_shipment_amount - FROM fact_sales_shipment - GROUP BY customer_id -), - --- 5. 客户级别退货统计 -customer_return AS ( - SELECT - customer_id, - COUNT(*) AS return_count, - SUM(COALESCE(amount, 0)) AS total_return_amount - FROM fact_sales_return - GROUP BY customer_id -), - --- 6. 订单风险评估 -order_risk AS ( - SELECT - so.sales_order_id, - so.sales_order_number, - c.customer_name, - so.order_date_utc, - so.deal_amount, - so.payment_status, - - -- 全局生产指标 - gp.avg_completion_rate AS production_completion_rate, - gp.pending_wo_count, - gp.total_wo_count AS work_order_count, - - -- 全局质检指标 - gq.qc_pass_rate, - gq.total_fail_qty, - - -- 全局工序指标 - go.operation_defect_rate, - - -- 客户级别指标 - COALESCE(cs.shipment_count, 0) AS shipment_count, - COALESCE(cr.return_count, 0) AS return_count, - CASE WHEN COALESCE(cs.shipment_count, 0) > 0 - THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count - ELSE 0 END AS return_rate - - FROM fact_sales_order so - LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true - CROSS JOIN global_production gp - CROSS JOIN global_quality gq - CROSS JOIN global_operation go - LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id - LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id -) - --- 7. 最终输出 -SELECT - sales_order_id, - sales_order_number, - customer_name, - order_date_utc, - deal_amount, - payment_status, - - -- 风险特征 - work_order_count, - ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate, - pending_wo_count, - ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate, - ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate, - return_count, - ROUND(return_rate::NUMERIC, 4) AS return_rate, - - -- 延迟概率 - ROUND(( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 - WHEN production_completion_rate < 0.5 THEN 0.20 - WHEN production_completion_rate < 0.8 THEN 0.10 - ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 - WHEN qc_pass_rate < 0.9 THEN 0.15 - WHEN qc_pass_rate < 0.95 THEN 0.08 - ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 - WHEN operation_defect_rate > 0.05 THEN 0.12 - WHEN operation_defect_rate > 0.02 THEN 0.05 - ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 - WHEN return_rate > 0.05 THEN 0.08 - WHEN return_rate > 0.02 THEN 0.03 - ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 - WHEN payment_status = 'PARTIAL' THEN 0.05 - ELSE 0 END - )::NUMERIC, 2) AS delay_probability, - - -- 红/黄/绿预警 - CASE - WHEN ( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END - ) >= 0.50 THEN 'RED' - WHEN ( - CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END - + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END - + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END - + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END - + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END - ) >= 0.25 THEN 'YELLOW' - ELSE 'GREEN' - END AS risk_level, - - -- 风险原因 - CONCAT_WS(' | ', - CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END, - CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END, - CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END, - CASE WHEN return_rate > 0.05 THEN '历史退货率高' END, - CASE WHEN payment_status = 'UNPAID' THEN '未付款' END - ) AS risk_reasons - -FROM order_risk -ORDER BY delay_probability DESC, deal_amount DESC; diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py deleted file mode 100644 index 3df2b44..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Utils package for lzwcai_mcp_sqlexecutor""" - -from .json_helper import load_json -from .name_helper import generate_tool_name -from .schema_helper import generate_input_schema, validate_input_schema -from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema -from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable - -__all__ = [ - 'load_json', - 'generate_tool_name', - 'generate_input_schema', - 'validate_input_schema', - 'DataSourceAPIClient', - 'get_skill_by_id', - 'process_skill_response', - 'test_sql_with_schema', - 'get_database_id', - 'get_datasource_id', - 'get_skill_id', - 'get_backend_base_url', - 'get_env_config', - 'set_env_variable' -] - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py deleted file mode 100644 index 279d800..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py +++ /dev/null @@ -1,332 +0,0 @@ -""" -第三方API调用客户端 -用于调用外部数据源接口 -""" - -import httpx -import logging -import json -from typing import Dict, Any, Optional, List - -# 支持直接运行和模块导入两种方式 -try: - from .env_config import get_backend_base_url -except ImportError: - from env_config import get_backend_base_url - -# 获取日志记录器 -logger = logging.getLogger(__name__) - - -class DataSourceAPIClient: - """数据源API客户端""" - - def __init__( - self, - base_url: Optional[str] = None, - token: Optional[str] = None - ): - """ - 初始化API客户端 - - Args: - base_url: API基础URL(默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088) - token: 认证令牌(Bearer Token) - """ - # 如果没有传入 base_url,则从环境变量读取 - if base_url is None: - base_url = get_backend_base_url() - - self.base_url = base_url.rstrip('/') - self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw" - self.client = httpx.Client(timeout=30.0) - - def _get_headers(self) -> Dict[str, str]: - """ - 获取请求头 - - Returns: - 请求头字典 - """ - return { - 'Authorization': f'Bearer {self.token}', - } - - def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]: - """ - 根据技能ID获取技能信息 - - Args: - skill_id: 技能ID - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - try: - url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}" - - logger.info(f"正在调用API: {url}") - logger.info(f"请求参数 - skill_id: {skill_id}") - - response = self.client.get( - url, - headers=self._get_headers() - ) - - # 检查HTTP状态码 - response.raise_for_status() - - # 解析JSON响应 - data = response.json() - - logger.info(f"API调用成功: {url}") - logger.debug(f"响应数据: {data}") - - return data - - except httpx.TimeoutException: - error_msg = f"API请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - - except httpx.RequestError as e: - error_msg = f"API请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理API响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]: - """ - 测试SQL语句并返回执行结果 - - Args: - request_data: 请求数据,包含以下字段: - - datasourceId: 数据源ID - - businessName: 业务名称 - - businessDescription: 业务描述 - - sqlTemplate: SQL模板 - - parameters: 参数定义 - - testParams: 测试参数 - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - try: - url = f"{self.base_url}/datasource/sqlExecutionLog/testBatchSqlWithSchema" - - # 构建请求头(包含Content-Type) - headers = self._get_headers() - headers['Content-Type'] = 'application/json' - headers['Accept'] = '*/*' - - logger.info(f"正在调用测试SQL API: {url}") - logger.info(f"请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - - # 发送POST请求 - response = self.client.post( - url, - headers=headers, - json=request_data - ) - - # 检查HTTP状态码 - response.raise_for_status() - - # 解析JSON响应 - result = response.json() - - logger.info(f"测试SQL API调用成功") - logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") - - # 处理返回数据结构: {code, data: [{errorMessage, data}, ...], msg} - # 检查外层 code - if result.get("code") != 200: - error_msg = result.get("msg", "接口返回错误") - logger.error(f"接口返回错误: code={result.get('code')}, msg={error_msg}") - raise Exception(error_msg) - - # data 是一个数组,每个元素包含 errorMessage 和 data - data_list = result.get("data", []) - - # 如果不是数组,兼容旧格式 - if isinstance(data_list, dict): - if data_list.get("errorMessage"): - error_msg = data_list.get("errorMessage") - logger.error(f"接口业务错误: {error_msg}") - raise Exception(error_msg) - return data_list.get("data") - - # 处理数组格式,提取每个 item 的 data 并组合 - combined_data = [] - for item in data_list: - if item.get("errorMessage"): - error_msg = item.get("errorMessage") - logger.error(f"接口业务错误: {error_msg}") - raise Exception(error_msg) - item_data = item.get("data", []) - combined_data.append(item_data) - - # 返回组合后的数据 [data1, data2, ...] - return combined_data - - except httpx.TimeoutException: - error_msg = f"测试SQL API请求超时: {url}" - logger.error(error_msg) - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(error_msg) - logger.error(f"错误响应: {e.response.text}") - raise Exception(error_msg) - - except httpx.RequestError as e: - error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}" - logger.error(error_msg) - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理测试SQL API响应时出错: {str(e)}" - logger.error(error_msg, exc_info=True) - raise Exception(error_msg) - - def close(self): - """关闭HTTP客户端""" - self.client.close() - - -# 创建默认客户端实例 -default_client = DataSourceAPIClient() - - -def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:根据技能ID获取技能信息 - - Args: - skill_id: 技能ID - base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) - token: 认证令牌(可选,使用默认值) - - Returns: - API响应数据 - """ - if base_url or token: - client = DataSourceAPIClient( - base_url=base_url, - token=token - ) - return client.get_skill_by_id(skill_id) - else: - return default_client.get_skill_by_id(skill_id) - - -def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:测试SQL语句并返回执行结果 - - Args: - request_data: 请求数据,包含以下字段: - - datasourceId: 数据源ID - - businessName: 业务名称 - - businessDescription: 业务描述 - - sqlTemplate: SQL模板 - - parameters: 参数定义 - - testParams: 测试参数 - base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) - token: 认证令牌(可选,使用默认值) - - Returns: - API响应数据 - """ - if base_url or token: - client = DataSourceAPIClient( - base_url=base_url, - token=token - ) - return client.test_sql_with_schema(request_data) - else: - return default_client.test_sql_with_schema(request_data) - - -def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: - """ - 处理API响应数据,映射为businessQueries格式 - - Args: - response: API原始响应数据 - - Returns: - 处理后的查询配置列表 - """ - try: - # 提取data数组 - data_list = response.get("data", []) - - # 默认的员工ID参数schema - default_employee_schema = { - "type": "object", - "required": ["employeeId"], - "properties": { - "employeeId": { - "type": "number", - "description": "员工ID,用于标识员工的唯一数字标识符", - "examples": [1001, 2002] - } - } - } - - # 映射每个skill为businessQuery格式 - queries = [] - for skill in data_list: - # 解析sqlParams字符串为JSON对象 - sql_params = json.loads(skill.get("sqlParams", "{}")) - - # 判断sqlParams是否为空对象 - is_empty_params = ( - not sql_params.get("properties") or - len(sql_params.get("properties", {})) == 0 - ) and ( - not sql_params.get("required") or - len(sql_params.get("required", [])) == 0 - ) - - # 如果是空对象,使用默认的员工ID参数 - if is_empty_params: - logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空,使用默认员工ID参数") - sql_params = default_employee_schema - - # 映射字段 - query = { - "id": skill.get("id"), - "businessName": skill.get("name"), - "businessDescription": skill.get("description"), - "sqlTemplate": skill.get("sqlTemplate"), - "parameters": sql_params, - "datasourceId": skill.get("datasourceId") - } - queries.append(query) - - logger.info(f"成功处理 {len(queries)} 条技能数据") - return queries - - except Exception as e: - logger.error(f"处理API响应数据失败: {e}", exc_info=True) - raise - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py deleted file mode 100644 index 585b0a6..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py +++ /dev/null @@ -1,106 +0,0 @@ -"""环境变量配置模块""" - -import os -from typing import Optional - - -def get_database_id(default: str = "29") -> str: - """ - 获取数据库ID环境变量 - - Args: - default: 默认值(默认为 "29") - - Returns: - str: 数据库ID - - Environment Variables: - databaseId: 数据库ID - """ - return os.environ.get("databaseId", default) - - -def get_datasource_id(default: str = "") -> str: - """ - 获取数据源ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 数据源ID - - Environment Variables: - datasourceId: 数据源ID - """ - return os.environ.get("datasourceId", default) - - -def get_skill_id(default: str = "") -> str: - """ - 获取技能ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 技能ID - - Environment Variables: - skillId: 技能ID - """ - return os.environ.get("skillId", default) - - -def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: - """ - 获取后端API基础URL环境变量 - - Args: - default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086") - - Returns: - str: 后端API基础URL - - Environment Variables: - backendBaseUrl: 后端API基础URL - """ - return os.environ.get("backendBaseUrl", default) - - -def get_env_config() -> dict: - """ - 获取所有环境配置 - - Returns: - dict: 包含所有配置的字典 - - Example: - config = get_env_config() - print(config['database_id']) # 输出: "29" - print(config['datasource_id']) # 输出: "" - print(config['skill_id']) # 输出: "" - print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086" - """ - return { - "database_id": get_database_id(), - "datasource_id": get_datasource_id(), - "skill_id": get_skill_id(), - "backend_base_url": get_backend_base_url() - } - - -def set_env_variable(key: str, value: str) -> None: - """ - 设置环境变量(仅在当前进程中有效) - - Args: - key: 环境变量名 - value: 环境变量值 - - Example: - set_env_variable("databaseId", "30") - set_env_variable("skillId", "1234567890") - """ - os.environ[key] = value - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py deleted file mode 100644 index 1a6fc95..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py +++ /dev/null @@ -1,60 +0,0 @@ -"""JSON 文件读取工具""" - -import json -from pathlib import Path -from typing import Any, Union - - -def load_json(json_path: Union[str, Path]) -> Any: - """ - 读取 JSON 文件并返回其内容 - - Args: - json_path: JSON 文件的路径(支持字符串或 Path 对象) - - Returns: - JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型) - - Raises: - FileNotFoundError: 当文件不存在时 - json.JSONDecodeError: 当 JSON 格式无效时 - Exception: 其他读取错误 - - Example: - >>> data = load_json('config.json') - >>> print(data) - {'key': 'value'} - - >>> data = load_json(Path('data/users.json')) - >>> print(data) - [{'id': 1, 'name': 'Alice'}] - """ - try: - # 转换为 Path 对象 - path = Path(json_path) - - # 检查文件是否存在 - if not path.exists(): - raise FileNotFoundError(f"JSON 文件不存在: {json_path}") - - # 检查是否为文件 - if not path.is_file(): - raise ValueError(f"路径不是一个文件: {json_path}") - - # 读取并解析 JSON 文件 - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - return data - - except json.JSONDecodeError as e: - raise json.JSONDecodeError( - f"JSON 格式错误: {e.msg}", - e.doc, - e.pos - ) - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py deleted file mode 100644 index 290964f..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py +++ /dev/null @@ -1,489 +0,0 @@ -# -*- coding: utf-8 -*- -""" -统一日志配置模块 -提供系统级别的日志配置和管理 -""" - -import os -import sys -import logging -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from datetime import datetime -from pathlib import Path - - -class LoggerConfig: - """日志配置管理类""" - - def __init__(self, logs_dir: str = None): - """初始化日志配置 - - Args: - logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹 - """ - # 确定日志目录 - if logs_dir: - self.logs_dir = Path(logs_dir) - else: - # 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录) - project_root = Path(__file__).parent.parent - self.logs_dir = project_root / "logs" - - # 创建日志目录 - self.logs_dir.mkdir(exist_ok=True) - - # 日志格式 - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' - self.date_format = '%Y-%m-%d %H:%M:%S' - - # 从环境变量获取日志级别,默认为INFO - self.log_level = self._get_log_level_from_env() - - # 是否已初始化 - self._initialized = False - - def _get_log_level_from_env(self) -> int: - """从环境变量获取日志级别 - - Returns: - int: 日志级别 - """ - log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() - - # 日志级别映射 - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'WARN': logging.WARNING, # 兼容性别名 - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, - 'FATAL': logging.CRITICAL # 兼容性别名 - } - - return level_mapping.get(log_level_str, logging.INFO) - - def setup_logging(self, - app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO, - max_file_size: int = 10 * 1024 * 1024, # 10MB - backup_count: int = 5, - console_output: bool = True) -> logging.Logger: - """设置系统日志配置 - - Args: - app_name: 应用名称,用于日志文件命名 - log_level: 日志级别 - max_file_size: 单个日志文件最大大小(字节) - backup_count: 保留的备份文件数量 - console_output: 是否输出到控制台 - - Returns: - logging.Logger: 配置好的根日志器 - """ - if self._initialized: - return logging.getLogger() - - # 设置根日志器 - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # 清除现有的处理器 - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - # 创建格式化器 - formatter = logging.Formatter(self.log_format, self.date_format) - - # 1. 主日志文件 - 按大小滚动 - main_log_file = self.logs_dir / f"{app_name}.log" - file_handler = RotatingFileHandler( - main_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # 2. 错误日志文件 - 只记录ERROR及以上级别 - error_log_file = self.logs_dir / f"{app_name}_error.log" - error_handler = RotatingFileHandler( - error_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - error_handler.setLevel(logging.ERROR) - error_handler.setFormatter(formatter) - root_logger.addHandler(error_handler) - - # 3. 按日期滚动的日志文件 - daily_log_file = self.logs_dir / f"{app_name}_daily.log" - daily_handler = TimedRotatingFileHandler( - daily_log_file, - when='midnight', - interval=1, - backupCount=30, # 保留30天 - encoding='utf-8' - ) - daily_handler.setLevel(log_level) - daily_handler.setFormatter(formatter) - daily_handler.suffix = "%Y-%m-%d" - root_logger.addHandler(daily_handler) - - # 4. 控制台输出 - # 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信 - if console_output: - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setLevel(log_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - self.date_format - ) - console_handler.setFormatter(console_formatter) - root_logger.addHandler(console_handler) - - self._initialized = True - - # 记录初始化信息 - root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") - root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}") - - return root_logger - - def get_module_logger(self, module_name: str) -> logging.Logger: - """获取模块专用日志器 - - Args: - module_name: 模块名称 - - Returns: - logging.Logger: 模块日志器 - """ - return logging.getLogger(module_name) - - def create_component_logger(self, - component_name: str, - log_file: str = None, - log_level: int = None) -> logging.Logger: - """为特定组件创建独立的日志器 - - Args: - component_name: 组件名称 - log_file: 独立日志文件名(可选) - log_level: 日志级别(可选) - - Returns: - logging.Logger: 组件日志器 - """ - logger = logging.getLogger(component_name) - - if log_file: - # 为组件创建独立的日志文件 - component_log_file = self.logs_dir / log_file - handler = RotatingFileHandler( - component_log_file, - maxBytes=5 * 1024 * 1024, # 5MB - backupCount=3, - encoding='utf-8' - ) - - formatter = logging.Formatter(self.log_format, self.date_format) - handler.setFormatter(formatter) - - if log_level: - handler.setLevel(log_level) - - logger.addHandler(handler) - logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}") - - return logger - - def setup_mqtt_logging(self) -> logging.Logger: - """设置MQTT专用日志 - - Returns: - logging.Logger: MQTT日志器 - """ - return self.create_component_logger( - "mqtt_communication", - "mqtt_communication.log", - logging.DEBUG - ) - - def setup_mcp_logging(self) -> logging.Logger: - """设置MCP专用日志 - - Returns: - logging.Logger: MCP日志器 - """ - return self.create_component_logger( - "mcp_services", - "mcp_services.log", - logging.DEBUG - ) - - def setup_api_logging(self) -> logging.Logger: - """设置API专用日志 - - Returns: - logging.Logger: API日志器 - """ - return self.create_component_logger( - "api_requests", - "api_requests.log", - logging.INFO - ) - - def get_logs_info(self) -> dict: - """获取日志系统信息 - - Returns: - dict: 日志系统信息 - """ - log_files = [] - if self.logs_dir.exists(): - for log_file in self.logs_dir.glob("*.log*"): - stat = log_file.stat() - log_files.append({ - "name": log_file.name, - "size": stat.st_size, - "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() - }) - - return { - "logs_directory": str(self.logs_dir), - "initialized": self._initialized, - "log_files": log_files, - "total_files": len(log_files) - } - - def cleanup_old_logs(self, days: int = 30): - """清理旧日志文件 - - Args: - days: 保留天数 - """ - if not self.logs_dir.exists(): - return - - from datetime import timedelta - cutoff_time = datetime.now() - timedelta(days=days) - - cleaned_files = [] - for log_file in self.logs_dir.glob("*.log*"): - if log_file.stat().st_mtime < cutoff_time.timestamp(): - try: - log_file.unlink() - cleaned_files.append(log_file.name) - except Exception as e: - logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}") - - if cleaned_files: - logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}") - - def set_log_level(self, level: int, logger_name: str = None): - """动态调整日志级别 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - old_level = logger.level - logger.setLevel(level) - - # 同时调整所有处理器的级别 - for handler in logger.handlers: - if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr): - # 不调整控制台处理器的级别,保持原有设置 - handler.setLevel(level) - - level_name = logging.getLevelName(level) - old_level_name = logging.getLevelName(old_level) - target = logger_name or "根日志器" - - logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}") - - def set_temporary_log_level(self, level: int, logger_name: str = None): - """临时调整日志级别(会保存原始级别用于恢复) - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - """ - if not hasattr(self, '_original_levels'): - self._original_levels = {} - - target_name = logger_name or 'root' - - if logger_name: - logger = logging.getLogger(logger_name) - else: - logger = logging.getLogger() - - # 保存原始级别 - if target_name not in self._original_levels: - self._original_levels[target_name] = logger.level - - # 设置新级别 - self.set_log_level(level, logger_name) - - level_name = logging.getLevelName(level) - target = logger_name or "根日志器" - logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)") - - def restore_log_level(self, logger_name: str = None): - """恢复日志级别到调整前的状态 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - if not hasattr(self, '_original_levels'): - logging.warning("没有找到保存的原始日志级别") - return - - target_name = logger_name or 'root' - - if target_name not in self._original_levels: - logging.warning(f"没有找到 {target_name} 的原始日志级别") - return - - original_level = self._original_levels[target_name] - self.set_log_level(original_level, logger_name) - - # 清除保存的级别 - del self._original_levels[target_name] - - target = logger_name or "根日志器" - level_name = logging.getLevelName(original_level) - logging.info(f"已恢复日志级别: {target} -> {level_name}") - - def get_current_log_levels(self) -> dict: - """获取当前所有日志器的级别信息 - - Returns: - dict: 日志器级别信息 - """ - levels_info = {} - - # 根日志器 - root_logger = logging.getLogger() - levels_info['root'] = { - 'level': root_logger.level, - 'level_name': logging.getLevelName(root_logger.level), - 'handlers_count': len(root_logger.handlers) - } - - # 其他已创建的日志器 - for name, logger in logging.Logger.manager.loggerDict.items(): - if isinstance(logger, logging.Logger): - levels_info[name] = { - 'level': logger.level, - 'level_name': logging.getLevelName(logger.level), - 'handlers_count': len(logger.handlers) - } - - return levels_info - - -# 全局日志配置实例 -logger_config = LoggerConfig() - - -def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor", - log_level: int = logging.INFO) -> logging.Logger: - """系统日志初始化快捷函数 - - Args: - app_name: 应用名称 - log_level: 日志级别 - - Returns: - logging.Logger: 根日志器 - """ - return logger_config.setup_logging(app_name, log_level) - - -def get_logger(name: str) -> logging.Logger: - """获取日志器的快捷函数 - - Args: - name: 日志器名称 - - Returns: - logging.Logger: 日志器实例 - """ - return logger_config.get_module_logger(name) - - -def set_log_level(level: int, logger_name: str = None): - """动态调整日志级别的快捷函数 - - Args: - level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 调整根日志器为DEBUG级别 - set_log_level(logging.DEBUG) - - # 调整特定模块日志器为WARNING级别 - set_log_level(logging.WARNING, "agent_ontology.core") - """ - logger_config.set_log_level(level, logger_name) - - -def set_temporary_log_level(level: int, logger_name: str = None): - """临时调整日志级别的快捷函数 - - Args: - level: 临时日志级别 - logger_name: 指定日志器名称,None表示调整根日志器 - - Examples: - # 临时调整为DEBUG级别进行调试 - set_temporary_log_level(logging.DEBUG) - # ... 进行调试 ... - # 恢复原始级别 - restore_log_level() - """ - logger_config.set_temporary_log_level(level, logger_name) - - -def restore_log_level(logger_name: str = None): - """恢复日志级别的快捷函数 - - Args: - logger_name: 指定日志器名称,None表示恢复根日志器 - """ - logger_config.restore_log_level(logger_name) - - -def get_current_log_levels() -> dict: - """获取当前日志级别信息的快捷函数 - - Returns: - dict: 日志器级别信息 - - Examples: - levels = get_current_log_levels() - print(f"根日志器级别: {levels['root']['level_name']}") - """ - return logger_config.get_current_log_levels() - - -# 便捷的日志级别常量 -class LogLevel: - """日志级别常量类""" - DEBUG = logging.DEBUG - INFO = logging.INFO - WARNING = logging.WARNING - ERROR = logging.ERROR - CRITICAL = logging.CRITICAL \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py deleted file mode 100644 index d66cf32..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" -名称生成工具模块 -""" - -from pypinyin import lazy_pinyin, Style -import logging - -logger = logging.getLogger(__name__) - - -def generate_tool_name(business_name: str, tool_id: str) -> str: - """ - 根据业务名称和ID生成工具名称 - 格式: tool_拼音_id - - Args: - business_name: 业务名称(中文) - tool_id: 工具ID - - Returns: - str: 格式化的工具名称 - """ - try: - # 将中文转换为拼音(无音调,小写) - pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL) - # 拼接拼音 - pinyin_str = ''.join(pinyin_list) - - # 将 ID 中的 '-' 替换为 '_' - formatted_id = tool_id.replace('-', '_') - - # 组合成最终的工具名称 - tool_name = f"tool_{pinyin_str}_{formatted_id}" - - return tool_name - except Exception as e: - logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True) - # 降级处理:如果拼音转换失败,使用 ID - return f"tool_{tool_id.replace('-', '_')}" - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py deleted file mode 100644 index 626559f..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Schema 生成工具模块 -""" - -from typing import Any, Dict, List - - -def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]: - """ - 从查询配置的参数定义生成 MCP 工具的 inputSchema - - 此函数会保留完整的 JSON Schema 信息,包括: - - type: Schema 类型(通常是 "object") - - required: 必填字段列表 - - properties: 属性定义(包括每个属性的 type, description, format, examples 等) - - description: Schema 的整体描述(如果有) - - 以及其他任何 JSON Schema 标准字段 - - 此函数还会自动添加以下字段(如果原始 parameters 中未定义): - - targetDatabaseName: 目标数据库名称(非必填,默认为空字符串) - - Args: - parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象 - - Returns: - Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象 - - Example: - >>> params = { - ... "type": "object", - ... "required": ["userId", "startTime"], - ... "properties": { - ... "userId": { - ... "type": "integer", - ... "description": "用户的唯一标识符", - ... "examples": [10086] - ... }, - ... "startTime": { - ... "type": "string", - ... "format": "date-time", - ... "description": "查询的起始时间", - ... "examples": ["2023-01-01 00:00:00"] - ... } - ... } - ... } - >>> schema = generate_input_schema(params) - >>> # schema 将包含所有原始信息,包括 format 和 examples - >>> # 同时会自动添加 targetDatabaseName 字段 - """ - # 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用 - # 但确保至少包含 type 和 properties - if not parameters: - # 如果 parameters 为空,返回一个空的 object schema - return { - "type": "object", - "properties": {}, - "required": [] - } - - # 检查 parameters 是否已经是完整的 JSON Schema 格式 - # 完整格式应该有 "type": "object" 和 "properties" 字段 - if "type" in parameters and parameters.get("type") == "object" and "properties" in parameters: - # 已经是完整的 JSON Schema 格式,直接使用 - input_schema = dict(parameters) - else: - # parameters 是简化格式(直接是参数定义),需要转换为 JSON Schema 格式 - # 例如: {"workOrderNumber": {"type": "string", "description": "...", "required": true}} - properties = {} - required = [] - - for param_name, param_def in parameters.items(): - if isinstance(param_def, dict): - # 提取 required 标记 - if param_def.get("required", False): - required.append(param_name) - - # 复制参数定义,但移除 required 字段(它应该在顶层的 required 数组中) - prop_def = {k: v for k, v in param_def.items() if k != "required"} - properties[param_name] = prop_def - - input_schema = { - "type": "object", - "properties": properties, - "required": required - } - - # 确保必需的字段存在 - if "type" not in input_schema: - input_schema["type"] = "object" - - if "properties" not in input_schema: - input_schema["properties"] = {} - - if "required" not in input_schema: - input_schema["required"] = [] - - # 添加 targetDatabaseName 字段(如果不存在) - if "targetDatabaseName" not in input_schema["properties"]: - input_schema["properties"]["targetDatabaseName"] = { - "type": "string", - "description": "目标数据库名称", - "default": "" - } - - # 保留所有其他字段,如 description, examples, format 等 - # JSON Schema 标准支持的字段都会被保留: - # - additionalProperties - # - patternProperties - # - minProperties / maxProperties - # - dependencies - # - 等等 - - return input_schema - - -def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: - """ - 验证 inputSchema 是否符合基本的 JSON Schema 规范 - - Args: - schema: 要验证的 schema 对象 - - Returns: - tuple[bool, str]: (是否有效, 错误消息或成功消息) - - Example: - >>> schema = {"type": "object", "properties": {"id": {"type": "string"}}} - >>> is_valid, msg = validate_input_schema(schema) - >>> print(is_valid, msg) - True, "Schema 验证通过" - """ - if not isinstance(schema, dict): - return False, "Schema 必须是一个字典对象" - - if schema.get("type") != "object": - return False, "Schema 的 type 字段必须是 'object'" - - if "properties" not in schema: - return False, "Schema 必须包含 properties 字段" - - if not isinstance(schema.get("properties"), dict): - return False, "Schema 的 properties 字段必须是一个字典对象" - - # 验证 required 字段(如果存在) - if "required" in schema: - required = schema["required"] - if not isinstance(required, list): - return False, "Schema 的 required 字段必须是一个列表" - - # 验证所有 required 的字段都在 properties 中定义 - properties = schema["properties"] - for field in required: - if field not in properties: - return False, f"必填字段 '{field}' 未在 properties 中定义" - - # 验证 properties 中每个字段的定义 - for prop_name, prop_def in schema["properties"].items(): - if not isinstance(prop_def, dict): - return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" - - if "type" not in prop_def: - return False, f"属性 '{prop_name}' 必须包含 type 字段" - - return True, "Schema 验证通过" - diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock deleted file mode 100644 index 017ec10..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock +++ /dev/null @@ -1,497 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, -] - -[[package]] -name = "anyio" -version = "4.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, -] - -[[package]] -name = "certifi" -version = "2025.10.5" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" }, -] - -[[package]] -name = "click" -version = "8.3.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, -] - -[[package]] -name = "httpx-sse" -version = "0.4.3" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" }, -] - -[[package]] -name = "lzwcai-mcpskills-analyzeWorkOrder" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "httpx" }, - { name = "mcp", extra = ["cli"] }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.28.1" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.10.1" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" }, -] - -[[package]] -name = "mcp" -version = "1.10.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "jsonschema" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-multipart" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" }, -] - -[package.optional-dependencies] -cli = [ - { name = "python-dotenv" }, - { name = "typer" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, -] - -[[package]] -name = "pydantic" -version = "2.12.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.11.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, -] - -[[package]] -name = "python-dotenv" -version = "1.1.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.20" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" }, -] - -[[package]] -name = "rpds-py" -version = "0.27.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" }, - { url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" }, -] - -[[package]] -name = "sse-starlette" -version = "3.0.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" }, -] - -[[package]] -name = "starlette" -version = "0.48.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" }, -] - -[[package]] -name = "typer" -version = "0.19.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, -] - -[[package]] -name = "uvicorn" -version = "0.37.0" -source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" } -wheels = [ - { url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" }, -] diff --git a/lzwcai_mcpskills_mfg_data_agentv2/main.py b/lzwcai_mcpskills_mfg_data_agentv2/main.py deleted file mode 100644 index 420d025..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/main.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Entry point for lzwcai-mcpskills-mfg-data-agentv2 (制造业数据智能体) -Runs the MCP server for manufacturing data intelligence -""" -import os - - -os.environ["databaseId"] = "57" -os.environ["datasourceId"] = "57" -os.environ["backendBaseUrl"] = "http://192.167.30.2:8088" -if __name__ == "__main__": - # Import and run the actual MCP server - from lzwcai_mcpskills_mfg_data_agentv2.main import main - main() diff --git a/lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md b/lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md deleted file mode 100644 index 02b9ec4..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md +++ /dev/null @@ -1,1094 +0,0 @@ -# manufacturing_data_model_v1.0.0 - -## 需求概述 - -本模型基于 `module/` 目录下 52 份业务导出 Excel,面向制造企业端到端经营与生产分析(线索→商机→合同→订单→采购/生产→入出库→质检→开票/收付款/核销→资金流→分佣),抽象形成可扩展的分析型数据模型。 - -设计目标: - -- 兼容 ISA-95 / IEC 62264:区分 Level 3(生产执行)与 Level 4(经营管理)数据边界 -- 分析型优先:星型模型 + 事实星座(Conformed Dimensions) -- 主数据一致性:维度表按 SCD Type 2 管理(`RowValidFrom/RowValidTo/IsCurrent`) -- 事务可追溯:事实表以 `EventTimeUTC` 作为事件时间分区/水印字段 - -范围说明: - -- 当前 Excel 以“编号/名称”作为主要业务键(例如 `产品编码`、`合同编号`、`工单编号`)。分析层统一引入代理主键 `{Entity}Id`。 -- 多数单据缺少“行明细”粒度(例如销售订单/采购订单仅见单头字段),模型保留单头事实为主,后续如补充明细可平滑扩展。 - -## 术语表 - -| 术语 | 含义 | -|---|---| -| 业务键(Business Key) | 来自源系统的编码/编号/单号等唯一标识,如 `ProductCode`、`ContractNumber` | -| 代理键(Surrogate Key) | 分析层自增/雪花主键,如 `ProductId` | -| SCD Type 2 | 维度历史保留:通过 `RowValidFrom/RowValidTo/IsCurrent` 记录有效期 | -| 退化维度(Degenerate Dimension) | 直接保存在事实表中的单据号、状态等维度属性 | -| EventTimeUTC | 统一事件时间(UTC),作为事实表分区/增量水印字段 | - -## 主题域总览(ISA-95 视角) - -| 主题域 | ISA-95 层级 | 覆盖 Excel | -|---|---|---| -| 主数据(产品/BOM/工艺/资源) | L3/L4 共用 | 产品列表、物料清单、工艺路线、工序、仓库、供应商、客户、设备、质检原因 | -| 生产执行 | Level 3 | 工单、任务、报工、装配工单 | -| 质量 | Level 3 | 质检记录、质检不通过原因 | -| 库存与仓储作业 | Level 3 | 出入库明细、盘点、调拨、其他出入库、报废 | -| 销售与履约 | Level 4 | 销售报价、销售订单、销售出库、销售退货 | -| 采购与供应 | Level 4 | 采购申请、采购订单、采购入库、采购退货 | -| 财务与资金 | Level 4 | 发票、回款、收款/付款、预收/预付、核销、资金流向、资金转账、其他收支、分佣 | -| CRM(市场到合同) | Level 4 | 线索、商机、合同 | - -## 实体清单(维度/事实) - -维度(SCD2): - -- `DimPerson`(人员维度) -- `DimProduct`(产品维度) -- `DimCustomer`(客户维度) -- `DimSupplier`(供应商维度) -- `DimWarehouse`(仓库维度) -- `DimBom`(物料清单维度) -- `DimRouting`(工艺路线维度) -- `DimOperation`(工序维度) -- `DimEquipment`(设备维度) -- `DimQcReason`(质检不通过原因维度) -- `DimContract`(合同维度,可选:合同也可作为事实) - -事实(按 `EventTimeUTC` 分区): - -- `FactInventoryMovement`(出入库流水事实) -- `FactWorkOrder`(工单事实) -- `FactOperationTask`(工序任务事实) -- `FactLaborReport`(报工事实) -- `FactQualityInspection`(质检事实) -- `FactSalesOrder`(销售订单事实) -- `FactSalesShipment`(销售出库事实) -- `FactSalesReturn`(销售退货事实) -- `FactPurchaseRequest`(采购申请事实) -- `FactPurchaseOrder`(采购订单事实) -- `FactPurchaseReceipt`(采购入库事实) -- `FactPurchaseReturn`(采购退货事实) -- `FactInventoryTransfer`(库存调拨事实) -- `FactInventoryCount`(库存盘点事实) -- `FactScrap`(报废事实) -- `FactArReceipt`(应收收款事实) -- `FactApPayment`(应付付款事实) -- `FactInvoice`(开票申请事实) -- `FactWriteOff`(核销事实) -- `FactCashFlow`(资金流水事实) -- `FactCommission`(分佣事实) -- `FactLead`(线索事实) -- `FactOpportunity`(商机事实) -- `FactProductionPlan`(生产计划/装配工单汇总事实,可选) - -## ER 图(事实星座) - -```mermaid -erDiagram - DimProduct ||--o{ FactInventoryMovement : "by_product (关联产品)" - DimWarehouse ||--o{ FactInventoryMovement : "by_warehouse (关联仓库)" - DimBom ||--o{ FactInventoryMovement : "by_bom (关联BOM)" - DimPerson ||--o{ FactInventoryMovement : "operated_by (操作人)" - - DimProduct ||--o{ FactWorkOrder : "produces (生产产品)" - DimSupplier ||--o{ FactWorkOrder : "outsourced_to (委外供应商)" - DimRouting ||--o{ FactWorkOrder : "uses_routing (使用工艺)" - DimPerson ||--o{ FactWorkOrder : "managed_by (生产主管)" - - DimOperation ||--o{ FactOperationTask : "executes (执行工序)" - DimProduct ||--o{ FactOperationTask : "for_product (关联产品)" - DimPerson ||--o{ FactOperationTask : "worker (执行工人)" - - DimProduct ||--o{ FactLaborReport : "reports_on (报工产品)" - DimOperation ||--o{ FactLaborReport : "at_operation (报工工序)" - DimPerson ||--o{ FactLaborReport : "reported_by (报工人员)" - - DimQcReason ||--o{ FactQualityInspection : "fail_reason (不合格原因)" - DimProduct ||--o{ FactQualityInspection : "inspects (质检产品)" - DimPerson ||--o{ FactQualityInspection : "inspected_by (质检员)" - - DimCustomer ||--o{ FactSalesOrder : "buys (购买)" - DimPerson ||--o{ FactSalesOrder : "sold_by (销售员)" - DimCustomer ||--o{ FactSalesShipment : "receives (收货)" - DimPerson ||--o{ FactSalesShipment : "shipped_by (发货员)" - DimCustomer ||--o{ FactSalesReturn : "returns (退货)" - DimPerson ||--o{ FactSalesReturn : "handled_by (处理人)" - - DimSupplier ||--o{ FactPurchaseOrder : "supplies (供应)" - DimPerson ||--o{ FactPurchaseOrder : "purchased_by (采购员)" - DimSupplier ||--o{ FactPurchaseReceipt : "delivers (送货)" - DimPerson ||--o{ FactPurchaseReceipt : "received_by (收货员)" - DimSupplier ||--o{ FactPurchaseReturn : "takes_back (退回)" - - DimContract ||--o{ FactInvoice : "invoiced_for (开票合同)" - DimContract ||--o{ FactArReceipt : "cash_in (收款合同)" - DimContract ||--o{ FactApPayment : "cash_out (付款合同)" - DimContract ||--o{ FactWriteOff : "writeoff (核销合同)" - DimPerson ||--o{ FactContract : "owned_by (合同负责人)" - DimPerson ||--o{ FactLead : "owned_by (线索负责人)" - DimPerson ||--o{ FactOpportunity : "owned_by (商机负责人)" -``` - -## 业务数据流转全景图 - -本流程图展示了从商机线索到最终产品交付的端到端数据流转过程,以及各环节涉及的关键数据实体。 - -```mermaid -flowchart TD - %% 节点样式定义 - classDef process fill:#e1f5fe,stroke:#01579b,stroke-width:2px; - classDef decision fill:#fff9c4,stroke:#fbc02d,stroke-width:2px; - classDef table fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px,stroke-dasharray: 5 5; - - %% 1. CRM阶段 - subgraph Stage_CRM [CRM与销售阶段] - direction TB - Node_Lead[("线索处理
In: FactLead
Logic: 线索清洗/转化")]:::process - Node_Opp[("商机确认
In: FactLead
Out: FactOpportunity")]:::process - Decision_Win{商机赢单?}:::decision - Node_Contract[("合同签订
In: FactOpportunity
Out: DimContract")]:::process - Node_SO[("销售订单生成
In: DimContract
Out: FactSalesOrder")]:::process - - Table_Lead[("FactLead")]:::table - Table_Opp[("FactOpportunity")]:::table - Table_SO[("FactSalesOrder")]:::table - end - - %% 2. 计划阶段 - subgraph Stage_Plan [计划与排程阶段] - direction TB - Node_MPS[("主生产计划(MPS)
In: FactSalesOrder
Logic: 需求合并/产能平衡
Out: FactProductionPlan")]:::process - Node_MRP[("MRP运算/BOM展开
In: FactProductionPlan, DimBom
Logic: 净需求计算
Out: FactWorkOrder(Planned)")]:::process - Decision_MakeBuy{自制/外购?}:::decision - Node_PR[("采购申请生成
Out: FactPurchaseRequest")]:::process - - Table_Plan[("FactProductionPlan")]:::table - Table_Bom[("DimBom")]:::table - Table_Stock[("FactInventoryMovement
(当前库存)")]:::table - end - - %% 新增:3. 采购与供应阶段 - subgraph Stage_Procure [采购与供应阶段] - direction TB - Node_PO[("采购订单(PO)
In: FactPurchaseRequest
Out: FactPurchaseOrder")]:::process - Node_Receive[("收货与IQC
Logic: 验收/质检
Out: FactQualityInspection")]:::process - Decision_IQC{IQC合格?}:::decision - Node_Return[("采购退货
Out: FactPurchaseReturn")]:::process - Node_Mat_In[("原材料入库
Logic: 批次录入/更新库存
Out: FactPurchaseReceipt
FactInventoryMovement")]:::process - Node_AP[("应付挂账(AP)
Logic: 三单匹配
Out: FactApPayment")]:::process - - Table_PO[("FactPurchaseOrder")]:::table - Table_Receipt[("FactPurchaseReceipt")]:::table - end - - %% 4. 生产执行阶段 (原 Stage_Make) - subgraph Stage_Make [生产执行阶段] - direction TB - Node_WO_Release[("工单下达
In: FactWorkOrder(Planned)
Logic: 物料齐套/锁定资源
Out: FactWorkOrder(Released)")]:::process - Node_Task[("工序任务派工
In: FactWorkOrder, DimRouting
Out: FactOperationTask")]:::process - Node_Labor[("生产报工
In: FactOperationTask
Logic: 投入/产出/工时记录
Out: FactLaborReport")]:::process - - Table_WO[("FactWorkOrder")]:::table - Table_Task[("FactOperationTask")]:::table - Table_Labor[("FactLaborReport")]:::table - end - - %% 5. 质量与入库阶段 (原 Stage_QC) - subgraph Stage_QC [质量与仓储阶段] - direction TB - Node_QC[("成品检验(FQC)
In: FactLaborReport
Logic: 检验标准比对
Out: FactQualityInspection")]:::process - Decision_Pass{质检合格?}:::decision - Node_Scrap[("报废处理
Logic: 记录不良原因
Out: FactScrap")]:::process - Node_FG_In[("成品入库
In: FactWorkOrder(Closed)
Logic: 更新库存余额
Out: FactInventoryMovement(IN)")]:::process - - Table_QC[("FactQualityInspection")]:::table - Table_Inv[("FactInventoryMovement")]:::table - end - - %% 6. 交付阶段 (原 Stage_Deliver) - subgraph Stage_Deliver [交付阶段] - direction TB - Node_Ship[("销售发货
In: FactSalesOrder, FactInventoryMovement
Logic: 拣货/出库扣减
Out: FactSalesShipment")]:::process - Node_End((交付完成)) - - Table_Ship[("FactSalesShipment")]:::table - end - - %% 新增:7. 财务与资金阶段 - subgraph Stage_Finance [财务与资金阶段] - direction TB - %% AR Flow - Node_Invoice[("销售开票
In: FactSalesShipment
Out: FactInvoice")]:::process - Node_ArRec[("销售收款
Out: FactArReceipt")]:::process - Node_ArOff[("应收核销
Logic: 核销发票与收款
Out: FactWriteOff")]:::process - - %% AP Flow - Node_ApPay[("采购付款
Out: FactApPayment")]:::process - Node_ApOff[("应付核销
Logic: 核销应付与付款
Out: FactWriteOff")]:::process - - %% Cash Flow - Node_Cash[("资金流水
Logic: 账户变动记录
Out: FactCashFlow")]:::process - - Table_Inv[("FactInvoice")]:::table - Table_Ar[("FactArReceipt")]:::table - Table_Ap[("FactApPayment")]:::table - Table_Cash[("FactCashFlow")]:::table - end - - %% 关系连线 - Node_Lead --> Node_Opp - Node_Opp --> Decision_Win - Decision_Win -- Yes --> Node_Contract - Node_Contract --> Node_SO - Node_SO --> Node_MPS - - Node_MPS --> Node_MRP - Node_MRP --> Decision_MakeBuy - Decision_MakeBuy -- 外购 --> Node_PR - Decision_MakeBuy -- 自制 --> Node_WO_Release - - %% 采购流程连线 - Node_PR --> Node_PO - Node_PO --> Node_Receive - Node_Receive --> Decision_IQC - Decision_IQC -- No --> Node_Return - Decision_IQC -- Yes --> Node_Mat_In - Node_Mat_In --> Node_AP - Node_Mat_In --> Node_WO_Release - - Node_WO_Release --> Node_Task - Node_Task --> Node_Labor - Node_Labor --> Node_QC - - Node_QC --> Decision_Pass - Decision_Pass -- No --> Node_Scrap - Decision_Pass -- Yes --> Node_FG_In - - Node_FG_In --> Node_Ship - Node_Ship --> Node_End - - %% 财务流程连线 - Node_Ship --> Node_Invoice - Node_Invoice --> Node_ArRec - Node_ArRec --> Node_ArOff - - Node_AP --> Node_ApPay - Node_ApPay --> Node_ApOff - - Node_ArRec --> Node_Cash - Node_ApPay --> Node_Cash - - %% 数据表关联(虚线) - Table_Lead -.-> Node_Lead - Node_Opp -.-> Table_Opp - Node_Contract -.-> Table_SO - Table_SO -.-> Node_MPS - Node_MPS -.-> Table_Plan - Table_Plan -.-> Node_MRP - Table_Bom -.-> Node_MRP - Table_Stock -.-> Node_MRP - - Node_PR -.-> Table_PO - Node_PO -.-> Table_PO - Node_Mat_In -.-> Table_Receipt - Node_Mat_In -.-> Table_Inv - - Node_WO_Release -.-> Table_WO - Node_Task -.-> Table_Task - Node_Labor -.-> Table_Labor - Node_QC -.-> Table_QC - Node_Receive -.-> Table_QC - Node_FG_In -.-> Table_Inv - Node_Ship -.-> Table_Ship - - Node_Invoice -.-> Table_Inv - Node_ArRec -.-> Table_Ar - Node_ApPay -.-> Table_Ap - Node_Cash -.-> Table_Cash -``` - -### 关键业务规则说明 - -1. **商机转订单**: - * 当 `FactOpportunity` 状态确认为“赢单”且 `DimContract` 签订后,系统生成 `FactSalesOrder`。 - * 此时确立销售对象(Customer)、产品(Product)及交付日期(DeliveryDate)。 - -2. **计划运算 (MPS/MRP)**: - * `FactProductionPlan` 汇总销售订单需求,结合 `FactInventoryMovement` 计算出的当前库存快照,进行净需求计算。 - * 利用 `DimBom` 展开物料清单,区分自制件(生成 `FactWorkOrder`)与采购件(生成 `FactPurchaseRequest`)。 - -3. **采购闭环管理**: - * **采购订单 (PO)**:`FactPurchaseOrder` 经审批后生效,明确供应商、价格、交期。 - * **入库验收 (IQC)**:物料到达后必须进行 IQC(`FactQualityInspection`),合格品方可办理入库(`FactPurchaseReceipt`)并增加库存(`FactInventoryMovement`)。 - * **财务对接 (AP)**:系统自动匹配 PO(采购单)、Receipt(入库单)和供应商发票,生成应付账款(`FactApPayment`),确保“三单一致”。 - * **供应商管理**:IQC 合格率与交期准确率回写至 `DimSupplier`,用于季度评估与优选名录维护。 - -4. **生产执行闭环**: - * 工单下达前需校验物料齐套性(依赖采购入库的 `FactInventoryMovement`)。 - * 现场工人通过 `FactLaborReport` 上报实际投入工时与产出数量,该数据是成本核算的核心依据。 - -5. **质量控制 (FQC)**: - * `FactQualityInspection` 记录成品检验结果。 - * 质检合格触发成品入库,不合格则根据原因(`DimQcReason`)触发返工或报废(`FactScrap`)。 - -6. **库存与交付**: - * 所有实物库存变动(入库、出库、报废)统一写入 `FactInventoryMovement`,确保库存台账的一致性。 - * `FactSalesShipment` 记录发货动作,作为收入确认(Revenue Recognition)的前置事件。 - -7. **财务资金流转**: - * **应收 (AR)**:销售发货后生成发票申请(`FactInvoice`),回款后(`FactArReceipt`)进行核销(`FactWriteOff`)。 - * **应付 (AP)**:采购入库确认应付后(`FactApPayment` 中的挂账状态),执行付款(`FactApPayment` 中的实付)并核销。 - * **资金 (Cash)**:所有收付款动作最终汇总至 `FactCashFlow`,提供实时的资金日记账分析。 - -## 表结构详设 - -类型说明(可按目标数据库微调): - -- `bigint`:代理主键/外键 -- `varchar(n)`:业务键、名称、状态等 -- `decimal(18,4)`:数量与金额(金额可按需要改 `decimal(18,2)`) -- `datetime2`:统一时间字段(UTC) - -### DimPerson(人员维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PersonId | bigint | N | autoincr | 主键 | | - | PersonCode | varchar(64) | N | | 人员编码(业务键) | 来源:用户ID/工号 | - | PersonName | varchar(255) | N | | 姓名 | | - | Department | varchar(100) | Y | | 部门 | | - | Position | varchar(100) | Y | | 职位/岗位 | | - | Phone | varchar(32) | Y | | 电话 | | - | Email | varchar(100) | Y | | 邮箱 | | - | Status | varchar(20) | Y | | 状态 | ACTIVE, INACTIVE | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | -| Birthday | date | Y | | 出生日期 | | -| Employment_day | date | Y | | 入职日期 | | -| Job_description | varchar(255) | Y | | 职务描述 | | - -### DimProduct(产品维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ProductId | bigint | N | autoincr | 主键 | | - | ProductCode | varchar(64) | N | | 产品编码(业务键) | 来源:产品列表.产品编码 | - | ProductName | varchar(255) | N | | 产品名称 | | - | ProductSpec | varchar(255) | Y | | 产品规格 | | - | ProductCategory | varchar(100) | Y | | 产品类别 | | - | ProductType | varchar(100) | Y | | 产品类型 | | - | UomCode | varchar(16) | Y | | 主单位 | kg,pcs,L,m | - | Brand | varchar(100) | Y | | 品牌 | | - | Origin | varchar(100) | Y | | 产地 | | - | TransportCondition | varchar(100) | Y | | 运输条件 | | - | Barcode | varchar(64) | Y | | 条形码 | | - | DrawingNumber | varchar(64) | Y | | 图号 | | - | Material | varchar(100) | Y | | 材料 | | - | SurfaceTreatment | varchar(100) | Y | | 表面处理 | | - | MinStockQty | decimal(18,4) | Y | | 最低库存数量 | | - | SafetyStockQty | decimal(18,4) | Y | | 安全库存数量 | | - | MaxStockQty | decimal(18,4) | Y | | 最高库存数量 | | - | SourceSystem | varchar(50) | Y | | 数据来源 | 来源:数据来源 | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimCustomer(客户维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| CustomerId | bigint | N | autoincr | 主键 | | - | CustomerName | varchar(255) | N | | 客户名称(业务键) | 来源:客户列表.客户名称 | - | CustomerSource | varchar(100) | Y | | 客户来源 | | - | Phone | varchar(32) | Y | | 手机 | | - | Telephone | varchar(32) | Y | | 电话 | | - | Email | varchar(100) | Y | | 邮箱 | | - | CustomerLevel | varchar(50) | Y | | 客户级别 | | - | Industry | varchar(100) | Y | | 客户行业 | | - | Address | varchar(500) | Y | | 详细地址 | | - | IsLocked | bit | Y | | 锁定状态 | 0,1 | - | DealStatus | varchar(50) | Y | | 成交状态 | | - | LastFollowUpTimeUTC | datetime2 | Y | | 最后跟进时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimSupplier(供应商维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SupplierId | bigint | N | autoincr | 主键 | | - | SupplierCode | varchar(64) | N | | 供应商编码(业务键) | 来源:供应商列表.供应商编码 | - | SupplierName | varchar(255) | N | | 供应商名称 | | - | SupplierCategory | varchar(100) | Y | | 供应商分类 | | - | SupplierLevel | varchar(50) | Y | | 供应商级别 | | - | Address | varchar(500) | Y | | 地址 | | - | DefaultCurrencyCode | varchar(16) | Y | | 默认币别 | CNY,USD,EUR | - | SettlementTerm | varchar(50) | Y | | 结算期限 | | - | ContractExpiryDateUTC | datetime2 | Y | | 合同到期日(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimWarehouse(仓库维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| WarehouseId | bigint | N | autoincr | 主键 | | - | WarehouseCode | varchar(64) | N | | 仓库编码(业务键) | 来源:仓库列表.仓库编码 | - | WarehouseName | varchar(255) | N | | 仓库名称 | | - | WarehouseType | varchar(50) | Y | | 仓库类型 | | - | Status | varchar(20) | Y | | 仓库状态 | ENABLED,DISABLED | - | Address | varchar(500) | Y | | 仓库地址 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimBom(物料清单维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| BomId | bigint | N | autoincr | 主键 | | - | BomNumber | varchar(64) | N | | 清单编号(业务键) | 来源:物料清单.清单编号 | - | BomTopic | varchar(100) | Y | | 清单主题 | 来源:清单主题 | -| IsPrimary | bit | Y | | 主辅清单标识 | 0,1 | -| LevelNumber | int | Y | | 层级 | | - | ProductCode | varchar(64) | Y | | 关联产品编码 | 来源:产品编号 | - | UnitUsageQty | decimal(18,4) | Y | | 单位用量 | | - | UomCode | varchar(16) | Y | | 产品单位 | | - | SupplierName | varchar(255) | Y | | 默认供应商(退化属性) | 来源:供应商 | -| Remark | varchar(500) | Y | | 备注 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimRouting(工艺路线维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| RoutingId | bigint | N | autoincr | 主键 | | - | RoutingNumber | varchar(64) | N | | 工艺路线编号(业务键) | 来源:工艺路线.工艺路线编号 | - | RoutingName | varchar(255) | N | | 工艺路线名称 | | - | OperationListText | varchar(2000) | Y | | 工序/工艺路线列表(原文) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimOperation(工序维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| OperationId | bigint | N | autoincr | 主键 | | - | OperationNumber | varchar(64) | N | | 工序编号(业务键) | 来源:工序.工序编号 | - | OperationName | varchar(255) | N | | 工序名称 | | - | ReportPermission | varchar(50) | Y | | 报工权限 | | - | ReportRatio | decimal(18,6) | Y | | 报工数配比 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimEquipment(设备维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| EquipmentId | bigint | N | autoincr | 主键 | | - | EquipmentNumber | varchar(64) | N | | 设备编号(业务键) | 来源:设备.设备编号 | - | EquipmentName | varchar(255) | N | | 设备名称 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimQcReason(质检不通过原因维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| QcReasonId | bigint | N | autoincr | 主键 | | - | QcReasonNumber | varchar(64) | N | | 不通过原因编号(业务键) | 来源:质检原因.不通过原因编号 | - | QcReasonName | varchar(255) | N | | 不通过原因 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### DimContract(合同维度) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ContractId | bigint | N | autoincr | 主键 | | - | ContractNumber | varchar(64) | N | | 合同编号(业务键) | 来源:合同.合同编号 | - | ContractName | varchar(255) | Y | | 合同名称 | | - | OwnerId | bigint | Y | | 负责人 (外键 → DimPerson) | | - | CustomerName | varchar(255) | Y | | 客户名称(退化) | | - | CustomerId | bigint | Y | | 关联客户(外键 → DimCustomer) | | - | ContractAmount | decimal(18,4) | Y | | 合同金额 | | - | OrderTimeUTC | datetime2 | Y | | 下单时间(UTC) | | -| PaidAmount | decimal(18,4) | Y | | 回款金额 | | -| UnpaidAmount | decimal(18,4) | Y | | 未回款金额 | | -| Stage | varchar(50) | Y | | 当前阶段 | | -| Result | varchar(50) | Y | | 结果 | | - | ContractType | varchar(50) | Y | | 合同类型 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | -| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | -| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInventoryMovement(出入库流水事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InventoryMovementId | bigint | N | autoincr | 主键 | | - | EventTimeUTC | datetime2 | N | | 出入库事件时间(UTC) | 来源:出入库日期/入库时间/出库时间 | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | - | WarehouseId | bigint | Y | | 关联DimWarehouse (外键 → DimWarehouse) | | - | BomId | bigint | Y | | 关联DimBom (外键 → DimBom) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(操作人) | | -|SalesOrderId | bigint | Y | | 关联FactSalesOrder (外键 → FactSalesOrder) | | -|PurchaseOrderId | bigint | Y | | 关联FactPurchaseOrder (外键 → FactPurchaseOrder) | | - | DocNumber | varchar(64) | N | | 出入库单号 | 来源:出入库单号/入库单编号/出库单编号 | - | DocType | varchar(30) | N | | 单据类型 | IN,OUT,TRANSFER,OTHER_IN,OTHER_OUT | - | MovementQty | decimal(18,4) | N | 0 | 出入库数量(入为正,出为负) | | -| UomCode | varchar(16) | Y | | 单位 | | -| UnitPrice | decimal(18,4) | Y | | 单价 | | -| DiscountUnitPrice | decimal(18,4) | Y | | 折后单价 | | -| Amount | decimal(18,4) | Y | | 总价 | | -| DiscountAmount | decimal(18,4) | Y | | 折后总价 | | -| OnhandQtyAfter | decimal(18,4) | Y | | 发生后库存量(源字段) | | -| ResponsiblePerson | varchar(100) | Y | | 负责人 | | -| AuditStatus | varchar(20) | Y | | 审核状态 | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactWorkOrder(工单事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| WorkOrderId | bigint | N | autoincr | 主键 | | - | WorkOrderNumber | varchar(64) | N | | 工单编号(退化维度) | 来源:工单编号 | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| SupplierId | bigint | Y | | 外键 → DimSupplier(委外时) | | -| RoutingId | bigint | Y | | 外键 → DimRouting | | -| ProductionManagerId | bigint | Y | | 外键 → DimPerson(生产主管) | | - | PlannedQty | decimal(18,4) | Y | | 计划生产数 | | - | CompletedQty | decimal(18,4) | Y | | 完成数 | | -| OperationGoodQty | decimal(18,4) | Y | | 工序良品数 | | -| OperationBadQty | decimal(18,4) | Y | | 工序不良品数 | | -| QcGoodQty | decimal(18,4) | Y | | 工单质检良品数 | | -| QcBadQty | decimal(18,4) | Y | | 工单质检不良品数 | | - | Status | varchar(30) | Y | | 工单状态 | OPEN,STARTED,CLOSED,SCRAP | - | EventTimeUTC | datetime2 | N | | 工单状态变更/抽取时间(UTC) | | -| SourceDocNumber | varchar(64) | Y | | 单据编号(退化) | 来源:单据编号 | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactOperationTask(工序任务事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| OperationTaskId | bigint | N | autoincr | 主键 | | -| WorkOrderNumber | varchar(64) | N | | 工单编号(退化维度) | 来源:工单编号 | -| OperationId | bigint | Y | | 关联DimOperation (外键 → DimOperation) | | -| ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| PlannedStartTimeUTC | datetime2 | Y | | 计划开始时间(UTC) | | -| PlannedEndTimeUTC | datetime2 | Y | | 计划结束时间(UTC) | | -| ActualStartTimeUTC | datetime2 | Y | | 实际开始时间(UTC) | | -| ActualEndTimeUTC | datetime2 | Y | | 实际结束时间(UTC) | | -| PlannedQty | decimal(18,4) | Y | | 计划数 | | -| GoodQty | decimal(18,4) | Y | | 良品数 | | -| BadQty | decimal(18,4) | Y | | 不良品数 | | -| BadReasonText | varchar(500) | Y | | 不良品项(原文) | | -| EventTimeUTC | datetime2 | N | | 任务事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactLaborReport(报工事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| LaborReportId | bigint | N | autoincr | 主键 | | - | WorkOrderNumber | varchar(64) | N | | 工单(退化维度) | 来源:报工列表.工单 | -| TaskName | varchar(255) | Y | | 任务(退化维度) | 来源:报工列表.任务 | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| OperationStatus | varchar(30) | Y | | 工序状态 | | - | WorkerName | varchar(100) | Y | | 生产人员 | | - | ReportQty | decimal(18,4) | N | 0 | 报工数 | | - | GoodQty | decimal(18,4) | Y | | 良品数 | | - | BadQty | decimal(18,4) | Y | | 良品数 | | -| DurationMinutes | decimal(18,2) | Y | | 报工时长(分钟) | | -| UomCode | varchar(16) | Y | | 单位 | | -| BadReasonText | varchar(500) | Y | | 不良品项(原文) | | - | EventTimeUTC | datetime2 | N | | 报工事件时间(UTC) | | -| SupplierId | bigint | Y | | 外键 → DimSupplier(委外时) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactQualityInspection(质检事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| QualityInspectionId | bigint | N | autoincr | 主键 | | - | InspectionNumber | varchar(64) | N | | 质检记录编号(退化维度) | 来源:质检记录编号 | -| InspectionBatchNumber | varchar(64) | Y | | 质检批次号 | | - | WorkOrderNumber | varchar(64) | Y | | 工单(退化维度) | | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | -| SupplierId | bigint | Y | | 外键 → DimSupplier | | -| QcReasonId | bigint | Y | | 外键 → DimQcReason(不通过原因) | | - | InspectorId | bigint | Y | | 质检员 (外键 → DimPerson) | | -| QcType | varchar(30) | Y | | 质检类型 | IN_PROCESS,FINAL,INCOMING | - | PassQty | decimal(18,4) | Y | | 通过数 | | - | FailQty | decimal(18,4) | Y | | 不通过数 | | -| SalesOrderNumber | varchar(64) | Y | | 销售订单(退化维度) | | -| CustomerName | varchar(255) | Y | | 客户(退化维度) | | -| ShipQty | decimal(18,4) | Y | | 发货数 | | - | EventTimeUTC | datetime2 | N | | 质检事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactSalesOrder(销售订单事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SalesOrderId | bigint | N | autoincr | 主键 | | - | SalesOrderNumber | varchar(64) | N | | 订单编号(退化维度) | 来源:销售订单.订单编号 | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SalesPersonId | bigint | Y | | 关联销售员 (外键 → DimPerson) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | - | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | OrderDateUTC | datetime2 | Y | | 订单单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | - | DealAmount | decimal(18,4) | Y | | 成交金额 | | -| PaymentStatus | varchar(30) | Y | | 收款状态/付款状态 | | -| TransportMode | varchar(50) | Y | | 运输方式 | | -| Packaging | varchar(100) | Y | | 包材 | | -| CustomsNumber | varchar(64) | Y | | 报关单号 | | -| ProductionDocNumber | varchar(64) | Y | | 生产单据编号 | | -| Stage | varchar(50) | Y | | 当前阶段 | | - | Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 订单事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactSalesShipment(销售出库事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SalesShipmentId | bigint | N | autoincr | 主键 | | - | ShipmentNumber | varchar(64) | N | | 出库单编号(退化维度) | 来源:销售出库单.出库单编号 | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SalesPersonId | bigint | Y | | 关联销售员 (外键 → DimPerson) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(发货员) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | - | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 出库单单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | -| TransportMode | varchar(50) | Y | | 运输方式 | | -| CustomsNumber | varchar(64) | Y | | 报关单号 | | - | Amount | decimal(18,4) | Y | | 成交金额/单据金额 | | - | EventTimeUTC | datetime2 | N | | 出库事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactSalesReturn(销售退货事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| SalesReturnId | bigint | N | autoincr | 主键 | | - | SalesReturnNumber | varchar(64) | N | | 销售退货单编号(退化维度) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SalesPersonId | bigint | Y | | 销售员 (外键 → DimPerson) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(处理人) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | -| ReturnReason | varchar(255) | Y | | 退货原因 | | -| IsReship | bit | Y | | 是否退货重发 | 0,1 | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | Amount | decimal(18,4) | Y | | 单据金额 | | - | EventTimeUTC | datetime2 | N | | 退货事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseRequest(采购申请事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseRequestId | bigint | N | autoincr | 主键 | | - | PurchaseRequestNumber | varchar(64) | N | | 采购申请单编号(退化维度) | | - | SupplierId | bigint | Y | | 建议供应商 (外键 → DimSupplier) | | - | ApplicantId | bigint | Y | | 申请人 (外键 → DimPerson) | | - | CurrencyCode | varchar(16) | Y | | 币别 | | - | FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | - | TotalAmount | decimal(18,4) | Y | | 采购总价 | | - | Status | varchar(30) | Y | | 采购状态 | | - | RelatedOrderNumber | varchar(64) | Y | | 订单编号(退化) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | EventTimeUTC | datetime2 | N | | 采购申请事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseOrder(采购订单事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseOrderId | bigint | N | autoincr | 主键 | | - | PurchaseOrderNumber | varchar(64) | N | | 采购订单编号(退化维度) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | BuyerId | bigint | Y | | 关联采购员 (外键 → DimPerson) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | -| DeliveryNoteNumber | varchar(64) | Y | | 送货单号 | | -| SettlementDateUTC | datetime2 | Y | | 结算日期(UTC) | | -| SettlementTerm | varchar(50) | Y | | 结算期限 | | - | EventTimeUTC | datetime2 | N | | 采购订单事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseReceipt(采购入库事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseReceiptId | bigint | N | autoincr | 主键 | | - | PurchaseReceiptNumber | varchar(64) | N | | 采购入库单编号(退化维度) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | -| BuyerId | bigint | Y | | 外键 → DimPerson(采购员) | | -| OperatorId | bigint | Y | | 外键 → DimPerson(收货员) | | - | PurchaseOrderNumber | varchar(64) | Y | | 采购订单(退化维度) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | -| ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | Amount | decimal(18,4) | Y | | 成交金额 | | -| EstimatedCost | decimal(18,4) | Y | | 预计采购费用 | | -| ReceiptQtyTotal | decimal(18,4) | Y | | 入库总数 | | -| PaidAmount | decimal(18,4) | Y | | 已付款金额 | | -| UnpaidAmount | decimal(18,4) | Y | | 未付款金额 | | -| PaymentStatus | varchar(30) | Y | | 付款状态 | | -| ReturnedQty | decimal(18,4) | Y | | 已退货数 | | -| NotReturnedQty | decimal(18,4) | Y | | 未退货数 | | -| ReturnStatus | varchar(30) | Y | | 退货状态 | | - | EventTimeUTC | datetime2 | N | | 入库事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactPurchaseReturn(采购退货事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| PurchaseReturnId | bigint | N | autoincr | 主键 | | - | PurchaseReturnNumber | varchar(64) | N | | 采购退货单编号(退化维度) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | BuyerId | bigint | Y | | 采购员 (外键 → DimPerson) | | - | OperatorId | bigint | Y | | 处理人 (外键 → DimPerson) | | - | CurrencyCode | varchar(16) | Y | | 币别 | | - | FxRate | decimal(18,6) | Y | | 汇率 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | ReturnReason | varchar(255) | Y | | 退货原因 | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | - | RelatedOrderNumber | varchar(64) | Y | | 订单号(退化) | | - | EventTimeUTC | datetime2 | N | | 退货事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInventoryTransfer(库存调拨事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InventoryTransferId | bigint | N | autoincr | 主键 | | - | TransferNumber | varchar(64) | N | | 调拨单编号(退化维度) | | - | OperatorId | bigint | Y | | 操作人 (外键 → DimPerson) | | - | TransferName | varchar(255) | Y | | 调拨名称 | | - | Tag | varchar(100) | Y | | 单据标签 | | - | TransferTimeUTC | datetime2 | Y | | 调拨时间(UTC) | | - | PlateNumber | varchar(32) | Y | | 车牌号 | | - | DeliveryMode | varchar(50) | Y | | 交货方式 | | - | EventTimeUTC | datetime2 | N | | 调拨事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInventoryCount(库存盘点事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InventoryCountId | bigint | N | autoincr | 主键 | | - | CountNumber | varchar(64) | N | | 盘点编号(退化维度) | | - | OperatorId | bigint | Y | | 盘点人 (外键 → DimPerson) | | - | WarehouseName | varchar(255) | Y | | 盘点仓库(退化) | | - | CountName | varchar(255) | Y | | 盘点名称 | | - | CountTimeUTC | datetime2 | Y | | 盘点时间(UTC) | | - | EventTimeUTC | datetime2 | N | | 盘点事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactScrap(报废事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ScrapId | bigint | N | autoincr | 主键 | | - | ScrapNumber | varchar(64) | N | | 报废编号(退化维度) | | - | OperatorId | bigint | Y | | 操作人 (外键 → DimPerson) | | - | WarehouseName | varchar(255) | Y | | 仓库(退化) | | - | ScrapTimeUTC | datetime2 | Y | | 报废日期(UTC) | | - | EventTimeUTC | datetime2 | N | | 报废事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactArReceipt(应收收款事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ArReceiptId | bigint | N | autoincr | 主键 | | - | DocNumber | varchar(64) | N | | 单据编号(退化维度) | 来源:收款单/回款/预收/其他收入等 | - | DocType | varchar(30) | N | | 单据类型 | RECEIPT,REFUND,ADVANCE,ADVANCE_REFUND,OTHER_INCOME | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | Amount | decimal(18,4) | Y | | 收款总金额/回款金额/应收金额 | | -| AmountBase | decimal(18,4) | Y | | 本位币金额 | 来源:本位币字段 | -| FeeAmount | decimal(18,4) | Y | | 手续费 | | -| DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | -| WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | -| WrittenOffAmount | decimal(18,4) | Y | | 已核销金额 | | -| UnwrittenOffAmount | decimal(18,4) | Y | | 未核销金额 | | -| RelatedDocNumber | varchar(64) | Y | | 关联单编号/销售订单编号 | | - | EventTimeUTC | datetime2 | N | | 收款事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactApPayment(应付付款事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ApPaymentId | bigint | N | autoincr | 主键 | | - | DocNumber | varchar(64) | N | | 单据编号(退化维度) | 来源:付款单/预付款/其他支出等 | - | DocType | varchar(30) | N | | 单据类型 | PAYMENT,REFUND,ADVANCE,ADVANCE_REFUND,OTHER_EXPENSE | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | -| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | -| CurrencyCode | varchar(16) | Y | | 币别 | | -| FxRate | decimal(18,6) | Y | | 汇率 | | - | Amount | decimal(18,4) | Y | | 付款总金额/应付金额 | | -| AmountBase | decimal(18,4) | Y | | 本位币金额 | | -| FeeAmount | decimal(18,4) | Y | | 手续费 | | -| DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | -| WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | -| WrittenOffAmount | decimal(18,4) | Y | | 已核销金额 | | -| UnwrittenOffAmount | decimal(18,4) | Y | | 未核销金额 | | -| RelatedDocNumber | varchar(64) | Y | | 关联单编号/采购订单编号 | | - | EventTimeUTC | datetime2 | N | | 付款事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactInvoice(开票申请事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| InvoiceId | bigint | N | autoincr | 主键 | | - | InvoiceRequestNumber | varchar(64) | N | | 发票申请编号(退化维度) | 来源:发票申请编号 | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | - | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | -| HandlerId | bigint | Y | | 外键 → DimPerson(经办人) | | -| CustomerId | bigint | Y | | 外键 → DimCustomer | | - | InvoiceAmount | decimal(18,4) | Y | | 开票金额 | | -| ContractAmount | decimal(18,4) | Y | | 合同金额 | | - | InvoiceDateUTC | datetime2 | Y | | 开票日期(UTC) | | - | ActualInvoiceDateUTC | datetime2 | Y | | 开票日期(UTC) | | -| InvoiceType | varchar(50) | Y | | 开票类型 | | -| Stage | varchar(50) | Y | | 当前阶段 | | -| Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 开票事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactWriteOff(核销事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| WriteOffId | bigint | N | autoincr | 主键 | | - | WriteOffNumber | varchar(64) | N | | 核销单编号(退化维度) | 来源:核销单编号 | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | - | HandlerId | bigint | Y | | 经办人 (外键 → DimPerson) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | - | WriteOffType | varchar(30) | Y | | 核销类型 | AR,AP | - | CurrencyCode | varchar(16) | Y | | 币别 | | - | FxRate | decimal(18,6) | Y | | 汇率 | | - | DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | - | WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | - | EventTimeUTC | datetime2 | N | | 核销事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactCashFlow(资金流水事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| CashFlowId | bigint | N | autoincr | 主键 | | - | DocNumber | varchar(64) | Y | | 单据编号(退化维度) | 来源:资金流向.单据编号/转账单编号 | - | DocType | varchar(30) | N | | 单据类型 | CASHFLOW,TRANSFER | - | AccountName | varchar(255) | Y | | 账户名称 | | - | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | - | BusinessType | varchar(100) | Y | | 业务类型 | | - | IncomeAmount | decimal(18,4) | Y | | 收入 | | - | ExpenseAmount | decimal(18,4) | Y | | 支出 | | - | BalanceAmount | decimal(18,4) | Y | | 余额 | | - | Counterparty | varchar(255) | Y | | 往来单位 | | - | Remark | varchar(500) | Y | | 单据备注 | | - | EventTimeUTC | datetime2 | N | | 资金事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactCommission(分佣事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| CommissionId | bigint | N | autoincr | 主键 | | - | CommissionPerson | varchar(100) | N | | 分佣人 | | - | Department | varchar(100) | Y | | 部门 | | - | CommissionRatePct | decimal(9,4) | Y | | 提成比例(%) | | - | Coefficient | decimal(18,6) | Y | | 系数 | | - | ReceivableCommission | decimal(18,4) | Y | | 应收佣金 | | - | ReceivedCommission | decimal(18,4) | Y | | 实收佣金 | | - | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | ProductName | varchar(255) | Y | | 产品名称(退化) | | - | OpportunityName | varchar(255) | Y | | 商机名称(退化) | | - | ContractAmount | decimal(18,4) | Y | | 合同金额 | | - | DealTimeUTC | datetime2 | Y | | 成交时间(UTC) | | - | IsInvoiced | bit | Y | | 是否开票 | 0,1 | - | EventTimeUTC | datetime2 | N | | 分佣事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactLead(线索事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| LeadId | bigint | N | autoincr | 主键 | | - | LeadName | varchar(255) | N | | 线索名称 | | - | OwnerId | bigint | Y | | 负责人 (外键 → DimPerson) | | - | LeadSource | varchar(100) | Y | | 线索来源 | | -| Phone | varchar(32) | Y | | 手机 | | -| Telephone | varchar(32) | Y | | 电话 | | -| Email | varchar(100) | Y | | 邮箱 | | -| Address | varchar(500) | Y | | 地址 | | -| Industry | varchar(100) | Y | | 客户行业 | | -| CustomerLevel | varchar(50) | Y | | 客户级别 | | -| NextContactTimeUTC | datetime2 | Y | | 下次联系时间(UTC) | | -| FollowUpStatus | varchar(50) | Y | | 跟进状态 | | -| IsConverted | bit | Y | | 是否已转化 | 0,1 | -| Stage | varchar(50) | Y | | 当前阶段 | | -| Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 线索事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactOpportunity(商机事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| OpportunityId | bigint | N | autoincr | 主键 | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | OpportunityAmount | decimal(18,4) | Y | | 商机金额 | | - | ExpectedDealDateUTC | datetime2 | Y | | 预计成交日期(UTC) | | -| NextContactTimeUTC | datetime2 | Y | | 下次联系时间(UTC) | | -| StatusGroup | varchar(50) | Y | | 商机状态组 | | -| Stage | varchar(50) | Y | | 当前阶段 | | - | Result | varchar(50) | Y | | 结果 | | - | EventTimeUTC | datetime2 | N | | 商机事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -### FactProductionPlan(生产计划_装配工单汇总事实) -| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | -|---|---|---|---|---|---| -| ProductionPlanId | bigint | N | autoincr | 主键 | | - | AssemblyWorkOrderNumber | varchar(64) | N | | 装配工单编号(退化维度) | 来源:装配工单编号 | - | WorkOrderCount | int | Y | | 工单数 | | - | FinishedCount | int | Y | | 已结束数 | | - | PlannedQty | decimal(18,4) | Y | | 计划数 | | - | CompletedQty | decimal(18,4) | Y | | 完工数 | | - | PlanStatus | varchar(50) | Y | | 生产计划状态 | | - | ProgressText | varchar(100) | Y | | 工单生成/用料生成/单据进度(原文) | | - | CustomerName | varchar(255) | Y | | 客户(退化) | | - | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | - | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | - | SalesOrderId | bigint | Y | | 关联FactSalesOrder (外键 → FactSalesOrder) | | - | EventTimeUTC | datetime2 | N | | 计划事件时间(UTC) | | -| SourceSystem | varchar(50) | Y | | 数据来源 | | -| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | - -## KPI 映射(示例) - -| KPI | 口径 | 表/字段 | -|---|---|---| -| 报工良率 | `sum(GoodQty) / nullif(sum(ReportQty),0)` | `FactLaborReport.GoodQty/ReportQty` | -| 质检良率 | `sum(PassQty) / nullif(sum(PassQty+FailQty),0)` | `FactQualityInspection.PassQty/FailQty` | -| 采购未付款 | `sum(UnpaidAmount)` | `FactPurchaseReceipt.UnpaidAmount` | -| AR 未核销 | `sum(UnwrittenOffAmount)` | `FactArReceipt.UnwrittenOffAmount` | -| AP 未核销 | `sum(UnwrittenOffAmount)` | `FactApPayment.UnwrittenOffAmount` | - - -## 数据字典 - -以下枚举值仅供参考,实际实施时应根据业务系统配置进行调整。 - -### 1. 通用类 (Common) - -| 字典代码 | 字典名称 | 枚举值 (Code: Name) | 适用字段 | -|---|---|---|---| -| **CurrencyCode** | 币种 | CNY: 人民币
USD: 美元
EUR: 欧元
JPY: 日元
HKD: 港币 | `FactSalesOrder.CurrencyCode`
`FactPurchaseOrder.CurrencyCode`
`FactArReceipt.CurrencyCode`
... | -| **UomCode** | 计量单位 | pcs: 个/件
kg: 千克
m: 米
L: 升
box: 箱
set: 套 | `DimProduct.UomCode`
`DimBom.UomCode`
`FactInventoryMovement.UomCode` | -| **Boolean** | 布尔标识 | 0: 否 (No/False)
1: 是 (Yes/True) | `IsCurrent`, `IsLocked`, `IsPrimary`, `IsReship`, `IsConverted`, `IsInvoiced` | - -### 2. 人员与组织 (Person & Org) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **PersonStatus** | 人员状态 | ACTIVE: 在职
INACTIVE: 离职
LEAVE: 休假 | `DimPerson.Status` | -| **Department** | 部门 | SALES: 销售部
MFG: 生产部
PUR: 采购部
WH: 仓储部
FIN: 财务部
QC: 质检部 | `DimPerson.Department` | -| **SupplierLevel** | 供应商等级 | STRATEGIC: 战略供应商
PREFERRED: 优选供应商
APPROVED: 合格供应商
PROBATION: 考察供应商
EXIT: 淘汰供应商 | `DimSupplier.SupplierLevel` | -| **CustomerLevel** | 客户等级 | KA: 重点客户
VIP: 重要客户
NORMAL: 普通客户
POTENTIAL: 潜在客户 | `DimCustomer.CustomerLevel`
`FactLead.CustomerLevel` | -| **Industry** | 行业 | MFG: 制造
RETAIL: 零售
IT: 互联网
FIN: 金融
EDU: 教育 | `DimCustomer.Industry`
`FactLead.Industry` | - -### 3. 生产与库存 (Production & Inventory) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **WorkOrderStatus** | 工单状态 | PLANNED: 计划中
RELEASED: 已下达
STARTED: 生产中
PAUSED: 暂停
COMPLETED: 已完工
CLOSED: 已结案
SCRAP: 报废 | `FactWorkOrder.Status` | -| **MovementType** | 出入库类型 | IN_PURCHASE: 采购入库
IN_FG: 完工入库
IN_RETURN: 销售退货入库
IN_OTHER: 其他入库
OUT_SALES: 销售出库
OUT_MATERIAL: 生产领料
OUT_RETURN: 采购退货出库
OUT_SCRAP: 报废出库
TRANSFER: 调拨 | `FactInventoryMovement.DocType` | -| **WarehouseType** | 仓库类型 | RAW: 原材料仓
FG: 成品仓
WIP: 线边仓/半成品仓
SCRAP: 废品仓
VMI: 供应商库存仓 | `DimWarehouse.WarehouseType` | -| **QcType** | 质检类型 | IQC: 来料检验
IPQC: 制程检验
FQC: 终检
OQC: 出货检验 | `FactQualityInspection.QcType` | -| **ProductType** | 产品类型 | FINISHED: 成品
SEMI: 半成品
RAW: 原材料
SERVICE: 服务 | `DimProduct.ProductType` | -| **TransportCondition** | 运输条件 | NORMAL: 常温
COLD: 冷链
FRAGILE: 易碎
HAZARDOUS: 危险品 | `DimProduct.TransportCondition` | -| **PlanStatus** | 计划状态 | DRAFT: 草稿
CONFIRMED: 已确认
EXECUTING: 执行中
COMPLETED: 已完成 | `FactProductionPlan.PlanStatus` | - -### 4. 销售与采购 (Sales & Purchase) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **LeadSource** | 线索来源 | WEB: 官网
REFERRAL: 推荐
EXHIBITION: 展会
COLD_CALL: 陌拜
OTHER: 其他 | `FactLead.LeadSource` | -| **SalesStage** | 销售阶段 | NEW: 新建
QUOTED: 已报价
NEGOTIATING: 谈判中
WON: 赢单
LOST: 输单 | `FactOpportunity.Stage`
`FactLead.Stage` | -| **OppStatusGroup** | 商机状态组 | OPEN: 开启
WON: 赢单
LOST: 输单 | `FactOpportunity.StatusGroup` | -| **PurchaseStatus** | 采购状态 | DRAFT: 草稿
APPROVED: 已审批
SENT: 已发送
PARTIAL_RECEIVED: 部分收货
RECEIVED: 全部收货
CLOSED: 关闭 | `FactPurchaseRequest.Status` | -| **PaymentStatus** | 支付状态 | UNPAID: 未付款
PARTIAL: 部分付款
PAID: 已付款
OVERDUE: 逾期 | `FactSalesOrder.PaymentStatus`
`FactPurchaseReceipt.PaymentStatus` | -| **TransportMode** | 运输方式 | LAND: 陆运
SEA: 海运
AIR: 空运
RAIL: 铁运
EXPRESS: 快递 | `FactSalesOrder.TransportMode`
`FactSalesShipment.TransportMode` | - -### 5. 财务 (Finance) - -| 字典代码 | 字典名称 | 枚举值 | 适用字段 | -|---|---|---|---| -| **FinDocType** | 财务单据类型 | RECEIPT: 收款单
PAYMENT: 付款单
INVOICE: 发票
REFUND: 退款
ADVANCE: 预收/预付
WRITEOFF: 核销 | `FactArReceipt.DocType`
`FactApPayment.DocType` | -| **WriteOffType** | 核销类型 | AR: 应收核销
AP: 应付核销 | `FactWriteOff.WriteOffType` | -| **InvoiceType** | 发票类型 | VAT_SPECIAL: 增值税专票
VAT_NORMAL: 增值税普票
ELECTRONIC: 电子发票 | `FactInvoice.InvoiceType` | - - -## 变更记录 - -| 版本 | 日期 | 作者 | 变更描述 | 脚本影响 | -|---|---|---|---|---| -| v1.0.0 | 2025-12-25 | GPT-5.2 | 首次输出:维度SCD2 + 事实星座 + ER图 + 字段清单 | 未生成 | -| v1.1.0 | 2025-12-28 | Trae | 新增 DimPerson(人员维度)并关联销售、采购、生产、审批等事实表 | 待生成 | -| v1.2.0 | 2025-12-29 | Trae | 新增「业务数据流转全景图」,包含商机-订单-计划-生产-交付全链路 Mermaid 流程图 | 无 | -| v1.3.0 | 2025-12-29 | Trae | 升级全景图,新增外部采购闭环(PO管理、入库验收IQC、三单匹配AP、供应商管理) | 无 | -| v1.4.0 | 2025-12-29 | Trae | 升级全景图,新增财务与资金阶段(开票Invoice、收付款AP/AR、核销WriteOff、资金CashFlow) | 无 | -| v1.5.0 | 2025-12-30 | Trae | 新增「数据字典」章节,规范化通用类、人员组织、生产库存、销售采购及财务类枚举值 | 无 | diff --git a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml deleted file mode 100644 index 25ea00e..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcpskills-mfg-data-agentv2" -version = "0.1.6" -description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-mcpskills-mfg-data-agentv2 = "lzwcai_mcpskills_mfg_data_agentv2.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_mfg_data_agentv2"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" diff --git a/lzwcai_mcpskills_mfg_data_agentv2/技能清单.md b/lzwcai_mcpskills_mfg_data_agentv2/技能清单.md deleted file mode 100644 index 61e84aa..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/技能清单.md +++ /dev/null @@ -1,10 +0,0 @@ -# 制造业数据智能分析技能清单 - -| 技能名称 | 编码 | 技能描述 | -|---------|------|---------| -| 供应商评估与智能补货 | SupplierEvaluationAndSmartReplenishment | 基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量 | -| 销售BI智能分析平台 | SalesBIIntelligentAnalyticsPlatform | 面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察 | -| 销售业绩智能统计系统 | SalesPerformanceIntelligentStatistics | 基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名 | -| 财务数据分析看板 | FinancialAnalyticsDashboard | 覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解 | -| 智能成本预测模型 | SmartCostPredictionModel | 基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演 | -| 人力资源数据分析 | HumanResourcesAnalytics | 提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析 | diff --git a/lzwcai_mcpskills_template/README.md b/lzwcai_mcpskills_template/README.md deleted file mode 100644 index 7ffaa1f..0000000 --- a/lzwcai_mcpskills_template/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# lzwcai-mcpskills-template - -MCP Server 模板项目,用于快速创建新的 MCP 服务。 - -## 功能 - -- 提供 MCP Server 基础框架 -- 支持动态工具注册 -- 内置日志系统 -- 支持环境变量配置 - -## 安装 - -```bash -pip install lzwcai-mcpskills-template -``` - -## 使用 - -```bash -# 设置环境变量 -export API_KEY="your-api-key" -export BASE_URL="http://your-api-server" - -# 运行 -lzwcai-mcpskills-template -``` - -## 环境变量 - -| 变量名 | 说明 | 默认值 | -|--------|------|--------| -| API_KEY | API密钥 | - | -| BASE_URL | 后端API地址 | http://localhost:8080 | - -## 项目结构 - -``` -lzwcai_mcpskills_template/ -├── main.py # 入口文件 -├── pyproject.toml # 项目配置 -├── README.md # 说明文档 -└── lzwcai_mcpskills_template/ # 核心代码 - ├── main.py # MCP Server 主逻辑 - ├── schema_converter.py # Schema 转换器 - └── utils/ # 工具模块 - ├── __init__.py - ├── api_client.py # API 客户端 - ├── env_config.py # 环境变量配置 - ├── json_helper.py # JSON 工具 - └── logger_config.py # 日志配置 -``` - -## 开发 - -基于此模板创建新项目: - -1. 复制整个目录 -2. 修改 `pyproject.toml` 中的项目名称 -3. 修改 `lzwcai_mcpskills_template` 目录名 -4. 在 `main.py` 中实现你的工具逻辑 - -## License - -MIT diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/.python-version b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/main.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/__pycache__/main.cpython-312.pyc deleted file mode 100644 index b1c8a03a6d62eb52ba019d65281f7425c22794a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14593 zcmcJ0Yjjg(y71n~&i&f^y`)f(AT76Y712@(lwt(|l`1C_vo|Frm%5Vxt?Af798yIo z$4iUiSkBpwa%YiS{Io;b;=541Hx^gOP-^%<#mdVD&OfnGLF+xl#Jos`Bk!x*K97`bh86^ym(-j;Gb zTL!Br=PFo5hX!^gjr>pdwukwCac?R1ftnXG*O1ky*%@i8S)7~?PWb=BY+AMg&Tk1j z6K<%6odxkyu9U6ZJC~w(E!^}{dEYeEu4q@xqPXHpinkFMOhFx;26Z$$9qMRy8r1Ei zdgvo-_EQIxJE{G&eRk+9oQ3|0x-GVC9KVm_ZIc5hCqKBLpM3kw!!y-yWEJ z_p{g+M_1{qY#zVM=~-pFdz^OD_123@2t`KvY z{6Y6_x62uH`+ZNgVH^+u05RB39YoSBaZ{t3DX-|xF9ASd%1Seh?y6h0nQQP zJy4k&;DVtxN1#0r3Nsup7I`}MyPR%^*VPu->-KnHI?mhXaRxc) zu64GxIebnpC+eWN!^7?4JYvzkO%t0CO|Vu!3;~%80OyvKvErP(JnXZzq4#6Fc_h#Q9TGCy&IA9-Mq?XTD;f)vnot^~1w9(Fq;GQsvXPSm*e_}wlpAZl1{w=?7kip(C))7D|A zs%j1ReN}DF;GU+?LpWzTmJ#fjI(H`a)gjwlvN*&_Scl!NDVamH|svI#@3X7f?Gc|Rsk23nJOu-0K5Y<}8P1a~uVc%@QYKvM6qNXiz z1EngwO{rDtxS3KJ8PPxV9Ia7eAF~#K}5lD zYbHd!6i0z1L7@}@5`=mPYFnxz5wUBBosDrA27NRa{+*~1OEBH#tG}$$x)n032?JYLf8C^8P2nV$m%BzhbY0mRTl)L)qyafVjS>|y(A zjBemTry zSMVS`QzRY0-P>#u=s+_!uuF%dBRB0ZtL4&7&@`|JqAn_G$Q2BwGuLXF`#@tsFdZ@rBo*!OrA??k_}01F%Hx6d3GvNXWVK?wUARR6Ew-`aFXf0PqC`Z zD|w&#fRV=rhNW;RSkO3FO1?Fk+D6Ty7Eu9uzk;TA0(yh|mXNt-Q9(p<5WoNh50Hwl@FLt6IR0h?irh6L!*9g?&=)%~0ChP~M)+245k_|%y*t88|?$oShhAAwpSM_cMWYA%UT_$l%~>)^msu@Z~L+K z{;apcJ$Ymr%5L%(w)az0vUZxC(~46R3r?A1th0O1)RBsD(upg;<($s?0XkTayH zaq5^^1*;rnWSWeev^4|wWmR%tR=pP(ByUgS;29JVR&$?mwDLI9FQo;j@_l;fT9OUm{Y(LzHjxq`}g4*H-)ZjjE$(y4h^fxIb*#F+%(33Gra$XTj2 z)1D0yPhgFMCV53N&$%yOFgFoUjD67s00?~GKFWNDegQz;0wU3f$_@3~Q8Dpy!99Lf zWRN)D_y*P%LX~Ig&2zD%@1|QEVyDhd{pq7sHc{yfwD67C5W)5SvrR^NVy}G&xB#;G zjl-bXg^*1WJU?@8^6Wu*!0jMhYqJa|*AJw{Y0(0z}ALvUT|rK=&KF z8-?OkW5(6v1vC1MLp#O_9*JzcW+^xt?hf~@AGOTt4_vX#Lk?p+W)x_HeMa ze65|jR8gye&t92EC5I5Pi8F|P*z&@H_4jv=49|lkfK=x5~ z6WB)s9oOfi7zZRJ!h@|v2F(EcJ@P5amWY$L!VvX>A%mrfU9KcW<^iuWQKO{n1k!4P z`~wZ3Scdf1rzq5E7#Rb|ZAr?6GG{skIY`^pM4OC7-jALC{4Sl6&xh`kTu+k^kMdL~ z7a|a(BrD$a_YTQ+41h+$WS_%cWb^u`ot{ua08;P{C>Pax_oIPensQ)r@f_Htz;cD! zE$@PMFTpSH7(`vvHH{&%|KQ8xmg4@WKW;wRJY*Q1y?V^@sGxl`sx$TEy;e4EDSZ7U zL0ddwE)+@@3^4=ygra4m=H-HRIk^cG-Eib->@~>++F%yEfz~85g@Qzo&(KMy$-c?c zlv^Q>2qr08FsWV0_XD!N;)CREfwZo)Gl^4A@XvIz6GCnoJNHLfs6}FGn;8CmdYdM@ zN)mFAz@*U+qz`tQXW^5^Re_G6?nS5|zZ>da#d@S=fca`FucWu}SmRqybUiW7=npsk zsK4wnogB6 z(WOo)os+asom8sLI%-!Z1v(kvMDm=IQ^D!7N||$XD(=%JsZ$-G1KFTgWgbbDHj?r_ zZQxBNIUAG4S(7t#qGp%W`BLjl@z2&IhfLLm6=7vqHK>(!Q2AVF>d4E?gHBeL!uv2K z_XN1pr^8(sXu%&qyRsWXXz7bZdSiz_pX~0MJkcFHdIoIk5{QMWp>p!=Ph*iG+h#Dz zY)8|tZHllkU~aK5-->m=ka!Lh1ASIjNjXc?K_0ha%!^JTW`cZe|r_k$8X2p|Ba2HL<$F{u}lPco}dAtI|!7b z8qC|Aj}=vDDFwp??f?XE6~O?opF%cm9r~0>UNoi~RDYHr+ zVWO6tqX)VV^r`z5z*-@nU);Or*q*-5(fq0_`He%l=L*jj4$uEaF}kQRVu*%KLP0%u0`cSlV?rjZYGXEc?TBna^ z!HlSP;lV45`xv5?^Y zm46ubk@K*CoqRzGRx?{x(j{vj&8mQ7C2r{r78C4sNj0<53`D1`S zQW#ctg5AVNGhkcknv+JcGGHvLoz5@8o|2S-S>5!ygEUx~k~WqwP#B;zFq0eRk#YJJ zSp7>%ih5VstNgV}MRhW1^F#UFfz?l+1LzjBjap6}DF-PVfu01AvMV}OX(MIYi6M;V zsF*HghgAn@I8W6tRVkYbp%7+bOPLyaB}Em=V6FsJsDoE3#Hj>PSY<=(f3(9m-x3#8GDjZn%vvC7CD;9MaPLkM|5 zF%J}3LajnrZNTEhyBGf=mRLaQhIbb%3xeT1Aj#c#(+q2ZW{{UXGW0K@^S{C`0ImQD zl3`5ZB@thSo*(fa_;cl7=3Sci%}#-9{dVo%I7Pio*DK(&mR^satPP6W6s6jz01lp0 z(A7AOR7I)E1}b8(99_}9qG$c6p`$ZkaZ+in9taNX z7V?&kX_t+g3;Wg#R1V||MGHsGiv;bWxLT{SPT1xTY#&fwRffh9uTg5k=DaZLNz zHM1q6`8%%4`rm=Ia3Tk5bL$?WFBMcld|8#db`^EGh+eybxm;X^>E$$*ub?r#N>^8@ z{90LBH$(aL3>Bs;l~60Hr6)>PIx-5FDoKI68c9+n(T`QKN%bC$wI3)! zkxh=;3M1Vez?iCp*U5zV*FqDqTua3Ab14!0KC{6)rQA~{SwI|;qUL>i88Y-z-KVV) z96J(g>r{u;tck2l9cIF+4%kYk#y1a2&1t0|h(hTMD7B=Og6aaLxln2)rKYfo)f3}} zii`zn4G`@tXro}WJ2hcVn1S=l39CtrL`cQvo=|jZA9R{2k-8N{;=4jv%jON{%R9Q~ z+zZIs3Li8FTX;{;BGR*jE$-B%^-da1Qp;dV!aBBeuuLZIi5o8`{j1aZB0;+=urt!? zGYEsMjjdp34$hLt9;}pinwVuanZ*!9OCkyE92ss7f<2FYh}3VmYZn=zmYthAgS1us z13OM@oq1$?YUCC$@58$0k#+664~#zSP8qHA_h~af&9G$%qQFT)D=MpZ1-42)6JWg6 zgz+jQ#tUM^f(O8tJ{`UQb{B>XD2Q|#AGGVRF>Tj`3WRBP5xe+=veWdSz7JTFiOpk| z4Avx}Vj8spvA|21_tFp=H1sI9O6)ShV7tyweRVAM*{2Dq8oe1}zkW6L$!DMmOr0FC z&8-cz`&@R?QOw~EIPp!`0bT_jzhl3j_plwBjhLBllVn5HGgx?st^kpmI}n7&KjJ`e z2qqV^C@em~BjV>z!M z99|9tL--ajb>aAA-NQ<`wzzs{~F8|*FQf4k3#U2JN4CDpa)$)_4&kCum5M) zK~YaSyS?Cl(D4iR(`%o6YX8E<4K4m!2u<5|?5W?;g2{S}*4EYT#P9C)u5Ftzv2KT_ ze(Tdu)hzaIS<$g#?bdbsYwP!H-B7!y(fdThR&23$(^ET^*7JKewY0Q6`lzUqCeo45 zf;L}^?1#~a8_{~*UT!=3dzi?oV4MCB@8U!>`H@sdF2296TCiXNyiWzd+}Q@N@+}fdoZNx*!21Z04p7!<6JyZKs(=OX)|9+bnj)-sX(BmmB0LLJ=&#n}eA>Pg7 z1@dn}B;V*exD@GF$n#ccJ`^NCD`6zP{^haj=TA(1`s!rwr=TRFw)oqw$qzr8y6~IH zH$O^r_PP&nEYUqDhF`z&!M{kC68oY*_WoDbyAS{H#e0%jgydpp-+?=ceR5>-%sY53 zMsffy&?-IL0Z|tMYbPO(A>{f;lBwHFoNa$qx=n4m_*$=sy-Y z2XZ6oFM0rl>wowPa+Q#~SG@e;i-_ch6Z_;h*WVoa;froKARQ3{QQqZ=Sg7#=-~puE zK*)ykIshM0?e?)C5!#JB(Mf$+X4nqrTqlW&qG~r-q=Gz(q&zZT(b$0CCRbbQy)SSq z%dt<%*PV;K`G$;Iw98zHbxf+s~RFEFp&~(RR^l$JB zT!3>SYB9>4M!`~fMNIw$oE`MOT{ER;Y1}Sy{ot30h8SPp|b5{PSVe-{oP16?nC+3{~tbTZn@a!&Ocgxtq zJ!3|tGE+D8SCJNM;jKDop*A9kk8e|R^{0iWwL#Gtd24q3Wz_FZCfypJ! zO@)?RgNc-)4cTBrz|ujVx1~7=&n51&vR7gg5gB+?gm^Trbf^|qFREUMF0j5d_gsfR z;h$RsvH-^e)AR+MpsqXHoG-XN;HS$4fOs1u??TD*)ttzHS1w8pqH$VdHIItCsFoUu zNUQJ+)xPlRo3difHNb=!`kzWwWl9)r^$YjN4a_+Sf#@7lNi; zz7$*+%Cm240H<$h3>N)QlmYMzqo0&8C>j1o9RBCH0t$bQW9iSNI92!GfoEV*2Q6!@ z^f9`2srsLlkb0+9U;7AnY|ypK)t77xrYj9lerW++yOO!IP>t#3G?uTVG5v^otx0p4 zrq?pY%L)yqbs8w2h9>|U(&PI$9L0kl3}pEo3~$g;3K^dklFwofY?<*nU&au?$CS>U zG^9HpWTs1VdqN}IKfG!N9ttw(ws(z@H0Nb*4W$`0%t4imx8TZ_GE1iKUOv#w&;9DO zT_m;4RHtH)>-L?W{fyr6kJ6=_5S zap{G0H&&~hzIL~t|6f>&Xb;qi!Bz^F*WiF9af-$tz^_t+O=-noZVCNdTZAPxo!5WQycnM3f8JIrW%?_?-4!K z{kb0(oh<4%2-f*yOx3ulIBLo|y0d#{G&{ey`dIaCmC~&4T94CPBKyXe{0ZqjP_V8T zV^&Tuy2A~>YBFOz(+>R|c&N{B=aI?I8d9$$(@pJDV7mg~UB zyPCYp@IK7zF)z_rLPvR|4`NoTv>M;fJ%I%6O)Do{LL$;u>HjCt5R3m}0V)bw{6uRg z==KDv(e23PaRWn;+$$2roNu3O%|#59T$XaAE|RC8T#beh9&u1KfS0tN4-$_)QMImq z?T!tSKLG)cKVm!b615mpOR$H<;c)tZQ4v))0A8{LW)OxXpXOzlYQhM$7V`8>UT8&q z+8dJ}tC0SQx|9{U9}E8lqu*kLMr9&(6M!QSb(aU+9qnq#2YU_wAJ7=UjSBn#aa>8$ z^i4)XGq@H0tca+M$skYKHewxG3Gk>HYx^2#(^Lysqrr>jXehH-Yw<-9npbZ%UcPiOV#_H!S%o@^bdSag$u{4FJ= z$RjoJ#iVi82%R-b=fo8yv^Am#$T>PtrSC4Jo0@`WSt zr(Za<^y-p^5%|+LjHou<)Id`l8)n5$t2vt9OZRPyQzS94J&_o$NhB_=`zG&dL-R;O zv#{%htGoOo@YmoM`29b@jnEB>ACsR=Qm&rfq_`!Gc5{o;OzUsXETD_yH56lvbX-vu z{gu(ax<0O?nBt$~FX_O%e-n-bFRJc4#{NCC>U(D06=q#rhlO_nNG;Em)>l#gR#mY+ LTlw!kVi3j;do9o>DH*`i2gE|pIF$y2mNx@s` zQ}~sgN(wPFOEW4~#j07F)$}N90Nq0~>X+zFEu(>1-Jk{`K+rI&g;_nLBNTY7hp~Y* zGKM275#l=-BU8pKVobe=F~3DI<>yqLWeUV<*|JNNEX_eBLY<2`O`T?#FJ~<5qF)Iv0GPPnmK@shoL@6)^OIc_puub=tVm6=m`_0|VhGjG1^);HM&k>>)vZu`t@Kc6{w zdG@ncGAG`K=@4w!%X>qIVdecl&c;4%&Rl$B_QEL{hUr)z+bz27)8jACynOO@^yKXe zAH#GY>_5cvK=kTZ<_{w|cDJ!1b~`Caxg{_lsW5N9q~=*M%m=!2R>u%T!w=i-ZX>iK zD1!P*S1|$}d_pcMb9nkp_w7^iREF?xcfcblwtuD*5}A zwwH1P#&Q(#ZT~fu^(cZ}ZA8KqAWKC~i;BD~_*`%lj4BDH0x(qsV_>QZm}=hfzvI|THi;lWvuLDoYRq4WI+eBX$=N4Xc( zA<>wp5f|lqJ&Jn7YbmgCp5g#HwfGq7R~|seDCgp^60U^a4i7kt%;{IB$A6zW^CDcs zqL(Q32q8WgVtH|ZP>^N|-z&ID4>KWm)NPkp+&*#o)~E0P>)I*Vk7V9?u?WQA;{`H2 z!jD572(b>g-66XdN8^KtfY(nDWG4?9&4Xe%A>{K8?9PE@d1m6D-MaWmCU$y0zJ722 zE;i6B9)|7Ltywc4PqxP!_K83C`octl`MB`x^I?u>8DJvvVRjxa!H~!W0|Ee^b;#~- zt)tPN>#lQj+3j`q>8mGlj?ylB+&N|#A|ulLi1@-1`-|a_k9FHu=D!m$P@}yt6iAq8 zv~z+dFRGJpl4xvtNi7>*qOoBmBpFH!DH)j(3rMSDU2C=?9do(9Ls1TS-@l z3zE7g$ost_>_j4gM6=-gCAAy?B+a2<(8qcMP8tLP14^nMuTNklYOO@AaVq#KIM+Om z+kM2_%jQ-*up%7cd_KVg#)1nXe&33GRy^Io z0B{mnesyR-vgbygoaRAK4wV<1Ujy=DO$0qSmMF?LAWPLq&Domxqvsz_l{ZG))8&=9 z@w!xb1B|WJBTt-tBHlIHp0Yj`-Icc0jO;wSGaeW{l(MajK9$FBOqH(yG;njCb;jFA zRVmBrXh(XH^$d5Ci|fv>OD$@MZqKTbt}+(MDiu}g=+jv((pqBABsELY4%e_Lu1;u| zeQl^nui7yDbi5;JTzOMxjU7+w9L42yeKP>H3GMO+fc;6`vLf(ngC*V21bo_(#yZTW zWI3r_hGzznI(yo&D!1`XL-nMg{<3wnE9KmH-MJ;<+>&y(Om07va2~qhY8v&YT$`@D zwkBL#Q?Ax8%O+j>hduEF$+8ug)v=^*Y1-k;u>vh0UNyR7vTg%RXp&m{efxu&H`hHn zY?`cIozymi73%Ur0HwAf-k#7jqzx7E>ZD49r6U!Q8r`m<1GT1zaU?flyeKs{}$tnz^Y!@x!`H3K7Wep)klq zuR&UnIdf(D>f1B%D{|2ROSdk+Gd=#{%LJp06$O1DY%(0KNfuP`e@bEGKr~;Hkn@!gk01*; zP6V8z9oEv352-j&0EjGoUVk|S;FrRebt-wBUP3G?L^&R3fBbsf;GR<_={bQ52%M2W^$A^u!adqW{Mz&JJhT1;&q-e2$# zn30@?TpS>Ak;gF$0zAnz_$C+#Sj3$0d^28k-#V}} zcb)5=(m23r>2^>zYM1`j^}6ePXR3D1$qw*MRm+DB)8LklTzq88urgh3A5%;y5~h|( zZ3{TQ@|tvIb=p>WM?>4x(Op?1vMl)q=~Rynt8SQT;tyZ6O_>_fM%#3K>%{(4eFwyV zVS2bHcH|!hQ@VM>@UHkn;P%do31g#Vs!f|8igzZ=E3ccI6XxbI`YZE>tQDALE0M8! zlCJvh+x5t7yKbsWnCieKg1G62>c_TDY)x3VCym>|EyC120blT6R%|UtUzFEuZBl;G zNI@&9!8|=&@BA)t97z7ZD)&;(8TM0x1!{x&>t(v6US6;kX4H9y2M)1V(G}d?5FMeQ zMu8fb(VV0ALgp2I1H>oiy;yO`|3Q#)VXnv72kD?)t|N-qcdnn2Ed@1436so~k7qx) zP^9>t3xEF(5|1d+9^QKlvQ;}U z;PEMTs`$;gk1qycOYRYm{k?$W7P%Vo#aNcV7{Pcm;j@Ho(7v2^nZ0 z!LlKv<&68JJJu6Flrk)jwx%uBBd)Wqcvs4@DrxB$J$hyEy}|LuFBPfg4oH~Ft436eoeOYl_(#pOG$Hg!n%7qz} zWN!PQR*olbd)sXs;hhS}$OeQk&w9KF^t1o=JOMo3zyt>r&ZE;JWQ|~ zfyNI@xTXX1yN2(WU+sM zLD-SPUr_L;7t{t!*dG$!gMTCN3U+7rw43}vChTLMA(7+|9s5=TBMc=g{ z%J_FwnN|Ogq8=G-%_96B+dsa3f}UW;Hzzi9+$B@l!-^)VdTjl;?E`lfL3g75E`DX7 zqbevhCWP16zPtF9U59An@L*C|BYQXVV*L4R)sl6^9fR7Uj<(-1qoq%d_obHXOwhH_ z*5QWO%CBkNa7Ubu9i7xKouZfF;f`2mY~M)d+0OIovHcTkrz*Bh+S(@d+o$Lq^71pW S_DTJ+uV{OAF`{dU-v0qEIt -2026-01-28 09:58:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:142] - 收到 ListTools 请求,当前配置数量: 7 -2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:161] - ListTools 响应: 返回 7 个工具 -2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:58:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_mixed, arguments=None -2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_mixed -2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:58:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_error, arguments=None -2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - ERROR - [main.py:190] - 工具执行失败: 这是一个演示用的模拟错误!执行被中断。 -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 186, in handle_call_tool - result_contents = execute_tool(name, arguments or {}, tool_config) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 259, in execute_tool - raise ValueError("这是一个演示用的模拟错误!执行被中断。") -ValueError: 这是一个演示用的模拟错误!执行被中断。 -2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:59:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_resource, arguments=None -2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_resource -2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:59:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None -2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image -2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:59:09 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None -2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image -2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 10:02:13 - root - INFO - [logger_config.py:112] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\logs -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'mcpskills_template_server' -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:322] - ================================================== -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:323] - MCP Skills Template Server 启动 -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:324] - ================================================== -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:329] - 命令行参数: {'mode': 'local', 'json_path': None} -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:332] - 使用模式: local -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:83] - 从本地加载 8 条配置: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\tools_config.json -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:137] - 已加载 8 个工具配置 -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:339] - 开始运行 MCP Server (stdio 模式) -2026-01-28 10:02:13 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 10:02:40 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:143] - 收到 ListTools 请求,当前配置数量: 8 -2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:162] - ListTools 响应: 返回 8 个工具 -2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 10:02:44 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_complex, arguments={"user_info": {}} -2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_complex -2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 10:02:50 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_mixed, arguments=None -2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_mixed -2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log deleted file mode 100644 index 14e452d..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_daily.log +++ /dev/null @@ -1,89 +0,0 @@ -2026-01-28 09:58:33 - root - INFO - [logger_config.py:112] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\logs -2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'mcpskills_template_server' -2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:304] - ================================================== -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:305] - MCP Skills Template Server 启动 -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:306] - ================================================== -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:311] - 命令行参数: {'mode': 'local', 'json_path': None} -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:314] - 使用模式: local -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:82] - 从本地加载 7 条配置: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\tools_config.json -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:136] - 已加载 7 个工具配置 -2026-01-28 09:58:33 - lzwcai_mcpskills_template.main - INFO - [main.py:321] - 开始运行 MCP Server (stdio 模式) -2026-01-28 09:58:33 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2026-01-28 09:58:33 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:58:34 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:142] - 收到 ListTools 请求,当前配置数量: 7 -2026-01-28 09:58:34 - lzwcai_mcpskills_template.main - INFO - [main.py:161] - ListTools 响应: 返回 7 个工具 -2026-01-28 09:58:34 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:58:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_mixed, arguments=None -2026-01-28 09:58:36 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_mixed -2026-01-28 09:58:36 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:58:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_error, arguments=None -2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - ERROR - [main.py:190] - 工具执行失败: 这是一个演示用的模拟错误!执行被中断。 -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 186, in handle_call_tool - result_contents = execute_tool(name, arguments or {}, tool_config) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 259, in execute_tool - raise ValueError("这是一个演示用的模拟错误!执行被中断。") -ValueError: 这是一个演示用的模拟错误!执行被中断。 -2026-01-28 09:58:55 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:59:01 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_resource, arguments=None -2026-01-28 09:59:01 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_resource -2026-01-28 09:59:01 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:59:04 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None -2026-01-28 09:59:04 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image -2026-01-28 09:59:04 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 09:59:09 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:171] - 收到 CallTool 请求: name=demo_image, arguments=None -2026-01-28 09:59:09 - lzwcai_mcpskills_template.main - INFO - [main.py:187] - 工具执行成功: demo_image -2026-01-28 09:59:09 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 10:02:13 - root - INFO - [logger_config.py:112] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\logs -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:154] - Initializing server 'mcpskills_template_server' -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:380] - Registering handler for ListToolsRequest -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:441] - Registering handler for CallToolRequest -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:322] - ================================================== -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:323] - MCP Skills Template Server 启动 -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:324] - ================================================== -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:329] - 命令行参数: {'mode': 'local', 'json_path': None} -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:332] - 使用模式: local -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:83] - 从本地加载 8 条配置: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\tools_config.json -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:137] - 已加载 8 个工具配置 -2026-01-28 10:02:13 - lzwcai_mcpskills_template.main - INFO - [main.py:339] - 开始运行 MCP Server (stdio 模式) -2026-01-28 10:02:13 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2026-01-28 10:02:13 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 10:02:40 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type ListToolsRequest -2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:143] - 收到 ListTools 请求,当前配置数量: 8 -2026-01-28 10:02:40 - lzwcai_mcpskills_template.main - INFO - [main.py:162] - ListTools 响应: 返回 8 个工具 -2026-01-28 10:02:40 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 10:02:44 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_complex, arguments={"user_info": {}} -2026-01-28 10:02:44 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_complex -2026-01-28 10:02:44 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent -2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:582] - Received message: -2026-01-28 10:02:50 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:621] - Dispatching request of type CallToolRequest -2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:172] - 收到 CallTool 请求: name=demo_mixed, arguments=None -2026-01-28 10:02:50 - lzwcai_mcpskills_template.main - INFO - [main.py:189] - 工具执行成功: demo_mixed -2026-01-28 10:02:50 - mcp.server.lowlevel.server - DEBUG - [server.py:662] - Response sent diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log deleted file mode 100644 index d73ec05..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/logs/lzwcai_mcpskills_template_error.log +++ /dev/null @@ -1,8 +0,0 @@ -2026-01-28 09:58:55 - lzwcai_mcpskills_template.main - ERROR - [main.py:190] - 工具执行失败: 这是一个演示用的模拟错误!执行被中断。 -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 186, in handle_call_tool - result_contents = execute_tool(name, arguments or {}, tool_config) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_template\lzwcai_mcpskills_template\main.py", line 259, in execute_tool - raise ValueError("这是一个演示用的模拟错误!执行被中断。") -ValueError: 这是一个演示用的模拟错误!执行被中断。 diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py deleted file mode 100644 index 2cf6e94..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/main.py +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env python3 -""" -MCP Server 模板 -支持从本地 JSON 或 API 动态加载工具配置 - -使用方式: -- local: 从本地 JSON 文件加载工具配置 -- api: 从远程 API 加载工具配置 -""" -import json -import os -import logging -import argparse -import anyio -import asyncio - -import mcp.types as types -from mcp.server import NotificationOptions, Server -from mcp.server.models import InitializationOptions -from mcp.server.stdio import stdio_server - -try: - from .schema_converter import convert_sql_params_to_input_schema - from .utils.api_client import call_api - from .utils.env_config import get_api_key, get_base_url - from .utils.logger_config import setup_system_logging, get_logger -except ImportError: - from schema_converter import convert_sql_params_to_input_schema - from utils.api_client import call_api - from utils.env_config import get_api_key, get_base_url - from utils.logger_config import setup_system_logging, get_logger - -# 初始化日志系统 -setup_system_logging(app_name="lzwcai_mcpskills_template", log_level=logging.DEBUG) -logger = get_logger(__name__) - -# 初始化 MCP Server -server = Server("mcpskills_template_server") - -# 全局配置 -_tools_config: list[dict] = [] -_config: dict = {} - - -def parse_arguments(): - """解析命令行参数""" - parser = argparse.ArgumentParser(description="MCP Skills Template Server") - parser.add_argument( - "--mode", - type=str, - choices=["local", "api"], - default="local", - help="数据加载模式: local(本地JSON,默认) 或 api(远程API)" - ) - parser.add_argument( - "--json-path", - type=str, - default=None, - help="本地 JSON 文件路径 (local 模式)" - ) - return parser.parse_args() - - -class DataLoader: - """数据加载器基类""" - - def load(self) -> list[dict]: - raise NotImplementedError - - -class LocalLoader(DataLoader): - """本地 JSON 文件加载器""" - - def __init__(self, json_path: str = None): - if json_path is None: - json_path = os.path.join(os.path.dirname(__file__), "tools_config.json") - self.json_path = json_path - - def load(self) -> list[dict]: - try: - with open(self.json_path, "r", encoding="utf-8") as f: - data = json.load(f) - logger.info(f"从本地加载 {len(data)} 条配置: {self.json_path}") - return data - except FileNotFoundError: - logger.error(f"配置文件不存在: {self.json_path}") - return [] - except json.JSONDecodeError as e: - logger.error(f"JSON 解析错误: {e}") - return [] - - -class ApiLoader(DataLoader): - """API 远程加载器""" - - def __init__(self): - self.base_url = get_base_url() - self.api_key = get_api_key() - logger.debug(f"ApiLoader 初始化,base_url: {self.base_url}") - - def load(self) -> list[dict]: - try: - # TODO: 根据实际 API 修改此处 - logger.info(f"开始从 API 加载工具配置") - response = call_api("/api/tools", method="GET") - - if response.get("code") != 200: - logger.error(f"获取工具配置失败: {response.get('msg')}") - return [] - - data = response.get("data", []) - if isinstance(data, dict): - data = [data] - - logger.info(f"从 API 加载工具配置成功,数量: {len(data)}") - return data - - except Exception as e: - logger.error(f"API 请求失败: {e}", exc_info=True) - return [] - - -def create_loader(mode: str, **kwargs) -> DataLoader: - """创建数据加载器""" - if mode == "local": - return LocalLoader(json_path=kwargs.get("json_path")) - elif mode == "api": - return ApiLoader() - else: - raise ValueError(f"不支持的模式: {mode}") - - -def init_tools(loader: DataLoader): - """初始化工具配置""" - global _tools_config - _tools_config = loader.load() - logger.info(f"已加载 {len(_tools_config)} 个工具配置") - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """列出所有可用工具""" - logger.info(f"收到 ListTools 请求,当前配置数量: {len(_tools_config)}") - tools = [] - - for tool in _tools_config: - name = tool.get("name", "") - description = tool.get("description") or tool.get("toolPrompt", "") - sql_params = tool.get("sqlParams", "[]") - - # 转换参数为 inputSchema - input_schema = convert_sql_params_to_input_schema(sql_params) - - tools.append( - types.Tool( - name=name, - description=description, - inputSchema=input_schema - ) - ) - - logger.info(f"ListTools 响应: 返回 {len(tools)} 个工具") - return tools - - -@server.call_tool() -async def handle_call_tool( - name: str, - arguments: dict | None -) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """调用工具""" - logger.info(f"收到 CallTool 请求: name={name}, arguments={json.dumps(arguments, ensure_ascii=False) if arguments else 'None'}") - - # 查找对应的工具配置 - tool_config = None - for tool in _tools_config: - if tool.get("name") == name: - tool_config = tool - break - - if tool_config is None: - logger.error(f"未找到工具配置: {name}") - raise ValueError(f"未知工具: {name}") - - # TODO: 在这里实现你的工具逻辑 - try: - # execute_tool 现在是 async 的 - result_contents = await execute_tool(name, arguments or {}, tool_config) - logger.info(f"工具执行成功: {name}") - return result_contents - except Exception as e: - logger.error(f"工具执行失败: {e}", exc_info=True) - # 重新抛出异常,以便 MCP SDK 能够捕获并将其格式化为错误响应 - raise e - - -async def execute_tool(name: str, arguments: dict, config: dict) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """ - 执行工具逻辑并返回 MCP 内容列表 (Async) - """ - # 1. 示例:Hello World (返回纯文本) - if name == "example_hello_world": - msg = f"Hello, {arguments.get('name', 'World')}!" - return [types.TextContent(type="text", text=msg)] - - # 2. 示例:计算器 (返回 JSON 格式的文本) - elif name == "example_calculator": - a = float(arguments.get("a", 0)) - b = float(arguments.get("b", 0)) - op = arguments.get("operation", "+") - - if op == "+": - result = a + b - elif op == "-": - result = a - b - elif op == "*": - result = a * b - elif op == "/": - result = a / b if b != 0 else "除数不能为0" - else: - result = "未知运算符" - - output = {"result": result, "expression": f"{a} {op} {b}"} - return [types.TextContent(type="text", text=json.dumps(output, ensure_ascii=False))] - - # 3. 演示:纯文本 - elif name == "demo_text": - return [types.TextContent(type="text", text="这是一个标准的纯文本输出示例。")] - - # 4. 演示:图片输出 - elif name == "demo_image": - # 这是一个 1x1 的红色像素点的 Base64 - red_dot_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" - return [ - types.ImageContent( - type="image", - data=red_dot_base64, - mimeType="image/png" - ) - ] - - # 5. 演示:资源输出 - elif name == "demo_resource": - # 模拟返回一个嵌入式资源 - return [ - types.EmbeddedResource( - type="resource", - resource=types.TextResourceContents( - uri="file:///logs/app.log", - text="[INFO] System started\n[WARN] Low memory", - mimeType="text/plain" - ) - ) - ] - - # 6. 演示:错误状态 (带 observation) - elif name == "demo_error": - # 尝试模拟用户要求的带 observation 的错误结构 - # 注意:标准 MCP 协议中 TextContent 可能不支持 observation 字段 - # 这里演示如何抛出异常,这是最标准的错误反馈方式 - raise ValueError("执行失败: 网络超时,请检查连接") - - # 如果客户端支持非标准字段,可以尝试返回如下结构(需 SDK 支持): - # return [ - # types.TextContent(type="text", text="执行失败") # 无法直接添加 observation - # ] - - # 7. 演示:混合输出 (文本 + 图片) - elif name == "demo_mixed": - red_dot_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" - return [ - types.TextContent(type="text", text="下面是一张图片:"), - types.ImageContent( - type="image", - data=red_dot_base64, - mimeType="image/png" - ), - types.TextContent(type="text", text="图片展示完毕。") - ] - - # 8. 演示:复杂参数 - elif name == "demo_complex": - user_info = arguments.get("user_info", {}) - tags = arguments.get("tags", []) - - # 模拟异步处理 - await asyncio.sleep(0.1) - - result_text = f"接收到复杂参数:\n用户: {json.dumps(user_info, ensure_ascii=False)}\n标签: {tags}" - return [types.TextContent(type="text", text=result_text)] - - # 默认返回参数 - default_result = { - "tool_name": name, - "arguments": arguments, - "message": "工具执行成功(默认实现)" - } - return [types.TextContent(type="text", text=json.dumps(default_result, ensure_ascii=False, indent=2))] - - -async def run_server(): - """运行 MCP Server (stdio 模式)""" - async with stdio_server() as streams: - await server.run( - streams[0], - streams[1], - InitializationOptions( - server_name="mcpskills_template_server", - server_version="0.1.0", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - -def main(): - """主入口""" - global _config - - logger.info("=" * 50) - logger.info("MCP Skills Template Server 启动") - logger.info("=" * 50) - - # 解析命令行参数 - args = parse_arguments() - _config = vars(args) - logger.info(f"命令行参数: {_config}") - - # 创建加载器并初始化工具 - logger.info(f"使用模式: {args.mode}") - loader = create_loader( - mode=args.mode, - json_path=args.json_path - ) - init_tools(loader) - - logger.info("开始运行 MCP Server (stdio 模式)") - # 运行服务器 - anyio.run(run_server) - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py deleted file mode 100644 index 64423d4..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/schema_converter.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -Schema 转换器 -将 sqlParams 数组格式转换为 MCP 工具需要的 JSON Schema 格式 - -支持的类型: -- string: 文本输入 -- paragraph: 段落/多行文本 -- select: 下拉选项 -- number: 数字输入 -""" -import json -from typing import Any - - -def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]: - """ - 将单个参数转换为 JSON Schema property - - Args: - param: 参数配置,格式如: - { - "type": "string", - "name": "param_name", - "displayName": "参数显示名", - "maxLength": 200, - "defaultValue": "", - "required": true, - "options": ["选项1", "选项2"] # 仅 select 类型 - } - - Returns: - tuple: (property_name, property_schema, is_required) - """ - param_type = param.get("type", "string") - param_name = param.get("name", "") - display_name = param.get("displayName", param_name) - default_value = param.get("defaultValue", "") - max_length = param.get("maxLength") - is_required = param.get("required", False) - options = param.get("options", []) - - property_schema = { - "description": display_name - } - - if param_type == "string": - property_schema["type"] = "string" - if max_length: - property_schema["maxLength"] = max_length - - elif param_type == "paragraph": - property_schema["type"] = "string" - property_schema["format"] = "paragraph" - if max_length: - property_schema["maxLength"] = max_length - - elif param_type == "select": - property_schema["type"] = "string" - if options: - property_schema["enum"] = options - - elif param_type == "number": - property_schema["type"] = "number" - - elif param_type == "boolean": - property_schema["type"] = "boolean" - # Boolean default handling - if default_value is not None and str(default_value).lower() in ("true", "false", "1", "0"): - property_schema["default"] = str(default_value).lower() in ("true", "1") - - elif param_type == "array": - property_schema["type"] = "array" - # Simple array of strings by default if no item type specified - # For more complex arrays, we might need extended config in sqlParams - property_schema["items"] = {"type": "string"} - - elif param_type == "object": - property_schema["type"] = "object" - # Allow any object structure by default - property_schema["additionalProperties"] = True - - else: - # 默认当作 string 处理 - property_schema["type"] = "string" - - # 添加默认值 - if default_value not in (None, ""): - if param_type == "number": - try: - property_schema["default"] = int(default_value) if str(default_value).isdigit() else float(default_value) - except (ValueError, TypeError): - property_schema["default"] = default_value - else: - property_schema["default"] = default_value - - return param_name, property_schema, is_required - - -def convert_sql_params_to_input_schema(sql_params: str | list) -> dict: - """ - 将 sqlParams 转换为 MCP 工具的 inputSchema - - Args: - sql_params: sqlParams 字段值,可以是 JSON 字符串或已解析的列表 - 格式: [{"type": "string", "name": "xxx", ...}, ...] - - Returns: - dict: MCP 工具的 inputSchema,格式如: - { - "type": "object", - "properties": {...}, - "required": [...] - } - """ - # 解析 JSON 字符串 - if isinstance(sql_params, str): - try: - params_list = json.loads(sql_params) - except json.JSONDecodeError: - return {"type": "object", "properties": {}, "required": []} - else: - params_list = sql_params - - if not isinstance(params_list, list): - return {"type": "object", "properties": {}, "required": []} - - input_schema = { - "type": "object", - "properties": {}, - "required": [] - } - - for param in params_list: - if not isinstance(param, dict): - continue - - name, schema, is_required = convert_param_to_schema_property(param) - - if name: - input_schema["properties"][name] = schema - if is_required: - input_schema["required"].append(name) - - return input_schema - - -def convert_tool_config_to_mcp_tool(config: dict) -> dict: - """ - 将单个工具配置转换为 MCP Tool 配置 - - Args: - config: 工具配置对象 - - Returns: - dict: MCP Tool 配置 - """ - name = config.get("name", "") - description = config.get("description") or config.get("toolPrompt", "") - sql_params = config.get("sqlParams", "[]") - - input_schema = convert_sql_params_to_input_schema(sql_params) - - return { - "name": name, - "description": description, - "inputSchema": input_schema, - "_raw": config # 保留原始数据 - } - - -# 测试用 -if __name__ == "__main__": - # 测试单个参数转换 - test_param = { - "type": "select", - "name": "operation", - "displayName": "运算符", - "required": True, - "options": ["+", "-", "*", "/"] - } - - name, schema, required = convert_param_to_schema_property(test_param) - print(f"参数名: {name}") - print(f"Schema: {json.dumps(schema, ensure_ascii=False, indent=2)}") - print(f"必填: {required}") diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json deleted file mode 100644 index b642048..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/tools_config.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "id": "example_tool_001", - "name": "example_hello_world2", - "description": "示例工具 - Hello World2", - "toolPrompt": "这是一个示例工具,用于演示 MCP 工具的基本结构", - "sqlParams": "[{\"type\":\"string\",\"name\":\"name2\",\"displayName\":\"名称\",\"maxLength\":100,\"defaultValue\":\"World\",\"required\":true}]" - }, - { - "id": "example_tool_002", - "name": "example_calculator", - "description": "示例工具 - 简单计算器", - "toolPrompt": "执行简单的数学计算", - "sqlParams": "[{\"type\":\"number\",\"name\":\"a\",\"displayName\":\"数字A\",\"required\":true},{\"type\":\"number\",\"name\":\"b\",\"displayName\":\"数字B\",\"required\":true},{\"type\":\"select\",\"name\":\"operation\",\"displayName\":\"运算符\",\"required\":true,\"options\":[\"+\",\"-\",\"*\",\"/\"]}]" - }, - { - "id": "demo_001", - "name": "demo_text", - "description": "演示纯文本输出", - "toolPrompt": "返回一段简单的文本信息", - "sqlParams": "[]" - }, - { - "id": "demo_002", - "name": "demo_image", - "description": "演示图片输出", - "toolPrompt": "返回一张 Base64 编码的图片", - "sqlParams": "[]" - }, - { - "id": "demo_003", - "name": "demo_resource", - "description": "演示资源输出", - "toolPrompt": "返回一个嵌入式资源", - "sqlParams": "[]" - }, - { - "id": "demo_004", - "name": "demo_error", - "description": "演示错误状态", - "toolPrompt": "触发一个执行错误", - "sqlParams": "[]" - }, - { - "id": "demo_005", - "name": "demo_mixed", - "description": "演示混合输出", - "toolPrompt": "同时返回文本和图片", - "sqlParams": "[]" - }, - { - "id": "demo_006", - "name": "demo_complex", - "description": "演示复杂参数输入", - "toolPrompt": "接受对象和数组类型的参数", - "sqlParams": "[{\"type\":\"object\",\"name\":\"user_info\",\"displayName\":\"用户信息\",\"required\":true},{\"type\":\"array\",\"name\":\"tags\",\"displayName\":\"标签列表\",\"required\":false}]" - } -] \ No newline at end of file diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__init__.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__init__.py deleted file mode 100644 index 1b74368..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Utils package for lzwcai_mcpskills_template""" - -from .json_helper import load_json -from .api_client import call_api, APIClient, get_default_client -from .env_config import get_api_key, get_base_url, get_env_config, set_env_variable -from .logger_config import setup_system_logging, get_logger - -__all__ = [ - 'load_json', - 'call_api', - 'APIClient', - 'get_default_client', - 'get_api_key', - 'get_base_url', - 'get_env_config', - 'set_env_variable', - 'setup_system_logging', - 'get_logger' -] diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index dfce60552753f32e6e88c62a294405bee77b894a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 707 zcmaJ;zl#$=7@hr*WV4&ZLk{a`w@3tA6%P>$8xevaL*Qd>cd|E^ncZb(6WL(nUtw=| ze~Xn^IZX<&5G>@byXxjkavaFvSG=9~W_G^!&HFkW#>Ca1v#AJtLVhB-F2}cZ@!owt zlbleJd$}(?r^Dxt|%@5q4~(xDpBkw*%W zcfWY_PTNbP^}~0KkY-%7Y{3dXo>zJ-*B>$_KxMUA2q_ITT-A~_{I}=4q@L}|isf)> zsxl31lrbrR)x!2)ynX#r3SKt$ZowPK`J63f1KCzfLz|%LPQrq>c7WuV84gP=?Fbn! zSCCcZTom@e?D>jm!Hy+QeS7G%Weuh^&Jf6|C`4J<82?1VbsFoyIn*IS2ce75Lx>Ri z2>9kYK^QoAlOEU}Kwf14Ho=+Tgv*-i?Nv5LySoAYxz@%vc5iE8hdWdIiEcV#JA5)Y zBNxs9x%~L*$*et@Ffprjb;`45wo71Q)@^aUc4ggmx8(Gnoh7C{0}!QX z03OxtW%Nu{`BL)d`kpg`37h*4{sx}s{UDEjkp~-c^o<;C$n-mzZphQ~FiHGRJ$>j- FT>;iY(zgHr diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/api_client.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/api_client.cpython-312.pyc deleted file mode 100644 index 0d2ddaf81c96f6322cfabb1d08789fb51c678081..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7295 zcmb_heQ*;;mhX{9qpz`KS^mN{w!nZbjQI#jE^ABzi_J&jHC!;M#0ychnz4~1OWK)H zf_)<8uw)~4A;w8I7RhblO-KcbY)ooX3%f~n@9vK~uI}ne0e{HM`}OPBuU~h+{=McON=j@5(tn)a7u-}y$iHJnOU`^@ZUsfi zIN47)(n~msGlZyKO8FXk4e&LFjDvJ9J!tAR4Klq9MXZFQIaA0yXz8^KT6?V&)|rrP z(B5mOh=K5SzT^VP#MJBH%)L&|(pzdEI|yezMmXCAL%z+$8T@5D!@HIMfp>HEcKC8E z@9HyfCCBLAax095e_T1|;43&M{7QkJ<|}wpp8?TL%EeoD_h9l$8zptrrxR{ zLnGnY6-01>_EgbqsQKAEM@|G^CWnpBk(ViNMeGR}&bD>$F3-%xAKiZYT=tXqrxTZM zUzoJbj=T%?+wUIp?Cj~;>p>R1F?;dVnGasG%|c(&?KR4DM=&7C#;xHY*|H}p1tVd9 zXcq0|HOOo~FZq7p7kOVy2+4M=`J+MKi~Nw6l5JQGMf&@Bp&x}2^5hn^@K?f?fJ#ww zeQj?+eVp(FR+Qjs_8K_@XW;2RBWL7IJfm2M)=MVd+{bVXSjfU#IWun?fL&9ZV0A3V z=nHvrFAIDnU~NWS5ooo-+Bx*Kb8|MH;q35ZVI1Ui7OY*#A~Xlk%!+2Jr2O_KL^Hzxaup?x(vCr#`0|^ckhmc)!>qAu zGJs1TJ8p!UN#o!QP-C>3z;|Mi3<-7!_Ek2o}srFvAs*&Ax!L z|71ozq%s{0_eH$4OpAP|Pwk>}i?~E+ynC;7wjCHc)anl&hzgMbJ|GKrtyzmk)v_}1b7ybQwzQqSE;)}shNc2hkU^L{H9@G+(f+6t$T3S_gZ4{i4 z&ld~_C7*A&Og}ImLUEX%BdP{;nQIe^u zzgo5KY|96&P-8OE&My%elI7aQq?WxK~FZNtI`02rmds5{c|7_`;1F2|G zUWy6ffPSKYh-XPTb#W-bsUrjMElwjVCH>4Tw%?(beq!ePeq4Aab@JjP>;sP+s|Kd& z*Wb+kWMbyaFIDfZZxry!=DgSN(n1+<%BDk-6g@06swYzR#d%7ofF==V=n>GN%8Y9L z;nI17tCaP~xE3-nH$&Lc(e~r*DSN|>TF;sG)9n}Osr4P{^&Qu1I};rzx-w4pX#99Q zWvLZV%f(H&9a_fmj)R;1FAYbi#iuGb7pKSJ{>NK6PJuD#7~U&0Z(M@IJ2QD{`qQJ? zv!4x@`lHcMFyP0FY~6qu340C0?6yc)0#Dr9GZf`zW@{k8N2Ouwv#l`S*4=oSDaXcZ zP%I;x5AlAE7eo=Aq$h79)`!4nWzK{|IOI+qUI%K*UMXs!eYG71!U@qh@M9^XEAv%~JsDry;n)%}YL5DpL z(ZTY-l|Y|Rlpd->@k%(PDoq4u#oaQ0#UG^}vb$_?^8mVyqEsjY_L_MBk3mt=c81cv zw5SoPTKhiINF=;ll{DbV7YaUrz`cOL?aCfE9I+JW+*rVtFLG?$vQR&1rN$MvXeR__ zK0oFvt$qFrV|E;|awT!=0GyQKw75+;hk6SCzcl2WN35?{U#6rL+W4T=6QEV8)S^-1 zR&7Q&=Rb91&h>Gb7GVKUUDQ6>=z3)1j;&e%1xLqbM_yGgJJr$aKus^)j{-G!sscEO zif$kM&CI3A?C*~0oe=M+zP3%3KK;&brhoOb1?}jA+dS&9x4wL9`ezrXPkyq1L-o&X zh%i0VXFi;nd_OyS6!;b}`LaOjn%6$Da*Y2m){WZycO{q5UD8pII#eIJ@t z6%Tr3$|D$G`d=reIhRiT5b?P3TxP)$`4b)LYi={NiK>6yvNgPs`rh;ikL5N~as zv=!LJop8|u5?M{kD#w?OEj{I&uq205j@1ccrfkX8vJJ_esihmzOE&;Kp&z$4-C)Z` zcOT!KTr=^~#G!Nki^g-YRP|%m*~f1>D-hZJ^p%NtqbA(X@IOYXBcGcDVfWZs9!}#}gf8wgEMIXO4UE5kbzOMb+y!PKXNY%1b zQ7fsJ+d?X$j;P;U02*!_FVx%Pd`l1 zA8$FUwXr|mchPm>nR7c~ZSU`PnM-uRObT@PW6lgZ-xG1OzTdfiyPN#2duz$AM&sXk zp4!=9ywboz`AW;iUG>JluQx$?0q{qtsesfMpzv)V_0{D7&Y-3&5r-FC^+f{bgza)flvfOSGPR#Yw4|GQ1yB@I=P|m*fZuuZp}D%ZL+FDuhg$(H zr}|e3qa-tW+L!zxnZbhe1O0(Obv+QJ^M~(`(pB2KAWB5f47ys_QQL7FMB&bIMdkCf zuo_yrySw3$45WnpgS^it+kC#k2p0=snf3X881sknEoPsOiv)n|?C9LSb$?fnuV>fy zI`{1F5lkRQ*bJF$>I+5ul7KghY=LJBxC5ji#b*nM2bKFkz*SK00|BijpodiEE^Nor zZp=C{Llmk^Qm|tQGbD-kAUj6>g|3HKuCXbzY-MKI`a4#)X*+eZtR}f}tTkuC3Ixhf zZ5&&gGb>dKscD!~*uW7pj+}uu)g?`5*wgG?0>wF_M$EAmQ&p0i*m%x3@xYBeJkSI!8nohh7sHRE6IgC)nCw#ha-mjR}Jgc z7c>2V1D?zDw-0zUgA?5Ytm-l?TspZ7qxB*+p8@6%;3wif86n>=#8Q;9=KyXwE$Q+1lR$w<>ZeVc*kbN04)J*00MDs3JpJUQDGxjU4MG$M zcAdvfDfcsEz`(R+JsCyrvg~{9x zX3v|x6%@fjZ|{X?s(~ zwIt)L$dtG+FRjYi3EK=nfn{=pF)`}WcR^=wwXhdHsslmzCo|v^gW-PFhtSxD8pt+2 z{1U*%a9^-rd5;zF305vq-&?f?d>xVPy9T2XK~f;0@I4fufYv|9HnwX|_YU2w;Sp2z zHJme_KOBw#mISCN`h2Pp(BTRA1ggxn$YtHLw5x9PN#O-x!7VBNCuBJzMNxMRMv9)Z z5vt^0iTx|GDos}9XcOiAihk_hXzv{xv6Y|NdCj_XgaOaut{QQI&mLj_i|H^>6?X{c zxxF-LUNho6Rh{&vT`R7UmKCH5?WEvXp*lQ>z<8bAS@V&`8 zb0+wLV7vR&qe)|OU(z!6WZJeoXGXGxlvJGRNj4_MWb@ebX?8`KpA&z{SNu-@3_C~H2#f) Hg8Y8~L^Jz@ diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/env_config.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/env_config.cpython-312.pyc deleted file mode 100644 index 9feae7e034dc129c8f70e862afbdb20398149909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1946 zcmcIl-EZ4e6uqCPNChgJb1s%AXfN!>ahx^{|E zRVYBq8bO)0Q_%cMPF!2{adG!oKMbx(WZ+-oOI*_gz`uH<`< z&xg-BzjN;KZ_#KN!TNh`#HjEH-6k)sCHTbthd@-3hLT8QG}dI244j+%6;gcb)3$(+;LSs{c)(yO_A{*-|JO^&-3!Cnb zpShJ<<5HzDJ72%Pv9o&F{l3~Du?)u;Cv+}v8@a4vIzdfODFxGZ1gzTyoK+d3<1fes zaq#R9!d6AL&qipRZIf+cJkTcFgvLyxbzgS`#n{3P5QRlpy9>X#m2bp>cit4;&CeTO z{oENd)u7t|PFoVCh>;(wBsQ+qb~k=sE5>LZ z=f-u_9yO;vQWYa^O%>BEI-};}mX0TN9M3E2gp$^Mi3}2J!Z1xsw)ITjRP0Ad3btWd zqY#U%=CUax-Ip&q-4Kzi1g~D^+)Pi<-q)C+N4I6KdreV`(eWl-CeV$wQ=vAac;anBegWJQf*g(6GJe z$csy_|8YcI8r}!2^>=)u=(`S4BpvkV^x>1#yoN!UuL z*^7>zm>Ilu_LH-7)~Dwva_}!eF=pzSG26~diG-O`6>}_S+0yA#r%%PgPEeM$oGQ!I zza!Yiypc^~0;|Kpa6PD`!;_YvHKTsdfsrl=7@($v0JX!rUj@4X6T zWh?}g#Z6eoMs+$#jByh;_6bTrH^v7wey{|S=}V0!>+4uz3=xwh@Wp#y`)G&8)AZhR z&pE$){@io#`ynqc2O#?6R+syA5`af|p%7ZjwB3rP89;*`Kto!^!XAibf>n8m9s*(+ z$&y~Ghw`d>)M!mHYDU8lF4ZA~qty)MB5BPS*`p1qtlD4^628*a*R};46cJuR0?(!W3iWgl!etPDq6K4 zE9ezn7?qADzoO>Sal8nIRYIPk^%xW`Gl%zL7$#^n{>`BrF+mU}xA^z)|nYe4`x;h zr{p^dY4tF9jvRo3(qI6^fubJ;La}0vBqb;^+(9zYTH%Pohe_p3o;@FB-+^@YR$Z_h z9ej=jQL4=>S&b(xEo|PKmagBB#uub_$E7<vrXCK_*=9w&XtD9vy{6ecg=%ZUW z&d=3bq_3}}u1+F)NxB`8KE9cpS=ieJ2U~-EHZx8?&15j?*k@OymC5AHZ1U=a0t@2P zdM3>DzV(*iVWdi>iM%Bv^NHyBMD)v)|9XoExgjbjxNNoP(rOtW^62*Ubp%a&8_MpU zOn2TZlp-`W~dz}Fr&v5TB zoGsvRo^kXsX^kCe_%m*nPr?FvRe)HZQI(#4Jj~P!v0Se8qXceAyL!D^}dPVK^Dn zp4{Ccuy%)ULMe|nnwvp+vyGzLIKp#9Db=d+yfl`e93jc1-223 zo+&i!XdAqKI><6Fak$0t*M@%{4JcL!e*vYBfc{rd{2M6UQkTOv7%@lq+2%-S{g8DF OV8Ko2-}1~=AkaS%JSZ^$ diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/logger_config.cpython-312.pyc b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/__pycache__/logger_config.cpython-312.pyc deleted file mode 100644 index 2a26a0fb3d041b758054a4f241a4202597748d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5886 zcmbVQZ*UXG72lIi(n-3L?D(H-$wnBmUH=?76W)0uqg z+dbV$PDDt0WADD*xBK?(?)&pw{fXmT2)=(`8;stlM(AI-VSJ7vvGRK$rjdY#kw6KS zI5kX>+A?e*wRPAEwKYyBY{RyMeb`Rnadg~~V1^kASx_GmY^RZ6zi!bb4m&MKbY3rt zA9e{2(Hg5SHp{9}i@=Di;1oH*1tS7wtRgvT7u=_9!(M@faSt0q3*2dXxN5=@_N04Z zqHFo1%XfZt>hAQffJ@Q`sIjn zY^1mWQgOw`%1?oqMk0a(M)+{yU~2@cNW($UuuYp_6YW6TVLJ{uG$!^~xfq=m7{MvH zMs0#yq~TcX0tf#d`1gt~n9o!^XjZ5K`K(Y4a~?K^79N9jIXK2zjqOj3jfv8(RB|*r zmac=9%TGx^qUBp34QNa}H74^yR2nJXJyFsMCH`NjqVR?@lvJ$%rGFXfdXdf`-t0|w zl}0GEG1eFvvl~67x>DbiB?HGg=|-qrb_T`DwJVBH#vn2B%aTHV-2iqgSv)%Uo;15oXANE@dw*UH!q*nID&*tVZMW90Dt1#FJ53;Zw&a zUk-Id zt3!vnLW!=B5De|@3BA-48r(Awc1d`r5>{JdcpeMldCfi^k;J5;*%HU$k~J5o$B(9@ zL`2ctLPQabMgu=4Dm>;AmH1I9mEgtX3C(HtX)GU2MwMtJ9-S10uvMdFF+Qp<$c{^? zm^h;FQYxj$*mQ!y;Ae03_8gu#))k2!F0v2DC*K%}M7!k4i7|stjEr~5qI5!(y2c|T z$6<#>4i6mpcr+fDc|}Z&$3fdK;iQ#lTs{oSg4y#U#O}MtC-6yNrFnj`uD{GBx{Yp# zsYp&haSHv@U9%Vn&iF14WOu*)(jEVrpD`D==lmO0|Hhnui|XH!?av1~W}j9Ao3gvl zymXsk-{HQ`)W)ag8@E8eCm&dw zr9WjFS71S;*(Fg)OUV(_hoBQ*AMw1S&^wJvK2^yBaCA3fcFeSMEL;B!Yo4MM)9z!Y zT^pkiD&vxDQ{rH%jLWX(d@Zi$OXpU7U9Mn#&w-;wtmca|Gv)iOup{Hl22q$E(477I z`}qFeS9<$3df&jF{b7e@+ughKTYVBvXqw~TjspYx2KqD_6Pm5}z=8b-G-lUPLZ0YfFNHY#>kC1w#W{^b0NiCQP;$kfnniVF5tx_$f zIn&j6S&^WLg9D$4jE{o|!>J^khb3&blWnUly0QZYVWd0`#VNGtYr5>b;GEeodt%=A zbk4U;^=+H?ZO?Ld>H^b8&K+5-^-uSo>tAeWnSS%!o4B?A-2TNP&$lM$>rj0i(9YF` z)Vk1pFVpDCa)rlGRddc8RK3BO<_{bnvbWfI?{jm^bCM4h)f#n=ui(p3gl5n^Y_&rg z1AF9g*pqO=)VM_Cq#nRz2sIje6yid99K1RWk<0_mEvF#3rP9iHS{X4d8)jleM7-w= zWFW{}Co%v44XLWb82qOUe?%AT>u3@la>gEE#LTF8OBS4sr<@7~ z5#-A_Gu8}EGHvnnrd%1I_rb9qhlc#4)Iht8B!>^`wh>y36!*e?d<+ z!&cH$G0{b=$ym2km#xqKpL<}-WITKiw&ET*1&0grWjMhuH~=zQ-gjOvM))aD##3n* ztL)A5C`|=d#>1Nk=pkqCDbw2~ohb8%U{S%cN@+D3u{MJ$)rO=gZ^rv5?aS7wVB>=O z5%ydKD^#+zDnkbfJ!HS$(g=YC)Mu&>g;zdbHNo1Cb7b4O41g2qPR7gh0jw$yr zaJYvPN|@H@H;#!(jfo}|z*3R8^bP2iHbXJV7-(_w$*+t=LmejC97;b9Qgo23zr0N3 zyL98s-RxNar$z3?8%rPms3#b%m2gs)+M&>#ke2j3r7<#)IHzgMG5sATYn&t|QYXxJ znR5@prmzPI+opuW7D1R24%r$D63h{rD}SHjyPc}MEPR5vc3z>?e;%wkLokWzCLRo0w3d9p>^f1_1!GSAPT_XdE0AWK66( zHl-&026pMNcoh%tSzZSL@DwVrh^@-G1AlP`7HgVvHEn85+k8#?9NT`UzByOFUaen0 zUmwo8^Gr>S38+lq{GkPA{q2UMT*p?`w>8Tx zHgx9M#(Z6C-q)6|3p{Yze6B1DiaP6ZOtZ=~pN}pu9eJiU$E;PEwKL%b=5eB4i^{Z| z&p-@DzR*0ocedx|@Lc!>wef|lXVJGI&o-3{CSV2x*XI~K`MozTPhFT=V7l@Q zEY+$qtutFm3-(TgW8OoBZ^Xi@{rS53ybo3hJaF5Ie2*#r{KNti{+#SJ;Vien1WFsg zYuvXZwrQ~?lxx|nwru`j*M~3OdU3wx`Rx8Y>(8-mD%&=5Xn}oVF}OY#+@uCK<*WAO zt7>yqLA5HlVzaLG%(2Y{262HL+o7@@bK#x^_E~a2tt#6(^ZWw4`F0?PpDT@IVV}zQ z=KOtsp#RMMk^9}7bBzZVm{)Imo937%9n!fIDIpE`3*)uQzZzhVdeqyc^ee(3x<1qUG+)V99h zB%}+qcNWN$f(t$U+=s8-dTm8-4p=B#_v~N+L3Q&WW<0>8t}juw6YE|YSUS4 Td(@`$2QR;N0oxw6iKz8I3vn{( diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/api_client.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/api_client.py deleted file mode 100644 index cd5cabd..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/api_client.py +++ /dev/null @@ -1,197 +0,0 @@ -""" -API 调用客户端 -通用的 HTTP API 客户端封装 -""" - -import httpx -import json -from typing import Dict, Any, Optional - -try: - from .env_config import get_base_url, get_api_key - from .logger_config import get_logger -except ImportError: - from env_config import get_base_url, get_api_key - from logger_config import get_logger - -logger = get_logger(__name__) - -# 默认超时配置(秒) -DEFAULT_TIMEOUT = 30.0 -LONG_TIMEOUT = 300.0 - - -class APIClient: - """通用 API 客户端""" - - def __init__( - self, - base_url: Optional[str] = None, - api_key: Optional[str] = None, - default_timeout: float = DEFAULT_TIMEOUT - ): - """ - 初始化 API 客户端 - - Args: - base_url: API 基础 URL(默认从环境变量读取) - api_key: API 密钥(默认从环境变量读取) - default_timeout: 默认超时时间(秒) - """ - self.base_url = (base_url or get_base_url()).rstrip('/') - self.api_key = api_key or get_api_key() - self.default_timeout = default_timeout - self._client: Optional[httpx.Client] = None - - logger.info(f"[客户端初始化] base_url={self.base_url}") - - @property - def client(self) -> httpx.Client: - """懒加载 HTTP 客户端""" - if self._client is None: - self._client = httpx.Client(timeout=self.default_timeout) - return self._client - - def _get_headers(self) -> Dict[str, str]: - """获取请求头""" - headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - if self.api_key: - headers['X-API-Key'] = self.api_key - return headers - - def request( - self, - endpoint: str, - method: str = "GET", - data: Optional[Dict[str, Any]] = None, - params: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None - ) -> Dict[str, Any]: - """ - 发送 HTTP 请求 - - Args: - endpoint: API 端点路径 - method: HTTP 方法 - data: 请求体数据 - params: URL 查询参数 - timeout: 超时时间 - - Returns: - API 响应数据 - """ - url = f"{self.base_url}{endpoint}" - timeout = timeout or self.default_timeout - - try: - logger.info(f"[API请求] {method} {url}") - - if method.upper() == "GET": - response = self.client.get( - url, - headers=self._get_headers(), - params=params, - timeout=timeout - ) - elif method.upper() == "POST": - response = self.client.post( - url, - headers=self._get_headers(), - json=data, - params=params, - timeout=timeout - ) - elif method.upper() == "PUT": - response = self.client.put( - url, - headers=self._get_headers(), - json=data, - params=params, - timeout=timeout - ) - elif method.upper() == "DELETE": - response = self.client.delete( - url, - headers=self._get_headers(), - params=params, - timeout=timeout - ) - else: - raise ValueError(f"不支持的 HTTP 方法: {method}") - - logger.info(f"[API响应] HTTP {response.status_code}") - response.raise_for_status() - - return response.json() - - except httpx.TimeoutException: - error_msg = f"API 请求超时: {url}" - logger.error(f"[API错误] {error_msg}") - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}" - logger.error(f"[API错误] {error_msg}") - raise Exception(error_msg) - - except Exception as e: - error_msg = f"API 请求异常: {str(e)}" - logger.error(f"[API错误] {error_msg}", exc_info=True) - raise Exception(error_msg) - - def close(self): - """关闭 HTTP 客户端""" - if self._client is not None: - self._client.close() - self._client = None - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - return False - - -# 懒加载的默认客户端 -_default_client: Optional[APIClient] = None - - -def get_default_client() -> APIClient: - """获取默认客户端(懒加载)""" - global _default_client - if _default_client is None: - _default_client = APIClient() - return _default_client - - -def call_api( - endpoint: str, - method: str = "GET", - data: Optional[Dict[str, Any]] = None, - params: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None -) -> Dict[str, Any]: - """ - 便捷函数:调用 API - - Args: - endpoint: API 端点路径 - method: HTTP 方法 - data: 请求体数据 - params: URL 查询参数 - timeout: 超时时间 - - Returns: - API 响应数据 - """ - return get_default_client().request( - endpoint=endpoint, - method=method, - data=data, - params=params, - timeout=timeout - ) diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/env_config.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/env_config.py deleted file mode 100644 index fba59a9..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/env_config.py +++ /dev/null @@ -1,60 +0,0 @@ -"""环境变量配置模块""" - -import os -from typing import Optional - - -def get_api_key(default: str = "") -> str: - """ - 获取 API 密钥环境变量 - - Args: - default: 默认值 - - Returns: - str: API 密钥 - - Environment Variables: - API_KEY: API 密钥 - """ - return os.environ.get("API_KEY", default) - - -def get_base_url(default: str = "http://localhost:8080") -> str: - """ - 获取后端 API 基础 URL 环境变量 - - Args: - default: 默认值 - - Returns: - str: 后端 API 基础 URL - - Environment Variables: - BASE_URL: 后端 API 基础 URL - """ - return os.environ.get("BASE_URL", default) - - -def get_env_config() -> dict: - """ - 获取所有环境配置 - - Returns: - dict: 包含所有配置的字典 - """ - return { - "api_key": get_api_key(), - "base_url": get_base_url() - } - - -def set_env_variable(key: str, value: str) -> None: - """ - 设置环境变量(仅在当前进程中有效) - - Args: - key: 环境变量名 - value: 环境变量值 - """ - os.environ[key] = value diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/json_helper.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/json_helper.py deleted file mode 100644 index 00ac6cc..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/json_helper.py +++ /dev/null @@ -1,41 +0,0 @@ -"""JSON 文件读取工具""" - -import json -from pathlib import Path -from typing import Any, Union - - -def load_json(json_path: Union[str, Path]) -> Any: - """ - 读取 JSON 文件并返回其内容 - - Args: - json_path: JSON 文件的路径 - - Returns: - JSON 文件中解析后的数据 - - Raises: - FileNotFoundError: 当文件不存在时 - json.JSONDecodeError: 当 JSON 格式无效时 - """ - try: - path = Path(json_path) - - if not path.exists(): - raise FileNotFoundError(f"JSON 文件不存在: {json_path}") - - if not path.is_file(): - raise ValueError(f"路径不是一个文件: {json_path}") - - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - return data - - except json.JSONDecodeError as e: - raise json.JSONDecodeError(f"JSON 格式错误: {e.msg}", e.doc, e.pos) - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") diff --git a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/logger_config.py b/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/logger_config.py deleted file mode 100644 index b423c64..0000000 --- a/lzwcai_mcpskills_template/lzwcai_mcpskills_template/utils/logger_config.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -""" -统一日志配置模块 -提供系统级别的日志配置和管理 -""" - -import os -import sys -import logging -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from pathlib import Path - - -class LoggerConfig: - """日志配置管理类""" - - def __init__(self, logs_dir: str = None): - """初始化日志配置""" - if logs_dir: - self.logs_dir = Path(logs_dir) - else: - project_root = Path(__file__).parent.parent - self.logs_dir = project_root / "logs" - - self.logs_dir.mkdir(exist_ok=True) - - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' - self.date_format = '%Y-%m-%d %H:%M:%S' - self.log_level = self._get_log_level_from_env() - self._initialized = False - - def _get_log_level_from_env(self) -> int: - log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'WARN': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, - 'FATAL': logging.CRITICAL - } - return level_mapping.get(log_level_str, logging.INFO) - - def setup_logging(self, - app_name: str = "lzwcai_mcpskills_template", - log_level: int = logging.INFO, - max_file_size: int = 10 * 1024 * 1024, - backup_count: int = 5, - console_output: bool = True) -> logging.Logger: - if self._initialized: - return logging.getLogger() - - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - formatter = logging.Formatter(self.log_format, self.date_format) - - # 1. 主日志文件 - 按大小滚动 - main_log_file = self.logs_dir / f"{app_name}.log" - file_handler = RotatingFileHandler( - main_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # 2. 错误日志文件 - error_log_file = self.logs_dir / f"{app_name}_error.log" - error_handler = RotatingFileHandler( - error_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - error_handler.setLevel(logging.ERROR) - error_handler.setFormatter(formatter) - root_logger.addHandler(error_handler) - - # 3. 按日期滚动的日志文件 - daily_log_file = self.logs_dir / f"{app_name}_daily.log" - daily_handler = TimedRotatingFileHandler( - daily_log_file, - when='midnight', - interval=1, - backupCount=30, - encoding='utf-8' - ) - daily_handler.setLevel(log_level) - daily_handler.setFormatter(formatter) - daily_handler.suffix = "%Y-%m-%d" - root_logger.addHandler(daily_handler) - - # 4. 控制台输出 (MCP协议使用stdio时,必须将日志输出到stderr) - if console_output: - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setLevel(log_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - self.date_format - ) - console_handler.setFormatter(console_formatter) - root_logger.addHandler(console_handler) - - self._initialized = True - root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") - - return root_logger - - def get_module_logger(self, module_name: str) -> logging.Logger: - return logging.getLogger(module_name) - - -# 全局日志配置实例 -logger_config = LoggerConfig() - - -def setup_system_logging(app_name: str = "lzwcai_mcpskills_template", - log_level: int = logging.INFO) -> logging.Logger: - return logger_config.setup_logging(app_name, log_level) - - -def get_logger(name: str) -> logging.Logger: - return logger_config.get_module_logger(name) diff --git a/lzwcai_mcpskills_template/main.py b/lzwcai_mcpskills_template/main.py deleted file mode 100644 index c79f492..0000000 --- a/lzwcai_mcpskills_template/main.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Entry point for lzwcai-mcpskills-template -MCP Server 模板项目入口 -""" - -import os - -if __name__ == "__main__": - # 设置环境变量(根据实际需求修改) - os.environ["API_KEY"] = "your-api-key" - os.environ["BASE_URL"] = "http://localhost:8080" - - # Import and run the actual MCP server - from lzwcai_mcpskills_template.main import main - main() diff --git a/lzwcai_mcpskills_template/pyproject.toml b/lzwcai_mcpskills_template/pyproject.toml deleted file mode 100644 index 8aeb48f..0000000 --- a/lzwcai_mcpskills_template/pyproject.toml +++ /dev/null @@ -1,31 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai_mcpskills_template" -version = "0.1.3" -description = "MCP Server 模板项目 - 用于快速创建新的 MCP 服务" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "template", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", -] - -[project.scripts] -lzwcai_mcpskills_template = "lzwcai_mcpskills_template.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_template"] diff --git a/lzwcai_mcpskills_visual2url/README.md b/lzwcai_mcpskills_visual2url/README.md deleted file mode 100644 index d491df8..0000000 --- a/lzwcai_mcpskills_visual2url/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# lzwcai_mcpskills_visual2url - -基于 FastMCP 的 MCP Server 项目。主要提供 HTML 转换为浏览器可访问 URL 的工具。 - -## 功能 - -提供以下两个工具(Tools): -- `html_file_to_url`: 传入本地 HTML 文件路径,转换成 `file://` 协议的 URL,方便浏览器直接打开预览。 -- `html_code_to_url`: 传入 HTML 代码片段,将其保存为临时文件并返回可访问的 `file://` URL。 - -## 安装 - -```bash -pip install lzwcai_mcpskills_visual2url -``` - -## 使用 - -```bash -# 直接运行服务端 -lzwcai_mcpskills_visual2url -``` - -## 项目结构 - -``` -lzwcai_mcpskills_visual2url/ -├── main.py # 入口文件 -├── pyproject.toml # 项目配置 -├── README.md # 说明文档 -└── lzwcai_mcpskills_visual2url/ # 核心代码 - └── main.py # FastMCP Server 主逻辑 -``` - -## License - -MIT diff --git a/lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/.python-version b/lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/__pycache__/main.cpython-312.pyc b/lzwcai_mcpskills_visual2url/lzwcai_mcpskills_visual2url/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 2f161ad178364c18718b1c89644851af355510cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12742 zcmcIqdvp{TMKmM!lx;%7O&$<6hoOm*}@#sC9iSXBsz&*O|{&ByrdU|F8!)5Ou zx6e#fb=6mouBz|%J*xiGYBdpf{<7nJ-+3n?f5jK|p(XHgZ7xg55%Lh>Ni*RYUgKw) z85(PvH8f_MSsHWA9F4WjS{mz`bu`wu8JZ0Y)@A+1HdC{y&D?CJ@0{P#W^K08wAOEH zvp3rrVkEqd*ZUo9In6mxqT>zz+_t>tJeoH8^V@0BgYGfi*`(@TIrn8DjtLF^-* znZk5~}Q$i0Iv66Wy5 z!mOPfUvhwJcJZZ<*6?MJo-0(gKBCml3{NIspq5iGv_6_yrlfJq&_?+IPNi+GhPJlf zOsSUh6)=iQVLGqr)Wer4E&toi^w8f=Z_dR!P_iR?2_21&pO!J2MXCN^hW{UWGw{=4 z{uc8yV1;V=nGh}!%K56@a|jU)u+mG^?`BAcrb9E62&Gj-bkaST-gQjIu4DMAyN={ zK_Q3oTCTW>l z)iY$&GxDZW=98S#4y|ES+f2tQ8*CviOSk~?FQ53$(M!yitZJAV1t!T6#5lfN47 zETtQnPN#Ega)y!{8&=QE3zAp#(OuH1tFCSf@`9|TTU2IYCBt>lFf=py!duGT1YW>> ztnA`B3MU{V{OyB-SI!Mwe(Si4vIJC}qwH5;4ws9Qxll*DAnU!mf7>CQnip4-JmSRu_ zflDhJJYoxMS4og>?`}_vkSxi%jsMrOHjgh*)7~N5 zQ)8k7mBg7aoND+>eh7NVgtcJIS~X&=iY&Z;)Y{auHpZF8xS|oRC}wbsTOF~S;(=Ka zhco6Vidi=&%tTv!o#?c>gpFv;Dq#T%o|LL+d3HYvYYz;Im$2g_k<@Ps^@f*xKF={C*WP4QQT z;%~hF?dfx}P6Tui1Ktz^Li3oW%xR;2g&H0xHUmQ8LquteA| zK-%JV{|wLw5eVM&ZZ{wFLW1-AB-qEi&l?hPk$y{PJG5tUHe}G?UPwsDr3c6t+_VJ8 zXlL~E1GR@+uM8VSvL^hbr28*|;fiQf-6T+3D zj|#awqiSj0Oc&Ed+$s3&ATCW)XHF*E*Wkyvbh0KW$sB%@%(Vu60RSGKNM#7D@lGGE ztGEn$acRVP5Ye=RU+;7(TL;K|ATZtT&istYtWk3}0I9SPf*ukx=SR%tUC@$-1O^ z#rXm|gEH$E0&>n;*!lpTYlGo{0-Lf{K=hRD*m8pagB2*8x;V-hGP4sP4uD){1PN!; z36BDChv>qc`{B?xORzrc(al^y9g z+%cH*Vpl&m?kGHBK5ULu)|`@tYfp9z?LJ$6f&V0MF%W5dC{q6LsN<1XQQ7x;sQEos zzly;>t{aGB`cd2xsk&^mROG7NL;JNkV_PjCs%n|#{ zskAjiKO6E!N*0Y;76aJ}>;I9^LQ_9Vbujk*#dS-_uj{mRF81OyBcw07v~`R07i(Bd zFQVa+>iSalQdKU7OH1kt*pKWCq(3UqLMXbR6_-)0fk>Q>0V27GfFVx9U^NDdFj$NM z&ZSt3K@|olyhOBxX|N6w(sT$=gv|PmW3@8}XI>)^B}`^*p(;)wsno`b*~JN%Qa6ed z4cJ*6h!hYcNio8K7}0lV0Yi;-?Y^WC!M%s(j-uP8gvS&J!|g!@#sS(*8o*SI6`&hM zJAKk%l~IbdmC}?D$O=#(1hPTmR7R!3A?$?qQlH_B_r4ZC@qGN?%b>QZCJJznPDB`# z4QiH%J``CG$}CzmVgtTud0|JmMZ6Qz87c-{$j;nU4=DlhQ=~)D0!l`4)vLB%?x1zN zXvT}(kg$v!Y=d)DD6BL(X|H{5p%*??$HC-z-cHb92*w5Fy^%|XeK zM&tA}^BSH##i^K#lC(98^yRf`U0$~v1f=N7klJ)>*iC9MrPMFZbjpyO$hA@ z@-m0ueC1OZTNurr%fCDmKlE~@&mn&N^yPQnT^uZ0TXKzs6M*|0 z`!+^OSB_d%jTcQDvr7 zN>TIz-4pPa?t!3(e4{t_?%n_7xV?1n;SiJ&}^7QQNYJVHsTsRNc^So+4gTQlK4r!4+st2~)^OKI$BLm<-)F zHJd6G%7{=3*+MDnLR}xw^);`hRtvOs*2N`fJt;pkVJD2zGJfU{s#1$!>YO;iNO#|89O~57to=DAeswe7Bx8@c!DVu5^Bjti!mAxfxMoa81 zXy8;P)f<-~SyMf9qh*(}`O@P|%g@%7giM!qX}Z`h?J0w5gR1kwkb_Thx0_jGn(w=a zS`+r2DRbX-0T%ootSgr}jNZO@?~(Yk?@#viOdjovA9@?S>&g}j*FfRqOYg>ehn<_i zDYF&bzs}24gn@92fB0g&?}emQl@xR`N1XvgMAwXL!kxBR47Rn0sACX3Jt0AqIyH}N z2S?*BAW`Vd)CazWy1>JzE~k5ozxYD@{g+mPg8Wka)z_VrC(=wXMP}lj7bzc5;K5xf z>%hk?1bA7C?o#kv;3t3vuB9vhPE>eJk3-yzRVk+aJa;K9v-PO2m$ zNkDQC{?a7~E|VX6$aNh!29bdxx%YmY_wn{<-ImC`TO*E#M!AP$7F$o_grnjrv1v`c zT+E(#XkXvH0o|YmgetrjmLA!4c-KI8w6OZK!i~fEXNpf3pPl!qCc1EAuQ^swa^%s& zj}G{U8lnXYdri<$@zUYeXi;6St?&ytVWGq<$^APl2Kv~ z7I(4T;4uj>9C%fFW@lhk7GC8IQza#MQc~J4Z=71Tp8@|;%Dd79*a8>>J^7#?m7AZ2 z(Lccw@-lmb{e@Oby19)0A^*mQH%;vW5R2PFmXU*%pkL!|rw{1Y)4R19EoJ)>a~IB| zW~zSOrQOfKd}@ELO?z7?HrS{a^^gGuXSl6h$^eS{AXoHYkww@ic*CH8P+UL- z4@_7JQ#iA-8MBk8UgFO%#}4{7oVs9M2wdipqH*7d8IB}o(33sdG%9=l0e=au0VE;A zlHyCMxC}o&65RK3)hBZ=&i!;-L}>kd&F%ytPcn@fh}JM`G0It|xlV}oJ`Kq3yrQ0s zh+7?C(3llnn)-mCnH?(zl#UJjupt-Oy>fAh5y1Cbm_04_e zNa5U}+M(Hz{Dq^2MKOD2#88>glCqyLe+PK76yT5?sjluEsvBAyDVTq@s@F1VxaS+2 zy;uJa9F^mr(i)hN{dM{EcQ6-=sv*3j&0n*UTqRr?cE8~g|$*%fU#bxe!-ThQn- zXtcMW(Pz-;0IX2;zv1cPvN;b_T+Y^sk+)2#=<(gVyIDYJ9khAy4BI(u&!DvK`F zb{3^pZ97GiMfFl?x{X~1fGi^dO(%d*w;}EHZbi1f;o;3)hV)gG76wh2E{3=Awxevf z>2`I~V4KYm-DTqKyyH}k3cXYNp&*wVMw8Ek@?C+IBoOtSyZooa@%P?MsvUUd8~?>K z@weUsUFq_%A?KVrsUzTZ(TZ?n52hU+s*9-cH;E5JDEc6f&B}qh8?_H=l6O|^3WeIG zmGkGPkK${DeI7h?tMLZgAY0fwzteh8M#Z~3b+ge`d^hMT!oCn3t{#RSu8iZ@vEQFNgp3L7$72jTF5QrbUWo`Zht5;COZI6&y9Rx(7$B^96W7GH`zD z&}+A}Rzzh=&VjF!JUa#Vj-|EK^)ARdHRh>zTk*dJkl4HxhFPybqKKF)?t{Q(PD7~( zN<0yNfdQSY9!zb70ObYFCJ&GbC+945o6e&Ca$EAG+pR#aWQJO}e91@g@pfQ)82lOj z5?CY($RhArdOJVUmwag`h}nw|b@z1-JUsYd)IJ9_uW3ix4z~^7KfEYfxD<4)8_EG> ziz~;9=8qHsK%X(6HviEYEm}QR)HqVq7%f`YYl%5(haVqqx=Quk69i4I|m*gXo}?D zIbyxDcguvMV9YUn#4&wv&v5;yV|m2BB4SvPFp$F1SV3hhZ^d_du29$0blpU(xnq`! z5lhA3(s#Hsrqiagt0RwYkL+w2U9fA^;)|GDBV21zt7~8ySjG!;l6p{2pukuKla#dG zAf-GiQ`eHG;f5R4S~N+mg`}<9tUv~O9xCzxvMk0Ko(F)_jJjE%Bn?Yy|2}d`r$R7X z{a}-$OU9s4DPv1&RP^Q&j90DkG@Pbqjvef72J|N{{EOgk#4|KX!>L-?GNJ`NG2>K? zQSLHP$S{PR(EsK0{h)+6Gj27_q4#~%ixf>13q(Xy5jDb2?Sh(xH4E_GP9Wnxkvo{Y zP*fu91Bme9!Mg`^3{SgfhtCfeiv$T+*Q{KKP%i7(k}q(LNP@davSkXfPDJfP)+t0X zD~1Ci3L4pg7lSfMMVvm+(DZO^Lx8(TQ~U%_EqU!nd(`S--4 zH6<_@EE>1vA8PGujal;g>%jC-qH)XgSoyNpwAteoGsh}wM=EN^U8|z5)v=ldpdM5% zfs>ZX-0OP1)pkv9wwt~uW-w7O_@0GEY4hJ<^FJgskoiLbbAO=O>9T)HkHDbz+t)al z!%W>0-M_Pt`c<8&?k+fBV(OOZE;>0(SD7LI;(VrV1$S|Q4%5pR%wNG^`YzoXtNs$h ztl=z|G4dtw09Ju55{tZ=Bg0+xpBC97P<;A-#W!BRNl|#Tsk`ml$ zyWev=+alwXHq#q;+p%!q*HG`nroN9%@hCzYDH|j^HRNVd2u`JyEha7A)2HmW9a~D3 z0ZKexqfcwTno7r^LN%RUtq#4*5JpUzID0Ps%$xB;zv*0b_y73`Plpz6eNW68?|&Qa zH--@xZn$2Sw0)9y9Fup$KsKYxlzLvnv#}CA?YJ!jxhxsB&rGN%b9><8mWZ>Ww1`_= zap>;EVy!38;R}i{V=l5ipcRKsTE&luYJ%zp;qY#i_&VfVhre_j0&sg6k~U1gVU#OL z`Vb?I#iLxU>QKZ2vr~p@na9@+u826QN4XjxQ=BOMHG}yl zN{*Ebnj?;Rqg?g4wKQhUIkc^BTP(NmNX_Az>sr>P>sgE4+k5wna)lF$`!eEKKFY0_ z;Ed0%e`@`~*3Y>T#S08~P;mHew`-|#bX*U^7gs
y!5=#d6F#O0ztu>iL4Q5w51y zP(PAL)f^MPD>zHBR78xBbJDpr=<@eVN&L;or;%*7YdgK&{UBf;gHWS z)!<2~*Y5+NpqvOLvxUGO)eJ>0R8Cp*lq$;Q1hp8C_(bGG*$fxEf?|kXLXfo$jcXoS zryPMaAjz*VpuV$3m|BcA>~6Ow0K$siYn9+)58XGokCbazOEJ}i!88o$UZUH`l;Io; zVvz)aY)qTK&*8geeH&<>Ni|V+G`8&!cu@Qv6r^BSgmW&mk*N+xw<6tZHyq{vXjr9O zCR;6@24YlOB)n*wU>SzF%IO*Inu9QwFNyI>V*8TVza)^h#YxH6WYJe-@mIu^;EYTo z^A&gRSKQpMxx2pR8oz@Y1(C|TF6{lJ`(pR!)?@7r^*8i&MoDGNk=vv1&5x4Yt2zfW?fcmrGvhk~!F5|6Q`kTEI)Ui=yfR3e zt`mq7tMttB0X9J(8k&0zqeMQ()HD5^*PvcKldutuVT{QgVREBPenL~o#VkT>Xh4>qLUO(UWL_!N`7`4vSJ8R5T zHexCp5XLGOjKI&dV0g*c;`Jl&Gp!%dZn&xkQfwP~#Y(HnnBC04mIMrYH#4+ViO<$5 z@r8y@3&z$rkF0NwY~L}qJvait^}&d^_j_0kX1(USRJ1{PYh*TPuF#aBIHMgADy0icl9vX znQ6N-x_ckrIrp5i_nwFTQdDF_@cg~=W6o2G&_D4Y`{?WroBjoSUp>Gk&>l3q(-QHNxP-p3iHK`ja~99 z^lMdil|p3J4lUJNt;k>SVl9k=DY;a75xC%I0opRwI)8RhHK=lzhyMz2? zMG@3nxQY?*;3INTCHIG^MKEfH&S6xGU^J0DAN3*_Z2=#85sWT-IEFQgAoPqOCyjOy zjIjshFgnH*QFGL=J_5RugImO?B6^0}3~EO-{5ug1jC6PuI1d;z!PxQ`#tiF43OZJR z{yCtRJit-M|0tpZ3Jc*l02nL5Xc=pE*FYc47L(a+OmTKLLjS}kDn}h8qqCOi)yUtc zu)U}Z7+XK$TmEY*dq09*9Yn%aAWMm&MM+i`d@eW&Mx_K(0+=#_F)?L1OgZ!_$hszP ztq9Fj_EY?poZgQZ(1P-To4NnVYQ>vGLv}51hk#KuJva+8$a-iZbhaOX?;Fu}tM{QQ zB$~4{;*xByTUCvCJq0$-QXE7lmmWm}>VxPgP!%WB>ZFI^kZXZ8&>y!8Xb@im|N78SDZ}-`1bh* z_OO8-@h~uNShsE=o@}=->=%FF^M{E93vuDuABH)eWq^suhuH z1xec-_j4gM6=-gC9NC)Bwc4P=x2QaHw^-T0VPeh&o8hNwO*puxmA1_oNFEj za!IEMBWZj*?}PBbiEN)BX@i};5McI84j;pC*wjA%GxDM3Si!B@%j3Mlt(Vk2tSG5L zT}k5)9%Xq+#|aGA!@(pM5Mf_`5TrDcuf2okgS=$e4;CU_w}HnYM>5KXkXTOX6T6ue zs}bu(6?_NYf>~Pb`&0t<4&-j&=H3fC8V?N~uJdt+0EZPl zTsvp~0SPzppD)C$M}!%h!c`T}OO9tg_;T72ycy_Y2;BP6+$_4PoB@oon)W z@pc6Rzzs5qwV^@Dsf@ge_Cc?L%8Jgf1DUZlf)Sic6lH3Vt!%X7Ohx?BbB`yBYoo2H z;!@$NK0~S^TDC@hU(AH}@HL zymd^Iw5^S{rIy%Fb0@gC;ao#_EDVJMe1zJA5W^C7F)kc`mP3fKY?GI|+ zYLN%~5;>Ghe)uv7zPa?FR|~ZFJrQ(K;V?`EAh{c+3OG_CsW&i5Hk}f* z482MLJQQ% z8c4KM45?2dG)^)iT9{oAb2N5!y~Q$pVVh4CD0H%gMiCWB0I*jgPj9wH%1*!=5&;8L zMLC_iR0q*MluHFP;Saz(BpBRRJmgb>e8GTA1uhT@i*l(z$Vjt5K0v-~V_`W0IX;vJ z>F8BR4$`MD&s=$HHhx(yJz(k9rMG7$KA0VOHT~fSbMO3i_Kg_qmL3_MJ3mIY$ht#0 zt$;v!?18+&=^~i{wuUD?2j(*=OY(r5LT69IlXd|S7g$4Mq!rChAzaSq6Dzzy!X4U&-)xPTz~ z0$nV>5~Jx}AsE2%&&LRo1?PFqY*&zxgPcVBIYE?YY!%|sC9N+MVgrm@$FIlKCgT4E zFM*lKVNfCgiHs;Wn47;csmCJwQdChaKJz2T#L>qXfvXvvI8E{HRE<7@ATAeC)j;pSz5|*Y( zeG|C7;)+yhdCF0GM@Kuf(LEV6vMu`>88nZMXl__4;tyYNOj~MFX2(o*^R)xX>Nbb~ zBlJjj?8rY%mQ?-5kv;K;!0DY66XsgUQkk+o6mL&hS6{c*C#?13^q1C+89Ok`lp=Ha zBwhCHHye=EaotjtuvCFV1aVUjRgZ7Kwmo6rIc44nP7$W=3HW;dqGWq9`n+h0Hy**DA8WtcN8*JCy?^^ z{JJ&#R@}$efiNUYc~+99;-m`>0KOBDw!tgt=ON~xmoXs&4J1|$ zWVW4dJkc2Ij&~+aE2GURTluKxj3?faw5^%4wT<;(9(s3ZqV@|_vc3%xrQ)*D$}^Sm zJ!93$;`(UojD7vMCux5YZUWqml2n;1RaTXA0+9$-aa1}@OpVW z9vW~B=)<{n7%MgRS?tz(*lF*FtesjTQFRjKk*Io59A9`@aa_*I9tp?8FdoW98I)s< z&LO=VMjD+hw{c{5t0XfU5W+m`^$A@ZCuz9=!v@4K4r}Spj;GIGY;?NqlFsX8f?Xij zMwpkhkch*dQanC{{1bRaL+T;L+2PWHI5Pfm48Y%p7!<3AqlMQCpPCg!Ij@z)eiDPQ z`-H!r;14gT3z%?UNO%|VJMap2V)yi$`+`i^&u-zLg(1FI0{%}9t~W*9Q5h)ut^-l# zzoF8M_Inid$XIg*;rIA~i4E82Ys|#f#KyL}WGZu5RY#SN3FG_U9m*i+PB3@zEAu>6 zLTND}yvFz6#ji{QqRk^iQ|b!YuUVJk&*vzY?5pmWv^H(D^^O%S-!jpgT(&zwS4Nvh zYGSLuq75T$aXQvNX str: - """ - 将本地 HTML 文件上传至服务器并转换成可在浏览器中打开的 URL。 - - Args: - file_path: 本地 HTML 文件的绝对路径 - """ - if not os.path.exists(file_path): - return f"Error: 文件不存在: {file_path}" - - headers = { - 'Authorization': AUTHORIZATION, - 'User-Agent': 'Apifox/1.0.0 (https://apifox.com)' - } - - try: - with open(file_path, 'rb') as f: - files = { - 'file': (os.path.basename(file_path), f, 'text/html') - } - response = requests.post(UPLOAD_URL, headers=headers, files=files) - response.raise_for_status() - data = response.json() - if data.get('code') == 200 and 'data' in data and 'fileUrl' in data['data']: - return data['data']['fileUrl'] - else: - return f"上传失败: {data.get('msg', '未知错误')}" - except Exception as e: - return f"请求发生异常: {str(e)}" - -@mcp.tool() -def html_code_to_url(html_code: str) -> str: - """ - 将 HTML 代码片段上传至服务器并转换成可在浏览器中打开的 URL。 - - Args: - html_code: HTML 代码字符串 - """ - headers = { - 'Authorization': AUTHORIZATION, - 'User-Agent': 'Apifox/1.0.0 (https://apifox.com)', - 'Content-Type': 'text/html' - } - - try: - response = requests.post(UPLOAD_URL, headers=headers, data=html_code.encode('utf-8')) - response.raise_for_status() - data = response.json() - if data.get('code') == 200 and 'data' in data and 'fileUrl' in data['data']: - return data['data']['fileUrl'] - else: - return f"上传失败: {data.get('msg', '未知错误')}" - except Exception as e: - return f"请求发生异常: {str(e)}" - -def main(): - # 运行 MCP Server - mcp.run() - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcpskills_visual2url/main.py b/lzwcai_mcpskills_visual2url/main.py deleted file mode 100644 index 23982d7..0000000 --- a/lzwcai_mcpskills_visual2url/main.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Entry point for lzwcai_mcpskills_visual2url -MCP Server 项目入口 -""" - -if __name__ == "__main__": - # Import and run the actual MCP server - from lzwcai_mcpskills_visual2url.main import main - main() diff --git a/lzwcai_mcpskills_visual2url/pyproject.toml b/lzwcai_mcpskills_visual2url/pyproject.toml deleted file mode 100644 index 2902e11..0000000 --- a/lzwcai_mcpskills_visual2url/pyproject.toml +++ /dev/null @@ -1,31 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai_mcpskills_visual2url" -version = "0.1.1" -description = "MCP Server 模板项目 - 用于快速创建新的 MCP 服务" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "template", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", -] -dependencies = [ - "mcp>=1.0.0", - "requests>=2.31.0", -] - -[project.scripts] -lzwcai_mcpskills_visual2url = "lzwcai_mcpskills_visual2url.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcpskills_visual2url"] diff --git a/lzwcai_workflow_to_mcp/README.md b/lzwcai_workflow_to_mcp/README.md deleted file mode 100644 index 4259a9f..0000000 --- a/lzwcai_workflow_to_mcp/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# lzwcai-workflow-to-mcp - -MCP Server for executing workflows with dynamic tool generation. - -## 功能 - -- 从 API 动态加载工作流配置 -- 自动生成 MCP 工具 -- 支持工作流执行 - -## 安装 - -```bash -pip install lzwcai-workflow-to-mcp -``` - -## 使用 - -```bash -# 设置环境变量 -export workflowId="your-workflow-id" -export workflowExecuteKey="your-api-key" -export backendBaseUrl="http://your-api-server" - -# 运行 -lzwcai-workflow-to-mcp -``` - -## 环境变量 - -| 变量名 | 说明 | 默认值 | -|--------|------|--------| -| workflowId | 工作流ID | - | -| workflowExecuteKey | API密钥 | - | -| backendBaseUrl | 后端API地址 | http://lzwcai-demp-corp-manager:8086 | - -## License - -MIT diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/.python-version b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/.python-version deleted file mode 100644 index 24ee5b1..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/README.md b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/__pycache__/main.cpython-312.pyc b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 2aad1c179ab0cea9701006721cbd69c1409c6898..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17061 zcmdseYjhJwnrOGwdds$K%WoUY4;}(+-j5gvW3V9xgOa z#!g_cHxHZ*1T#x6W^saZZ+7s8Fo8*SLMFLq+Ocv_dORmN=UN8tpLKw}$!6x9d%v$* z-I9$MZ_e3Yw~ka@UG>%L>#FZnUwx5~V5A_t^glaYBTFdif8mQ943Wg$FI5zEf_j2t zsalGqSydxlOOvOnRz;rbS~YnxwG4S`YBl7kZPL~1Xsn}d)HfMw4Nb;cBY9^UO-<%n zGf8V26Pglh6KTpov07HwnABvcwKOHyCPS%~)i9s z8&|*;W&r?K#3rtUr;BnHbA|Ahgm0|n1*&$sids*x$uCfBirjYX43d`$c~)-51$Cr{ znItz2a?@jSXR#Taoz3KC)H7_>3ry{7HXG6^HV4vkxcr74QtjCILgWQ%6>z$S+SoEF zjbny3a$jI%*xFKP%W*%XT+ZgfD5i0Rtg2N9Gm~20b3Z)vxA*>BtOF%%{1P%6Et?-R znnkP)aJQH(fOrX)#}*!#MNzySzQGcCL^Rc+YEczYTuw2?7n4;oab3`H)&9LM3QZan#o$!m7jK;CoE{+|2 zZglYM&2z`b`p$$uI$mZhu{C5c9ob~{oapup_)u`Z9pOJ;W)*E}>d94=P0a)*7qv(D$Jce$O74v*gl zO~Y`KCcwH_7>w1+`TWfeZ;RK*H8~nR`}Voq`$QuG;tR)%>5Z+2>YT1pnpOL>axTlTntdu(!)p6=7b39qgX-yH z>KRyLzv%)awe!Fl<_R?tCe@IsxjYzn%DZg~Wl!{{LB_3f|2Xz`SGfDi=;gDw&KwON zKQi|EKx?Y9PokRyzN3ldymh>btix7qNlBB3tYcPayg-F@>{7sJ1PK773A_DjcY zazj#JB*fagmbA zyXZmLO-`4)w7Er0Qbt0C=jErvAm+l~dkms>YBVA3MndsWLb0%5T`*yN`??TgyuoA) zF&QDfWhB88O3v(=E?8_KOIj#l^SFu9WZt8+8tr%@r7_8XX$WYsh7y*J8wh{~0L(D} z88Sfa1ORi&4O8xrDR2p?loGizaT@ky6!*BM5Pk(sx{mY`32b#sZ>l6wB&10HSk3`a!4`L}C zq0iGb@D-7SS2Z;^a`@>vwvy*PJY+LouCZSFw0tqt5Sd2kzBMU2kW$Qe1d6s-G`hU7dstVU zk1vG=&Qqiv(lZaNm?BXnB)muzUZB2V^2ZrkQ`p7!EbeZ&M?q4mfxz*RJs(OYAzR0) zIR*q1<^>cFHJp~yMNqE=)azSRcD-n-MqU}geGas7|6Z1834Dhd(LmS|Zv47f>Wq|? ze1#q|hydMQ9{m#W=y*K3Jw*-V6O)1uzYGa7Phe?TDr}{?Of@rn#iOJo{6E;mp-7f(1GeCB!=ep(*oTf2%cfd@%@5dk)vw0Z!{1&rI_Xtl=R zvsBJ~6hOTTAZn*VCaYk|9m&i-t$R~<+We;Z?BnOQpV@xSamMk%lf%;=7Jjj9ICFbP zRVb^lgE^KceRVrjov`c`ruiXkfD<6bN7*JY#n?{+H8G%a(65$-<0VuZ?NdFgnY713F zEug&gAr(#SfE^B^X+(@HqI|f|NdPf*CQZeREmFsvXd)kCAIO zq{yl06UE{-^%VW-0&1AweQmQ;oBCPx7MLYI57CNPtgjJizE$J*)t4*>s-)-KbsiQZ zF~0>mR>W6n1KRv%$h3`}I&=HQV>hq7S7!5PK!)@p$+n||$HMRSgirQCSt^tfu~!l? zZol^Ct&11SY<8o_c$zskS;Mf}fsqrEBx)maALDY@dqj03=N6OK!O{o%xz6KvOO#X8 za7aMKBy72YgTZnVMP+W#s&zgPK2J^*b2-<({6h6x^Xy(1$tYcy(vBkh5@L z@ui2~e^^+!M#x$lG*^UD(_XF`NlLwuR5X-SG!eG6@9DleA!}jKya>RKRsCgL15N$p zEr+om$t+(&{X@B?+^)WsZ-DeQyQX}h?pmoD(+f$wq-1Tj`gg@C7%$CQo2LFGiH7ti zX&Q+6Inc^^13wQQJkoIc_JYMDN)Tn48@K@wODV)`^*DTm z1o(h{z-7x1u?P_m^Wli4&Dr(!cyXKZs58R%px%bLK=mRhh0_tUM6DK zr)W>{q2(Tdkwtrp$DY(t_SB#-kLU~oH0p@`TA4LY97TX`h{slRa__AE0PG)rMa&o! zwi@k&HB8kGuuJ0uU`%pfFtQlNiFkfM4YOPWSohz?O+rA$XHJ115Fcal%7y%+7fxpxQ^bsohH_z@^^@rw*t9swif}D638iY?2RUR1#u^jfpH5*mQK> z@m8QE{+fcXknBUfRBB%><8dP91Hi~Z5Sb>!fnYGBjfv?o-U>(wFo-oawcjdZMix9k zC}5sth;RVU7~vqT#*f5#?BqX<9q$_b?OQPf1<1>-i*Vg3Sw@Oq)%p>cX)V&R* z30wHx6JziFbD2$KYCLYvUkq^35ypamnizijmGDPL!h?e#10f3j=H$Vg7=HQmeJDWb zYpyLKS-n00)&*z`WkSNtE?ydY|A;(|t>;l}J6s3yf4#?Ui4u6e3gG#57$I5Ww_sEO z5m0`AQ?pl81M?G&F0gufeNJ~BC-HV1vZy=cm%;SO1K+n42q@rH$S4p$w zUh~VZ&iZWbwYk?14zJlJly4Wd?-F*`-`L$Sw7Wq#&^)~RX<-*H2HLeM6*wpYZs0VUP3c#e0S3gTkUiLe}A+xdjGp$?hpW%LJ3A zhq9;jq<3pal5=`0&K3ugXNI!!y0xQ#hs5qgp`h|p_WI&a8m{gX@*fXcwuG{By0r08 z;_w8Uy@IcGxcM2u6A&`ng63yKsaYMHejH88oG4J1^f~*s2w4jTD>|x!=JE+bpW18^~lWg_(z%K3o$8o3tmYo)$Vq42COK*jQ=BeY%b|92RK z5w}9PBu(>3$R)D{O&&ZlR6?NaGeV=4|vwbUhVc5BZ{2!a=OmDkjwNoD+^IkidYZh{EJ?_Gg6cQ){tP*YC*qx zG%-`inK!`n9Tc*b4ks=X^vlR9MV%GlkBF z^h6|zrAg8m6@{sQQ^H*@QDBaO9gqD^3apA%%kom2>H#o{R1BKmbTBE!Z%LsY0G0AU zC~ZuC>X=z0y_*z(+NfX!=mTnA6Hv2oVw6pRbZmk=^C_jb2lk|#eYY_IhA#sXUV_{~ zK$%gSW{NQ+`jX^Y4Y;5tg@A@l>bJ-kjHQh<9_OnOb9d`MERAiNPWo*e3-xN1id9duO;n zhh;NQs@pVdZa{O8<~1$~@R&<{5EwSkMdKUNQg#~V0fm`9sX=)4&6M*R&}Kg=1XKZa zK+~TuYn{>l;ne(PW~%*WiyChOlw23qT|q203J^o8PcdbJp7I}U+9@!R9MD2LDHGa> zzn>i{!HClT8cg0h{Tu*o<9YJ1ExB zrhiuNE|Q*6-7luycl(O;*a^o|`n5 zvy;d2E||dWg+$W}cbo{n^x;@%``F1&INt@Epu`US$OXq<`%SoGz*Y^1XIs&azy>-m zs9vb9U3oR!d2FJBCNkv2hT@T0iALm#n|V)DGbmYpx9e#?R|BVVe%!a=voGB2`ny=Z zTV}($t6CH1+G5VwSJm7mHrSm|r&V2AS}GdAcLUCk!F9urdmeR5CC7H_O8?lgUxOHB zBR(Blh)W0P%;#x-I{^Ff*gIYgS(WtX0{~8L;MA>)uSYDTq^S*HrI(r$lO|;lWjH>p zZ5BuyV}mckT>WADfrKAe^F zaE!qFz*R>CP4sEMi|1JXDySSqNciuM-q+Rb=da)?1^Lu4*U8-w97rb?_f=Jf_@C~D zoQ1^cg+C1A^zVWQ7J=k#aQoM%!e`%rk2gB_^620zaGoOhV*J}j9pV3WB-X}q^Ih;k zKsN-K#OURB#(E&WJbLAI$@T#Gw@ND+9dgqw_>AvFe0`FH0~0E1CNeqJc@WcDuTQd zqXLM~cf`qhc@(_(4VYor3XUJ-M2DC!L@l`baBf!AphE~8k>WPA8{ieUqo{#R#ka&v z#u#;N63t{{5f>(6l@ODqETSYuR+Wld=*8|)eIh|RPfgzMbhB{mis$B%4-pSiBGm_f zuR#r#CF;j^3RHNF3FTTLwtE^J+yaFK@hy5@g3be zdY&0ho6}+Z#*{X$vL-x9kAP`o_Hh2DF0*3msQ7g2XSLUAh0Qy`v=OX)TBtzN#@80# z_-v|R#yP_ogD`u=ux+Ja&if|GHm))xLo*o#LgAthwgfXC8ksrgT}>B5Xchgu7GZkp*O|}!IFf6-kvo4V zcm4-sDdY9$~sOn7KDJfBAKl@MP`K%%@J7@n8@@e}eNy(7$TB zYlNb`L2F%T?&7O!gst0#ine!E|KI-@FT(l#vRV`&cUqZvn=S#CTEZ6-ZDSyKv-5*Ldkn@K{8q8Tf z4boqvYoYQNxf-baMS&XA#aY!eHD9dCuP)SlnTw@g7HS~p%b8e82!UvjeX6_@j_DMo zk^&>oe@>}HV-5>SADgQBbi zC^SlW>7$tebsKmhxM>Ew720RSh-Qky2HzkOKpmW5f`WrcML~mOpb2G1=$N+R9WxoH zm^UcLtO=Z~fgoa-sNMxsap$L~osnZwWcMVs2agj2%c&fvFN)-b=q@Gl@>89wxI>kJ|$Lqms0F*N1^CZNm;jwB< zWbMdM_yo0u99BiYaLgFvt<&@=#;E^WWAqiteFyY%kDyrD<=h6ehEED?&d=+yKUZFT z`)G1_S1dOOYTTkWL(Kf*?dgnw0apl{*`Fo11M5#w@B;>U+yN@Uu-RZb(1R+bZ8JP; zILb-Ss3Jy`pFHBrb8F!FsKSFbk%+>x*4fyIl2%0Dk(CqkT$@5$SFPjyP=QC)q=pb) z;tYgmg`K|zrD>vatJ+K9ve?Ao3lqN<$ zK6Uf;H%A9QRD6CXb}Ahgf@{UgI14zO1UGoEpCGvN9C}&HLcgR>%7>c-gG$*L0hJC2 zVSuPqPAa>3`PH$vPsT5kb(O6=Y8fE$SK&|$C^r+oQDT1wKWkJ6RMIiuF$7GH5$Y=- zLz3O`TS<~VHpB17h{*W3T19oUr&$u}7vrne-wcPQoYd$%1VzI$kIrL~5`ZLu|0P7C zX)E+kv=X<^Zj=Q14pKk{D!J|RM-cuoj9$UA^oidIefn`4q8YV74&-$_vgJ91-vp5t zdyfeB@Q~ztOGLg0*V9HK-}A2l)b|M9rIN*abVZiLdt&<^*g51m{AuxLv#-s*zC+*| zzFu=+oT7e7SE}H#hF*t{$RJM*GH*cj(EzLR|}>qDobR zxY$8IiimlV-i(i|$5j7LQ3;PDe&$uu9qWYD*+b?zN+XLu*eaB59$xa;jU}~1OKOG1 zPeG-fbTz5F3ENAkQGvLhQ5Z_e{9dEBYTK(pi%`DP2h1ho`^LeQkt5&v^}@V;f@Ob@ zag8&H8slhk0pKHfUWXPDZR#`$=_P#!`gRG{6+!*VDDwM#7hBJ_4$ipl7k2Fq&T|N* zdxSJ+P`?*czLXig^~1>}9ol6AjT*EL zwZ^O+r@*iM$9u_?IqeSkz#F685TCxZg@ao=%t8H{(X`B-JW_70dYHbJQJDvi-!Cc8ePlItJ-HNKuCF#!tx$iiw^uDyf4)EiIiD}p zV9p9Pr0bxr5^%#l5C6Mx#Yz@~AteP_1Y1l!54ZiE0oMG6>V*35G#aWc)~5;t=*a=j zP*rHzf~OjvF=wLSy%RmTig(81Q_eQz{#_KDufjIO0QY8K92n&1^Kjlv#-^6WM(&Ei zc;y<;gQtJ&*x_7^UPc|QgMSA64d~oUU>KMmX=54a1QXnSY3bG&VU>En4LZN|aTgHo zm^)*(S>%qDe6^&8$MNzyj8LR))yywlP&yxPI=N%+a5+4YOI%spVX$7g@KONWl&W(! zJNLR8;pP?Rh1*sp=?;f<$0;%L0#~=ZaQ{m*PXN~Pc-|;#B_L7F``tW#AJKxYjj^CS zq7i(2b#T*!0|VlOb3CZ#kec961K2~bB)naaAa;(?q?}it{ed!TjN=&fW{f0Sk2iES zgv_a3<)Cd#j}dc0D0e|9f5u2&(T%*tLwSow>??=u4~I(Uk87xDOTYzmTFO10E+O%* z&Xi>Qfil7Vz~~1xG)kKP9-IGhTm_jwj$`hRBs*I6S1%eek0h zv3v!1zS8B(wAXA5ri)FGe{CLJ{t$C*z82HVXv}|z#`Fs9ngreNXnGA}{+&vPX@d^( z>!4{RAPx~>ckciyrapg^1+XdA0>uMiO2`B zKDn{@Di;H1Ovpwjg-LB7Z6P5(G~}WfI4MA`Wl|Qr_G{$b4*r#jiWt2VFeUaBWe6Y@ zg|I>K`iu4$qx?naAkYgP_-&xUV07?u_{Dd^$N#Z);i{hWR!jxPQhYPNZfV zFopa@60w>PM&oq1xIFwx%td+tWQ;+76UhMpDV;1xF?eH)KMOeCK2{4T6BOJL) zA7rv*k;5`K$dt&Q09XLb)SLt2P>WBv?vK3CYKIbnXbHq@O75E)7{TZ&-_mti@pWOVqZnL&+ei<6w|UM~Mjy zwU*^U=Alu>@KV)tRYx1XX0jwd4!Ayp^KdxqOC_&&BKnrWWJF!$ZYq!Gas00_%K+Ex zN{K;Ql46bc9-*E{Gk9Pul$adNEk$phMsI{L$K(?hB2jRQWRON_7BA_7^NJpu!c{0* z{61Hsw-g+J9CeK@U>TA`jeL`c)iMC*J{Y;-N}RpLRH>eHu|h7v1;(RINHoE{G!O41 z=5|q2QMu-c_0ko9N(6ZeBP&LWFj@?enB;Ic-N4Vtbz?7FN+Qb$mz8vjY#F9DV^n|< zSz2U48Dk9T?O23nL(vcwzmeI9=Bg&RvKY|^L`!AUUXEos_HoYZ@$=xI_!`#4^+*=n zByY2y^ed%na1Zmp0TA5G-nSthSJO0ohtbi@T?<8<|3n%7L?!--O8OIJ4pUj*QVah~ zE&4NMA7>17CH*Z^`z^C`h}rpPX2G{i<@Zn{O_;Xy>dendu9bXEZT`lP+V#k=A+KHg zy_Pas1k3bc!;E$UFRmL`Q%v@cd(yl jZ<&g(n2K=&=KthHD)>Ka6=l??Wz#E*)c;yYL;U{$W4;b= diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/__pycache__/schema_converter.cpython-312.pyc b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/__pycache__/schema_converter.cpython-312.pyc deleted file mode 100644 index 83efa78a6aef6ac47c2dce7e9dd05cced392a62d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9905 zcmd5?Yj6`+mhP5XZyEU^Kk-AB9mg`3ZNN?zk&uAR!w(;WfXH_n7?s7z8dj~{DUX3%!ARZP~h&94=Z+Dh_k&%Ldd zBQ`jpV8qE*nU z?pF{*oI?$*8d{T(poMoWoyMpc3h@C|yap21KwlcOhSnXcBnb9rbUK|uXVO`;p3d$h z=$yAmI`@L2T_+(J6{8yiozZhhN)YYo?HTQv(4Ix-G3mc1ej^ckNdmX#LrHdl)TIxT zK^M?##|y_m3VK%HEn+g4w~h(q1guVRdp1bdFxldm?KybN67gNoVO0%s7Hg2?>XYGg@|@6IK}bvT@i#Snevb>N@<^j~Hte>r=7 z6v$C~n}Y*6AbwHbU9|3dOe8Pe>gwa=99|Y)&N3b^>$1m90`H9mO0=mDzQM-rN%Z*$ zj|5XdF&3|J5+AW2jNeBbC)iu5v^0s#bU6q6r4Q&)Uzbdjmi!gXqlq|t1xyr186XEF zDRL#tC356YE-mp%juTKT^hsz5l0G0!;gWjf;us0d$)OD%mDuW&isnO0k2MiU($-Xd z8Xwjb$3EzjHW9;=UD8R|C5P)_WGaP|TF%27eN-1(-_jTvy&f2 z{nxPoTx?}KISZ~KnS$+siT>(h^xZS5vXdN7W4laYq8;dT2v1p~Pa*ACWTm|9xXtNhc$w37gmLo3_o`mAyT{{jyEv1aS35X|i}ToAc7~_WAiT`s zVY)$p6V4GYbuunq+R1oK3YJ2`RA>yV%ht{C8eEN^Bn=wRibn!oB!bu4os7-J&{ovN zlgByO9Re)?Jm2?jw;bv_YPLBJfs`&d`iGqTC+s$dnd|TC6zkpg9y7j^h|ti$O(VfDDJK($)4pq$n2c7Y5J!`Afl@p{hYa(Ep5jJ1n%yQ~5UTL8Lc zz#m7#dG{0F=m~93pyL8PqbZB%vIo;*azd#O91kgqE>{JgnP>}{w|-eaW84*~s16+(m|UH076RZrwhZk}GhBT{S%R)mVz2Q;^{ zibjr&*Wb*lyS9IFZMb3kAIWe-^K5SE=(?coQq8sUo4Jq1l!U4NzhzWq!5u^S?GjV4 z{2Db=(h!poMWq*ao!>S3lOTQd=zB*ePTY`$YZ`ABHpQesJfe*e@DtM#n|G3bt!bOs z_vh00Uzo0G8_|sJ2<5H2vvCX5TBq~sVkMw!%t&Z6{ky-rpAAgk-PaPeyGbq+mg0+? zO^tQ(KV&N!8{~f|qM+nu$d7j~a+e4tV0Re+cXkdARae4cLQnib=s-NW1K=!Iq><88 zf(G2`iZn7`QjT;L8hMHwY84vASX|2i*(wxDT9wd7z6y=HGjS0NC|04-IFd9GVlDIt?h+Bskt&#PKR{2q@@x zNe-U^B){y5BNcFi3J*HV_z~v_6kZQHp!o46)`w0<*8%i)r1I10_;M3*U}A>sb3O&Q zV|B`9pibdadzNz;ed-l=I%Adg%;fHhy2mdA&4Ajcfzh=omx0!&#g~CLaT#b~^|J8l zDWIlDHP^RT>%WuF^*31KY`iOSIvZr*-E;jFT61vgk>twLx#C{Yc~Hy8<0{!3KIMvY zE)cDfRr-{40a|0=S}Sm^$Wge6qcF}<^aC8li#Uqo93=t=jAlSJmm6kNif6;nMrg%z zq$PANU3LLkY&oK$AA*`Vr)U1(&qs5p&0Zn|AxK>?h zFx@4O2-R4PM<;eAIb3QFx=!$uY=!lz#Oo{bQDWT(>*f#6u~(xj7l1zi=Gn>jqr*Q3IGsfMsmMN# zAo?%|E*4`K?Ee9}U=epuy)ysV6#zs+JP>{7=Sh%*Mm&iHpqls?J$*fl(PA(PVS=%G zIR%0ih{Epf?ygrY)nPxg{UZ>|7gqDy7D@kFQ7rYJ@&VQsU4G2%a+vVa$4CwIN>JU_4xH zFq9c)KRp$Xcnm`5w0s>MnK-XnzVFtUG^SHaW+U-n_2gp}iC`@NdP}FqwbXG&b zK-uH!G%k}nu&70N!%fS8um>gvv0tTVsn|fO4T)C)&s7^7oK@T`6PCnNXrd7M;Ruzd zkfE{Ytn-=_Q{uI$X27cwX2UB*aXWZLe3N-~a+g2~!`WE}M%Dn6#ekQWcevSZn+FEQ zAuh(`EDF3>1f5uPBeGhgh8$or1iL(igz{1^>jWMMrkzZ`rq_%b&B6?Xb?sO7nuQv_ zX7LvGXY^u9vpun^ygF$Q{b{{s;mzC)=7C0$sW9dK(Y??u1CF!_yU?VEb><@a-D2CDv(2D*FeEbVH0?TgQFdt z4hS+FE*K2pJgkx$AHVG^>t=b?0oX8HHKnm@5uY0SA1hvlcpZl#+V~C%fk|3i10fvY z)v$+Fh-2XF^O_X1V9Sw64Vo^B5YXIpOU#Iu!cMc44 zdhU2sVHL%(fT0KojR_8Ao%}>>1Cg0~F8^%)$Rnfw5YDLfw?r~>;_n;685QuJm3MB_ z*-aydgDv5#NBwOPeg3)Tv&|!};E}L?y??hzzd4-oFi?Xu(^=C;22YC(hYbr9*4<=iPOxxMzGQTurP%-TALbn*)?D$XlL z6~U~lx$ospeVI*hmJ(;vz z4K0e0(f|urRsVmd5)4_Krn(UFhX-#QNB2C+TyK%>j zfpFu&TaAZ9jfZC%t+4q;Gv+5|n69Z~yKqJ-QOvJ&BYOE8~NRRHBq~Z{Pvp;lE^N(teVQIx=)a4nZ-|3Z;a@@mM43Wt|l6x^Z= z9S&}vhj8&6k&@S1DYKUg;4zWY>q=zYKt}xW5o2Q5NxC4!9UxOAk{+CJN2Fq4!^vsV zK|l&yasl9BvOXa3k&qz-r?Mo=hwc&lBqWeS`V@GY$nqgKG(bWAOzxu~K&pTsQX=H| zd=w4^P!4T9wBclXR|3k4@*yxufMb#v+5y=#$$kPLDkV~MB|<&1g&+W`LQ*<854+@9 z$iXGRs?0k9%Y!fL#aZekR!wH6Q3lN6>hXH^<5Vu@!o_01n%r1RO4>V~wew#G@ERSy zI{WF{b0b%UOk5&^H#hi3^y3c~-u==}xQ3qbGWGF)pgia55n zw-@kVO-;=JE)ORaT?TE09N(%_{9gfW_Yss_TLJnR<9Cw)`A%X57tIdM(kNmcgbIN~ z&|aLs0MmH6FosDg-~^OJV9QXEsF_p(3TS{<#-(mKAIRi;1GP#3qS#3 z1bk)ka$8Ri>p2}j|ghNuE(0YP3CEPGn&dso?$|ALlV+8PAMC~NoM3na`GbjoO=pNFZZ{>WtCZUpHRsj8I+;?;6s=7 zGrEe1RzF+bbmL&SycO=$L29rgaO`W1E>hbt*fvrKu5r{8(pK}jf=GJdNP8%~@>Y6n zD7|)qx|!Y(%K~As973BnMdg0`%|;?!e@j;u(v^XO1a%{Y>;*5w_AT;&iS(Wyd%#taTz^-3Z!GTndP(jf2v}E6 z@FlzoP-U)41);p?;lLNJEJ;o)5;W43Bvir&!nXqwg74K*gi2B0RpiTlq+j(T{pug- z*NFYJ_JWK~yFl$HOl!Q4qfR>1uRrak1D# z;yt%4krKW7$-)Phk}N;qtCkKSdWRLFf6*rj-xgPo)l(uV6kiJDbHl?6pRX9&r(6R- zj$IknT+~GHQ-X6q0Uuid1+VtN+C0TVngOB?ugd}Jum^qCP$j;O_Dh~S%u5_JuYslm zOgE$(JPZVgHjj;Sds#cvO!G=41;r^Q8G8W9ei{`AQSl5aegcJw62`{EYErc2DJyF` z0nrlMgK`NYK_P5MtHB5Acv$ldq10Rx>=0DW0YF0E6aWyF1E^W_v2-l%at5S6sqoPiV2$2Kb|LO1 zPx2Ee0GbK_G$b52iFX{xur&cBnLF*DyKs8ZQ32IfIC7jJ1ukIjwxo zQv!WNW8&E7fmRNy$su$I)}$xWbpGv;jJ*4VR91qnGINj#>*@!oh(;HXpLr|-*4B2q zZA5=lTO3mW4P2Dk41XK@90=f*R%^G-;j&s;6MEHhUW`+4{O(04cK+S7^S{0#xQM$n zG~h(T0h3hdy>baY%Pw5|Wd5}vZn*oJKl<90l!k?CZ$*E3NoWA{fHN5QP#KUcBPvQ! zfq@Fx4dF$eLC9;us1`%NQUI0~!;ZV?Gpb3#YZ({kWf`lDvpXET+~J}bm&c26ZuIk0 z(Mw|%gZBt5Q34Jc6G+YtfXASclD;&~ogIPyvxuJh=XM-sCr(qxMQPF>9Jj;fSF}_1j`?wpV$uaBuUl>n_>j|oj5qT@dkB+o_r$I z(E0#3#e7mTnK!Xju8mIpoWRmBLX zPUMe+(F`aC|X@t#hZDfhSB(-Xy8CcDB# z%^|A5-!xbesQh ''\n),\nmonth_filters AS (\n SELECT \n month::INT AS target_month\n FROM (\n SELECT unnest(string_to_array(nullif((SELECT months_param FROM query_params LIMIT 1), ''), ',')) AS month\n ) t\n WHERE month IS NOT NULL AND TRIM(month) <> ''\n)\n\nSELECT \n c.company_name AS 公司,\n v.dbill_date AS 日期,\n v.csign || LPAD(v.ino_id::text, 4, '0') AS 凭证号,\n v.cdigest AS 摘要,\n a.ccode AS 科目编码,\n a.ccode_name AS 科目名称,\n v.md AS 借方,\n v.mc AS 贷方,\n v.cbill AS 制单人\nFROM uf_voucher v\nJOIN uf_company c ON v.company_id = c.id\nJOIN uf_account_code a ON v.account_code_id = a.id\nCROSS JOIN query_params qp\nWHERE v.bdelete = false\n AND v.iyear = qp.target_year\n AND v.cdigest LIKE '%' || qp.digest_keyword || '%'\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n AND (\n NOT EXISTS (SELECT 1 FROM month_filters)\n OR v.iperiod IN (SELECT target_month FROM month_filters)\n )\nORDER BY c.company_name, v.dbill_date DESC, v.ino_id;", - "sqlParams": "[{\"type\":\"paragraph\",\"name\":\"digest\",\"displayName\":\"摘要关键字\",\"maxLength\":500,\"defaultValue\":\"\",\"required\":true},{\"type\":\"string\",\"name\":\"company_names\",\"displayName\":\"公司名称\",\"maxLength\":200,\"defaultValue\":\"\",\"required\":false},{\"type\":\"number\",\"name\":\"year\",\"displayName\":\"会计年度\",\"maxLength\":4,\"defaultValue\":\"2025\",\"required\":true},{\"type\":\"string\",\"name\":\"months\",\"displayName\":\"月份\",\"maxLength\":50,\"defaultValue\":\"\",\"required\":false}]", - "resultType": "list", - "sourceType": "ai", - "trainingTaskId": null, - "tableMetadataIds": "", - "executionCount": 0, - "visualizationConfigs": [], - "inputJsonSchema": "{}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"array\"}}}", - "lastExecutionTime": null - }, - { - "id": "2000852304343240706", - "createBy": "bjt", - "createTime": "2025-12-16 16:55:38", - "updateBy": null, - "updateTime": null, - "serviceId": "2000821852159709186", - "uniqueName": "按科目查凭证", - "name": "ankemuchapingzheng_6a268ea1", - "description": "按照科目名称查询凭证信息", - "visualizable": 1, - "toolPrompt": "按照科目名称查询凭证信息", - "toolType": "sql", - "datasourceId": "158", - "sqlTemplate": "WITH query_params AS (\n SELECT \n {year}::INT AS target_year,\n '{company_names}' AS company_names_param,\n '{subject_name}' AS subject_keyword,\n '{months}' AS months_param\n),\ncompany_filters AS (\n SELECT \n TRIM(keyword) AS company_keyword\n FROM (\n SELECT unnest(string_to_array(nullif((SELECT company_names_param FROM query_params LIMIT 1), ''), ',')) AS keyword\n ) t\n WHERE keyword IS NOT NULL AND TRIM(keyword) <> ''\n),\nmonth_filters AS (\n SELECT \n month::INT AS target_month\n FROM (\n SELECT unnest(string_to_array(nullif((SELECT months_param FROM query_params LIMIT 1), ''), ',')) AS month\n ) t\n WHERE month IS NOT NULL AND TRIM(month) <> ''\n),\n目标科目 AS (\n SELECT DISTINCT a.id, a.ccode\n FROM uf_account_code a\n JOIN uf_company c ON a.company_id = c.id\n CROSS JOIN query_params qp\n WHERE a.ccode_name LIKE '%' || qp.subject_keyword || '%'\n AND a.iyear = qp.target_year\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n),\n相关科目 AS (\n SELECT a.id\n FROM uf_account_code a\n JOIN uf_company c ON a.company_id = c.id\n CROSS JOIN query_params qp\n WHERE a.iyear = qp.target_year\n AND EXISTS (\n SELECT 1 FROM 目标科目 t \n WHERE a.ccode LIKE t.ccode || '%'\n )\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n)\nSELECT \n c.company_name AS 公司,\n v.dbill_date AS 日期,\n v.csign || LPAD(v.ino_id::text, 4, '0') AS 凭证号,\n v.cdigest AS 摘要,\n a.ccode AS 科目编码,\n a.ccode_name AS 科目名称,\n a.igrade AS 级次,\n v.md AS 借方,\n v.mc AS 贷方\nFROM uf_voucher v\nJOIN uf_company c ON v.company_id = c.id\nJOIN uf_account_code a ON v.account_code_id = a.id\nCROSS JOIN query_params qp\nWHERE v.bdelete = false\n AND v.account_code_id IN (SELECT id FROM 相关科目)\n AND v.iyear = qp.target_year\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n AND (\n NOT EXISTS (SELECT 1 FROM month_filters)\n OR v.iperiod IN (SELECT target_month FROM month_filters)\n )\nORDER BY c.company_name, a.ccode, v.dbill_date, v.ino_id;", - "sqlParams": "[{\"type\":\"select\",\"name\":\"subject_name\",\"displayName\":\"科目名称\",\"maxLength\":100,\"defaultValue\":\"\",\"required\":true,\"options\":[\"管理费用\",\"销售费用\",\"财务费用\",\"应收账款\",\"应付账款\"]},{\"type\":\"paragraph\",\"name\":\"company_names\",\"displayName\":\"公司名称列表\",\"maxLength\":300,\"defaultValue\":\"\",\"required\":false},{\"type\":\"number\",\"name\":\"year\",\"displayName\":\"年度\",\"maxLength\":4,\"defaultValue\":\"2025\",\"required\":true},{\"type\":\"select\",\"name\":\"months\",\"displayName\":\"期间月份\",\"maxLength\":30,\"defaultValue\":\"\",\"required\":false,\"options\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"10\",\"11\",\"12\"]}]", - "resultType": "list", - "sourceType": "ai", - "trainingTaskId": null, - "tableMetadataIds": "", - "executionCount": 0, - "visualizationConfigs": [], - "inputJsonSchema": "{}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"array\"}}}", - "lastExecutionTime": null - }, - { - "id": "2000852610904920066", - "createBy": "bjt", - "createTime": "2025-12-16 16:56:51", - "updateBy": null, - "updateTime": null, - "serviceId": "2000821852159709186", - "uniqueName": "按科目查余额", - "name": "ankemuchayue_68b302c2", - "description": "按照科目名称查询余额", - "visualizable": 1, - "toolPrompt": "按照科目名称查询余额", - "toolType": "sql", - "datasourceId": "158", - "sqlTemplate": "WITH query_params AS (\n SELECT \n {year}::INT AS target_year,\n '{company_names}' AS company_names_param,\n '{subject_name}' AS subject_keyword,\n '{months}' AS months_param\n),\ncompany_filters AS (\n SELECT \n TRIM(keyword) AS company_keyword\n FROM (\n SELECT unnest(string_to_array(nullif((SELECT company_names_param FROM query_params LIMIT 1), ''), ',')) AS keyword\n ) t\n WHERE keyword IS NOT NULL AND TRIM(keyword) <> ''\n),\nmonth_filters AS (\n SELECT \n month::INT AS target_month\n FROM (\n SELECT unnest(string_to_array(nullif((SELECT months_param FROM query_params LIMIT 1), ''), ',')) AS month\n ) t\n WHERE month IS NOT NULL AND TRIM(month) <> ''\n),\n目标科目 AS (\n SELECT DISTINCT a.id, a.ccode\n FROM uf_account_code a\n JOIN uf_company c ON a.company_id = c.id\n CROSS JOIN query_params qp\n WHERE a.ccode_name LIKE '%' || qp.subject_keyword || '%'\n AND a.iyear = qp.target_year\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n),\n相关科目 AS (\n SELECT a.id\n FROM uf_account_code a\n JOIN uf_company c ON a.company_id = c.id\n CROSS JOIN query_params qp\n WHERE a.iyear = qp.target_year\n AND EXISTS (\n SELECT 1 FROM 目标科目 t \n WHERE a.ccode LIKE t.ccode || '%'\n )\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n)\nSELECT \n c.company_name AS 公司,\n a.ccode AS 科目编码,\n a.ccode_name AS 科目名称,\n a.igrade AS 级次,\n a.cclass AS 类别,\n b.iyear AS 年,\n b.iperiod AS 月,\n b.cbegind_c AS 期初方向,\n b.mb AS 期初余额,\n b.md AS 借方发生,\n b.mc AS 贷方发生,\n b.cendd_c AS 期末方向,\n b.me AS 期末余额\nFROM uf_account_balance b\nJOIN uf_company c ON b.company_id = c.id\nJOIN uf_account_code a ON b.account_code_id = a.id\nCROSS JOIN query_params qp\nWHERE b.iyear = qp.target_year\n AND b.account_code_id IN (SELECT id FROM 相关科目)\n AND (\n NOT EXISTS (SELECT 1 FROM company_filters)\n OR EXISTS (\n SELECT 1 FROM company_filters cf \n WHERE c.company_name LIKE '%' || cf.company_keyword || '%'\n )\n )\n AND (\n NOT EXISTS (SELECT 1 FROM month_filters)\n OR b.iperiod IN (SELECT target_month FROM month_filters)\n )\nORDER BY c.company_name, a.ccode, b.iperiod;", - "sqlParams": "[{\"type\":\"string\",\"name\":\"subject_name\",\"displayName\":\"科目名称关键字\",\"maxLength\":100,\"defaultValue\":\"银行存款\",\"required\":true},{\"type\":\"string\",\"name\":\"company_names\",\"displayName\":\"公司筛选\",\"maxLength\":150,\"defaultValue\":\"\",\"required\":false},{\"type\":\"number\",\"name\":\"year\",\"displayName\":\"查询年度\",\"maxLength\":4,\"defaultValue\":\"2025\",\"required\":true},{\"type\":\"number\",\"name\":\"months\",\"displayName\":\"查询月份\",\"maxLength\":2,\"defaultValue\":\"\",\"required\":false}]", - "resultType": "list", - "sourceType": "ai", - "trainingTaskId": null, - "tableMetadataIds": "", - "executionCount": 0, - "visualizationConfigs": [], - "inputJsonSchema": "{}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"array\"}}}", - "lastExecutionTime": null - } -] \ No newline at end of file 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 cb9382c..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp.log +++ /dev/null @@ -1,137 +0,0 @@ -2026-03-28 12:30:33 - 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 -2026-03-28 12:30:33 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'workflow_mcp_server' -2026-03-28 12:30:33 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest -2026-03-28 12:30:33 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:334] - ================================================== -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:335] - Workflow MCP Server 启动 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:336] - ================================================== -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:341] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:344] - 使用模式: api -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2037527155235225601 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2037527155235225601 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:115] - [客户端初始化] base_url=http://192.168.2.236:8088 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:116] - [客户端初始化] token=wf_bd39a583670c42ceab48b3353bf2ba43 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:117] - [客户端初始化] execute_timeout=600.0s -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:148] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2037527155235225601 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:149] - [API请求] Headers: {'X-API-Key': 'wf_bd39a583670c42ceab48b3353bf2ba43'} -2026-03-28 12:30:34 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2026-03-28 12:30:34 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - 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, 28 Mar 2026 04:30:30 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2026-03-28 12:30:34 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2037527155235225601 "HTTP/1.1 200 " -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:156] - [API响应] HTTP 200 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:157] - [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, 28 Mar 2026 04:30:30 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:162] - [API响应] 获取工作流配置成功: workflow_id=2037527155235225601 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:163] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2037535460665982977", - "createBy": "duchangyuan", - "createTime": "2026-03-27 22:21:24", - "updateBy": "admin", - "updateTime": "2026-03-28 12:21:49", - "serviceId": "2037535460632428546", - "uniqueName": "银行流水状态检测", - "name": "yinxingliushuizhuangtaijiance_7c4d9a63", - "description": "用于检测流水导入状态,检查是否全部导入完成", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"workflow_extraContext\":{\"description\":\"工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填\"}},\"required\":[]}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"additionalProperties\":false}", - "lastExecutionTime": null - } - ] -} -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2037535460665982977", - "createBy": "duchangyuan", - "createTime": "2026-03-27 22:21:24", - "updateBy": "admin", - "updateTime": "2026-03-28 12:21:49", - "serviceId": "2037535460632428546", - "uniqueName": "银行流水状态检测", - "name": "yinxingliushuizhuangtaijiance_7c4d9a63", - "description": "用于检测流水导入状态,检查是否全部导入完成", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"workflow_extraContext\":{\"description\":\"工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填\"}},\"required\":[]}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"additionalProperties\":false}", - "lastExecutionTime": null - } - ] -} -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2037535460665982977", - "createBy": "duchangyuan", - "createTime": "2026-03-27 22:21:24", - "updateBy": "admin", - "updateTime": "2026-03-28 12:21:49", - "serviceId": "2037535460632428546", - "uniqueName": "银行流水状态检测", - "name": "yinxingliushuizhuangtaijiance_7c4d9a63", - "description": "用于检测流水导入状态,检查是否全部导入完成", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"workflow_extraContext\":{\"description\":\"工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填\"}},\"required\":[]}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"additionalProperties\":false}", - "lastExecutionTime": null - } -] -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2037527155235225601, 配置数量: 1 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - INFO - [main.py:352] - 开始运行 MCP Server (stdio 模式) -2026-03-28 12:30:34 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2026-03-28 12:30:34 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2026-03-28 12:30:40 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: -2026-03-28 12:30:40 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-03-28 12:30:40 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:178] - 处理工具配置: name=yinxingliushuizhuangtaijiance_7c4d9a63, description=用于检测流水导入状态,检查是否全部导入完成... -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:185] - 从 sqlParams 转换的 inputSchema: {"type": "object", "properties": {}, "required": []} -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - INFO - [main.py:237] - ListTools 响应: 返回 1 个工具 -2026-03-28 12:30:40 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - 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 cb9382c..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/logs/lzwcai_workflow_to_mcp_daily.log +++ /dev/null @@ -1,137 +0,0 @@ -2026-03-28 12:30:33 - 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 -2026-03-28 12:30:33 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'workflow_mcp_server' -2026-03-28 12:30:33 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest -2026-03-28 12:30:33 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:334] - ================================================== -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:335] - Workflow MCP Server 启动 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:336] - ================================================== -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:341] - 命令行参数: {'mode': 'api', 'json_path': None, 'workflow_id': None} -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:344] - 使用模式: api -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:103] - ApiLoader 初始化,工作流ID: 2037527155235225601 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.main - INFO - [main.py:111] - 开始从 API 加载工作流配置,工作流ID: 2037527155235225601 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:115] - [客户端初始化] base_url=http://192.168.2.236:8088 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:116] - [客户端初始化] token=wf_bd39a583670c42ceab48b3353bf2ba43 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:117] - [客户端初始化] execute_timeout=600.0s -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:148] - [API请求] GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2037527155235225601 -2026-03-28 12:30:33 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:149] - [API请求] Headers: {'X-API-Key': 'wf_bd39a583670c42ceab48b3353bf2ba43'} -2026-03-28 12:30:34 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='192.168.2.236' port=8088 local_address=None timeout=30.0 socket_options=None -2026-03-28 12:30:34 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - 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, 28 Mar 2026 04:30:30 GMT'), (b'Keep-Alive', b'timeout=60'), (b'Connection', b'keep-alive')]) -2026-03-28 12:30:34 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/system/workflowManage/getByWorkflowId/2037527155235225601 "HTTP/1.1 200 " -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request= -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started -2026-03-28 12:30:34 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:156] - [API响应] HTTP 200 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:157] - [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, 28 Mar 2026 04:30:30 GMT', 'keep-alive': 'timeout=60', 'connection': 'keep-alive'} -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - INFO - [api_client.py:162] - [API响应] 获取工作流配置成功: workflow_id=2037527155235225601 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.utils.api_client - DEBUG - [api_client.py:163] - [API响应] Body: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2037535460665982977", - "createBy": "duchangyuan", - "createTime": "2026-03-27 22:21:24", - "updateBy": "admin", - "updateTime": "2026-03-28 12:21:49", - "serviceId": "2037535460632428546", - "uniqueName": "银行流水状态检测", - "name": "yinxingliushuizhuangtaijiance_7c4d9a63", - "description": "用于检测流水导入状态,检查是否全部导入完成", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"workflow_extraContext\":{\"description\":\"工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填\"}},\"required\":[]}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"additionalProperties\":false}", - "lastExecutionTime": null - } - ] -} -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:116] - API 响应原始数据: { - "msg": "查询成功", - "code": 200, - "data": [ - { - "id": "2037535460665982977", - "createBy": "duchangyuan", - "createTime": "2026-03-27 22:21:24", - "updateBy": "admin", - "updateTime": "2026-03-28 12:21:49", - "serviceId": "2037535460632428546", - "uniqueName": "银行流水状态检测", - "name": "yinxingliushuizhuangtaijiance_7c4d9a63", - "description": "用于检测流水导入状态,检查是否全部导入完成", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"workflow_extraContext\":{\"description\":\"工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填\"}},\"required\":[]}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"additionalProperties\":false}", - "lastExecutionTime": null - } - ] -} -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:124] - API 响应 data 字段: [ - { - "id": "2037535460665982977", - "createBy": "duchangyuan", - "createTime": "2026-03-27 22:21:24", - "updateBy": "admin", - "updateTime": "2026-03-28 12:21:49", - "serviceId": "2037535460632428546", - "uniqueName": "银行流水状态检测", - "name": "yinxingliushuizhuangtaijiance_7c4d9a63", - "description": "用于检测流水导入状态,检查是否全部导入完成", - "visualizable": 0, - "toolPrompt": null, - "toolType": "api", - "datasourceId": null, - "sqlTemplate": null, - "sqlParams": "[]", - "resultType": null, - "sourceType": null, - "trainingTaskId": null, - "tableMetadataIds": null, - "executionCount": 0, - "visualizationConfigs": null, - "inputJsonSchema": "{\"type\":\"object\",\"properties\":{\"workflow_extraContext\":{\"description\":\"工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填\"}},\"required\":[]}", - "outputJsonSchema": "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"additionalProperties\":false}", - "lastExecutionTime": null - } -] -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - INFO - [main.py:135] - 从 API 加载工作流配置成功,工作流ID: 2037527155235225601, 配置数量: 1 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - INFO - [main.py:165] - 已加载 1 个工具配置 -2026-03-28 12:30:34 - lzwcai_workflow_to_mcp.main - INFO - [main.py:352] - 开始运行 MCP Server (stdio 模式) -2026-03-28 12:30:34 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor -2026-03-28 12:30:34 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0') -2026-03-28 12:30:40 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: -2026-03-28 12:30:40 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest -2026-03-28 12:30:40 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - INFO - [main.py:171] - 收到 ListTools 请求,当前配置数量: 1 -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:178] - 处理工具配置: name=yinxingliushuizhuangtaijiance_7c4d9a63, description=用于检测流水导入状态,检查是否全部导入完成... -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - DEBUG - [main.py:185] - 从 sqlParams 转换的 inputSchema: {"type": "object", "properties": {}, "required": []} -2026-03-28 12:30:40 - lzwcai_workflow_to_mcp.main - INFO - [main.py:237] - ListTools 响应: 返回 1 个工具 -2026-03-28 12:30:40 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - 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 e69de29..0000000 diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py deleted file mode 100644 index 9f6a2e9..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/main.py +++ /dev/null @@ -1,358 +0,0 @@ -#!/usr/bin/env python3 -""" -Workflow to MCP Server -从 businessQueries.json 或 API 动态生成 MCP 工具 - -支持两种模式: -- local: 从本地 JSON 文件加载 -- api: 从远程 API 加载 -""" -import json -import os -import logging -import argparse -import anyio - -import mcp.types as types -from mcp.server import NotificationOptions, Server -from mcp.server.models import InitializationOptions -from mcp.server.stdio import stdio_server - -try: - from .schema_converter import convert_sql_params_to_input_schema, sanitize_json_schema - from .utils.api_client import execute_workflow, get_workflow_by_id, extract_final_output - from .utils.env_config import get_workflow_id - from .utils.logger_config import setup_system_logging, get_logger -except ImportError: - from schema_converter import convert_sql_params_to_input_schema, sanitize_json_schema - from utils.api_client import execute_workflow, get_workflow_by_id, extract_final_output - from utils.env_config import get_workflow_id - from utils.logger_config import setup_system_logging, get_logger - -# 初始化日志系统 -setup_system_logging(app_name="lzwcai_workflow_to_mcp", log_level=logging.DEBUG) -logger = get_logger(__name__) - -# 初始化 MCP Server -server = Server("workflow_mcp_server") - -# 全局配置 -_tools_config: list[dict] = [] -_config: dict = {} - - -def parse_arguments(): - """解析命令行参数""" - parser = argparse.ArgumentParser(description="Workflow MCP Server") - parser.add_argument( - "--mode", - type=str, - choices=["local", "api"], - default="api", - help="数据加载模式: local(本地JSON) 或 api(远程API,默认)" - ) - parser.add_argument( - "--json-path", - type=str, - default=None, - help="本地 JSON 文件路径 (local 模式)" - ) - parser.add_argument( - "--workflow-id", - type=str, - default=None, - help="工作流ID (api 模式,可选,默认从环境变量 workflowId 获取)" - ) - return parser.parse_args() - - -class DataLoader: - """数据加载器基类""" - - def load(self) -> list[dict]: - raise NotImplementedError - - -class LocalLoader(DataLoader): - """本地 JSON 文件加载器""" - - def __init__(self, json_path: str = None): - if json_path is None: - json_path = os.path.join(os.path.dirname(__file__), "businessQueries.json") - self.json_path = json_path - - def load(self) -> list[dict]: - try: - with open(self.json_path, "r", encoding="utf-8") as f: - data = json.load(f) - logger.info(f"从本地加载 {len(data)} 条配置: {self.json_path}") - return data - except FileNotFoundError: - logger.error(f"配置文件不存在: {self.json_path}") - return [] - except json.JSONDecodeError as e: - logger.error(f"JSON 解析错误: {e}") - return [] - - -class ApiLoader(DataLoader): - """API 远程加载器 - 使用 get_workflow_by_id 获取工作流配置""" - - def __init__(self, workflow_id: str = None): - self.workflow_id = workflow_id or get_workflow_id() - logger.debug(f"ApiLoader 初始化,工作流ID: {self.workflow_id}") - - def load(self) -> list[dict]: - try: - if not self.workflow_id: - logger.error("未提供工作流ID,请设置环境变量 workflowId") - return [] - - logger.info(f"开始从 API 加载工作流配置,工作流ID: {self.workflow_id}") - - # 调用 get_workflow_by_id 获取工作流配置 - response = get_workflow_by_id(self.workflow_id) - - logger.debug(f"API 响应原始数据: {json.dumps(response, ensure_ascii=False, indent=2)}") - - # 返回格式: {code, data, msg} - if response.get("code") != 200: - logger.error(f"获取工作流配置失败: code={response.get('code')}, msg={response.get('msg')}") - return [] - - data = response.get("data") - logger.debug(f"API 响应 data 字段: {json.dumps(data, ensure_ascii=False, indent=2) if data else 'None'}") - - # data 可能是单个对象或列表 - if isinstance(data, dict): - result = [data] - elif isinstance(data, list): - result = data - else: - logger.warning(f"API 响应 data 字段类型异常: {type(data)}") - result = [] - - logger.info(f"从 API 加载工作流配置成功,工作流ID: {self.workflow_id}, 配置数量: {len(result)}") - return result - - except Exception as e: - logger.error(f"API 请求失败: {e}", exc_info=True) - return [] - - -def create_loader(mode: str, **kwargs) -> DataLoader: - """ - 创建数据加载器 - - Args: - mode: "local" 或 "api" - kwargs: - - json_path: 本地 JSON 路径 (local 模式) - - workflow_id: 工作流ID (api 模式,可选,默认从环境变量获取) - """ - if mode == "local": - return LocalLoader(json_path=kwargs.get("json_path")) - elif mode == "api": - return ApiLoader(workflow_id=kwargs.get("workflow_id")) - else: - raise ValueError(f"不支持的模式: {mode}") - - -def init_tools(loader: DataLoader): - """初始化工具配置""" - global _tools_config - _tools_config = loader.load() - logger.info(f"已加载 {len(_tools_config)} 个工具配置") - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """列出所有可用工具""" - logger.info(f"收到 ListTools 请求,当前配置数量: {len(_tools_config)}") - tools = [] - - for query in _tools_config: - name = query.get("name", "") - description = query.get("description") or query.get("toolPrompt") or query.get("uniqueName", "") - - logger.debug(f"处理工具配置: name={name}, description={description[:50] if description else 'None'}...") - - # 优先从 sqlParams 转换,因为它包含更详细的文件配置信息 - sql_params = query.get("sqlParams") - if sql_params: - try: - input_schema = convert_sql_params_to_input_schema(sql_params) - logger.debug(f"从 sqlParams 转换的 inputSchema: {json.dumps(input_schema, ensure_ascii=False)}") - except Exception as e: - logger.warning(f"从 sqlParams 转换失败: {e},尝试使用 inputJsonSchema") - # 回退到 inputJsonSchema - input_json_schema = query.get("inputJsonSchema") - if input_json_schema: - try: - if isinstance(input_json_schema, str): - input_schema = json.loads(input_json_schema) - else: - input_schema = input_json_schema - input_schema = sanitize_json_schema(input_schema) - logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}") - except (json.JSONDecodeError, TypeError) as e2: - logger.error(f"解析 inputJsonSchema 也失败: {e2},使用空 schema") - input_schema = {"type": "object", "properties": {}, "required": []} - else: - logger.warning("sqlParams 和 inputJsonSchema 都不可用,使用空 schema") - input_schema = {"type": "object", "properties": {}, "required": []} - else: - # 如果没有 sqlParams,尝试使用 inputJsonSchema - input_json_schema = query.get("inputJsonSchema") - if input_json_schema: - try: - if isinstance(input_json_schema, str): - input_schema = json.loads(input_json_schema) - else: - input_schema = input_json_schema - input_schema = sanitize_json_schema(input_schema) - logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}") - except (json.JSONDecodeError, TypeError) as e: - logger.error(f"解析 inputJsonSchema 失败: {e},使用空 schema") - input_schema = {"type": "object", "properties": {}, "required": []} - else: - logger.warning("sqlParams 和 inputJsonSchema 都不存在,使用空 schema") - input_schema = {"type": "object", "properties": {}, "required": []} - - # 添加 workflow_extraContext 字段到 schema,可以接收任何类型(非必填) - if "properties" not in input_schema: - input_schema["properties"] = {} - input_schema["properties"]["workflow_extraContext"] = { - "description": "工作流额外的上下文参数(如环境变量等),可以是任何类型,非必填" - } - - tools.append( - types.Tool( - name=name, - description=description, - inputSchema=input_schema - ) - ) - - logger.info(f"ListTools 响应: 返回 {len(tools)} 个工具") - return tools - - -@server.call_tool() -async def handle_call_tool( - name: str, - arguments: dict | None, -) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """调用工具""" - logger.info(f"收到 CallTool 请求: name={name}, arguments={json.dumps(arguments, ensure_ascii=False) if arguments else 'None'}") - - # 查找对应的工具配置 - tool_config = None - for query in _tools_config: - if query.get("name") == name: - tool_config = query - break - - if tool_config is None: - logger.error(f"未找到工具配置: {name}") - raise ValueError(f"未知工具: {name}") - - logger.debug(f"找到工具配置: {json.dumps(tool_config, ensure_ascii=False, indent=2)}") - - # 获取工作流ID - workflow_id = tool_config.get("workflowId") or get_workflow_id() - logger.info(f"使用工作流ID: {workflow_id}") - - # 提取 workflow_extraContext 字段并合并到 inputs - inputs = arguments or {} - workflow_extra_context = inputs.pop("workflow_extraContext", None) - - # 如果 workflow_extraContext 存在,将其内容合并到 inputs - if workflow_extra_context is not None: - if isinstance(workflow_extra_context, dict): - # 如果 workflow_extraContext 是字典,合并到 inputs - inputs.update(workflow_extra_context) - logger.info(f"workflow_extraContext 是字典类型,已合并到 inputs: {json.dumps(workflow_extra_context, ensure_ascii=False)}") - else: - # 如果 workflow_extraContext 不是字典,作为 workflow_extraContext 字段保留 - inputs["workflow_extraContext"] = workflow_extra_context - logger.info(f"workflow_extraContext 是 {type(workflow_extra_context).__name__} 类型,保留为 workflow_extraContext 字段") - - # 构建请求数据 - request_data = { - "workflowId": workflow_id, - "inputs": inputs - } - - logger.info(f"执行工作流请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - - try: - # 调用工作流执行API - result = execute_workflow(request_data) - logger.info(f"工作流执行成功: {workflow_id}") - logger.debug(f"工作流执行结果: {json.dumps(result, ensure_ascii=False, indent=2)}") - - # 提取 final_output 最后一个节点的值 - result = extract_final_output(result) - except Exception as e: - logger.error(f"工作流执行失败: {e}", exc_info=True) - result = { - "error": str(e), - "tool_name": name, - "workflow_id": workflow_id - } - - return [ - types.TextContent( - type="text", - text=json.dumps(result, ensure_ascii=False, indent=2) - ) - ] - - -async def run_server(): - """运行 MCP Server (stdio 模式)""" - async with stdio_server() as streams: - await server.run( - streams[0], - streams[1], - InitializationOptions( - server_name="workflow_mcp_server", - server_version="0.1.0", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - -def main(): - """主入口""" - global _config - - logger.info("=" * 50) - logger.info("Workflow MCP Server 启动") - logger.info("=" * 50) - - # 解析命令行参数 - args = parse_arguments() - _config = vars(args) - logger.info(f"命令行参数: {_config}") - - # 创建加载器并初始化工具 - logger.info(f"使用模式: {args.mode}") - loader = create_loader( - mode=args.mode, - json_path=args.json_path, - workflow_id=args.workflow_id - ) - init_tools(loader) - - logger.info("开始运行 MCP Server (stdio 模式)") - # 运行服务器 - anyio.run(run_server) - - -if __name__ == "__main__": - main() diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/pyproject.toml b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/pyproject.toml deleted file mode 100644 index 1a485da..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/pyproject.toml +++ /dev/null @@ -1,18 +0,0 @@ -[project] -name = "lzwcai-workflow-to-mcp" -version = "0.1.0" -description = "从 businessQueries 动态生成 MCP 工具" -readme = "README.md" -requires-python = ">=3.10" -dependencies = [ - "mcp>=1.0.0", - "anyio>=4.0.0", - "requests>=2.28.0", -] - -[project.scripts] -workflow-mcp = "main:main" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py deleted file mode 100644 index 640a4a5..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/schema_converter.py +++ /dev/null @@ -1,304 +0,0 @@ -""" -Schema 转换器 -将 sqlParams 数组格式转换为 MCP 工具需要的 JSON Schema 格式 - -支持的类型: -- string: 文本输入 -- paragraph: 段落/多行文本 -- select: 下拉选项 -- number: 数字输入 -- file: 单文件上传 -- fileList: 多文件上传 -""" -import json -from typing import Any - - -def sanitize_json_schema(schema: dict) -> dict: - """ - 清理 JSON Schema,确保 enum 字段只包含字符串值 - - Args: - schema: 原始 JSON Schema - - Returns: - dict: 清理后的 JSON Schema - """ - if not isinstance(schema, dict): - return schema - - # 递归处理 properties - if "properties" in schema and isinstance(schema["properties"], dict): - for prop_name, prop_schema in schema["properties"].items(): - if isinstance(prop_schema, dict) and "enum" in prop_schema: - enum_values = prop_schema["enum"] - if isinstance(enum_values, list) and len(enum_values) > 0: - # 检查是否是对象数组 - if isinstance(enum_values[0], dict): - # 提取 value 字段 - cleaned_enum = [ - item.get("value", item.get("label", "")) - for item in enum_values - if isinstance(item, dict) - ] - # 过滤空值 - prop_schema["enum"] = [v for v in cleaned_enum if v] - # 保留原始数据 - prop_schema["x-options"] = enum_values - - return schema - - -def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]: - """ - 将单个参数转换为 JSON Schema property - - Args: - param: 参数配置,格式如: - { - "type": "string", - "name": "company_names", - "displayName": "公司名称", - "maxLength": 200, - "defaultValue": "", - "required": true, - "options": ["选项1", "选项2"], # 仅 select 类型 - "fileConfig": { # 仅 file/fileList 类型 - "uploadMode": "both", - "typeCategories": ["image"], - "customAccept": "", - "accept": ".jpg,.jpeg,.png" - } - } - - Returns: - tuple: (property_name, property_schema, is_required) - """ - param_type = param.get("type", "string") - param_name = param.get("name", "") - display_name = param.get("displayName", param_name) - default_value = param.get("defaultValue", "") - max_length = param.get("maxLength") - is_required = param.get("required", False) - options = param.get("options", []) - file_config = param.get("fileConfig", {}) - - property_schema = { - "description": display_name - } - - if param_type == "string": - property_schema["type"] = "string" - if max_length: - property_schema["maxLength"] = max_length - - elif param_type == "paragraph": - property_schema["type"] = "string" - property_schema["format"] = "paragraph" - if max_length: - property_schema["maxLength"] = max_length - - elif param_type == "select": - property_schema["type"] = "string" - if options: - # 处理 options 可能是对象数组 [{label, value}] 或字符串数组的情况 - if isinstance(options, list) and len(options) > 0: - if isinstance(options[0], dict): - # 对象数组,提取 value 字段作为 enum,过滤空值 - enum_values = [ - opt.get("value", opt.get("label", "")) - for opt in options - if isinstance(opt, dict) - ] - # 过滤掉空字符串 - property_schema["enum"] = [v for v in enum_values if v] - # 保留原始 options 供前端使用(如果前端支持) - property_schema["x-options"] = options - else: - # 字符串数组,直接使用,过滤空值 - property_schema["enum"] = [v for v in options if v] - - elif param_type == "number": - property_schema["type"] = "number" - - elif param_type == "file": - # 单文件上传 - property_schema["type"] = "string" - property_schema["format"] = "url" - property_schema["x-file-type"] = "single" - - # 添加文件配置信息 - if file_config: - if file_config.get("accept"): - property_schema["x-accept"] = file_config["accept"] - if file_config.get("typeCategories"): - property_schema["x-type-categories"] = file_config["typeCategories"] - if file_config.get("uploadMode"): - property_schema["x-upload-mode"] = file_config["uploadMode"] - # 保留完整配置 - property_schema["x-file-config"] = file_config - - elif param_type == "fileList": - # 多文件上传 - property_schema["type"] = "array" - property_schema["items"] = { - "type": "string", - "format": "url" - } - property_schema["x-file-type"] = "multiple" - - # 添加文件配置信息 - if file_config: - if file_config.get("accept"): - property_schema["x-accept"] = file_config["accept"] - if file_config.get("typeCategories"): - property_schema["x-type-categories"] = file_config["typeCategories"] - if file_config.get("uploadMode"): - property_schema["x-upload-mode"] = file_config["uploadMode"] - # 保留完整配置 - property_schema["x-file-config"] = file_config - - else: - # 默认当作 string 处理 - property_schema["type"] = "string" - - # 添加默认值(文件类型不需要默认值) - if default_value not in (None, "") and param_type not in ("file", "fileList"): - if param_type == "number": - try: - property_schema["default"] = int(default_value) if str(default_value).isdigit() else float(default_value) - except (ValueError, TypeError): - property_schema["default"] = default_value - else: - property_schema["default"] = default_value - - return param_name, property_schema, is_required - - -def convert_sql_params_to_input_schema(sql_params: str | list) -> dict: - """ - 将 sqlParams 转换为 MCP 工具的 inputSchema - - Args: - sql_params: sqlParams 字段值,可以是 JSON 字符串或已解析的列表 - 格式: [{"type": "string", "name": "xxx", ...}, ...] - - Returns: - dict: MCP 工具的 inputSchema,格式如: - { - "type": "object", - "properties": {...}, - "required": [...] - } - """ - # 解析 JSON 字符串 - if isinstance(sql_params, str): - try: - params_list = json.loads(sql_params) - except json.JSONDecodeError: - return {"type": "object", "properties": {}, "required": []} - else: - params_list = sql_params - - if not isinstance(params_list, list): - return {"type": "object", "properties": {}, "required": []} - - input_schema = { - "type": "object", - "properties": {}, - "required": [] - } - - for param in params_list: - if not isinstance(param, dict): - continue - - name, schema, is_required = convert_param_to_schema_property(param) - - if name: - input_schema["properties"][name] = schema - if is_required: - input_schema["required"].append(name) - - return input_schema - - -def convert_business_query_to_tool(query: dict) -> dict: - """ - 将单个 businessQuery 转换为 MCP Tool 配置 - - Args: - query: businessQuery 对象 - - Returns: - dict: MCP Tool 配置 - { - "name": "工具名称", - "description": "工具描述", - "inputSchema": {...} - } - """ - name = query.get("name", "") - description = query.get("description") or query.get("toolPrompt") or query.get("uniqueName", "") - sql_params = query.get("sqlParams", "[]") - - input_schema = convert_sql_params_to_input_schema(sql_params) - - return { - "name": name, - "description": description, - "inputSchema": input_schema, - # 保留原始数据供后续使用 - "_raw": { - "id": query.get("id"), - "uniqueName": query.get("uniqueName"), - "sqlTemplate": query.get("sqlTemplate"), - "datasourceId": query.get("datasourceId"), - "toolType": query.get("toolType"), - } - } - - -def convert_all_queries_to_tools(queries: list[dict]) -> list[dict]: - """ - 将所有 businessQueries 转换为 MCP Tools 列表 - - Args: - queries: businessQueries 列表 - - Returns: - list: MCP Tools 配置列表 - """ - tools = [] - for query in queries: - tool = convert_business_query_to_tool(query) - if tool["name"]: - tools.append(tool) - return tools - - -# 测试用 -if __name__ == "__main__": - # 测试单个参数转换 - test_param = { - "type": "select", - "name": "subject_name", - "displayName": "科目名称", - "maxLength": 100, - "defaultValue": "", - "required": True, - "options": ["管理费用", "销售费用", "财务费用"] - } - - name, schema, required = convert_param_to_schema_property(test_param) - print(f"参数名: {name}") - print(f"Schema: {json.dumps(schema, ensure_ascii=False, indent=2)}") - print(f"必填: {required}") - print() - - # 测试完整 sqlParams 转换 - test_sql_params = '[{"type":"paragraph","name":"digest","displayName":"摘要关键字","maxLength":500,"defaultValue":"","required":true},{"type":"string","name":"company_names","displayName":"公司名称","maxLength":200,"defaultValue":"","required":false},{"type":"number","name":"year","displayName":"会计年度","maxLength":4,"defaultValue":"2025","required":true}]' - - input_schema = convert_sql_params_to_input_schema(test_sql_params) - print("InputSchema:") - print(json.dumps(input_schema, ensure_ascii=False, indent=2)) 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 deleted file mode 100644 index 892d416..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Utils package for lzwcai_workflow_to_mcp""" - -from .json_helper import load_json -from .name_helper import generate_tool_name -from .schema_helper import generate_input_schema, validate_input_schema -from .api_client import ( - WorkflowAPIClient, - get_workflow_by_id, - execute_workflow, - process_workflow_response, - get_default_client, - DEFAULT_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 - -__all__ = [ - 'load_json', - 'generate_tool_name', - 'generate_input_schema', - 'validate_input_schema', - 'WorkflowAPIClient', - 'get_workflow_by_id', - 'execute_workflow', - 'process_workflow_response', - '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', - 'set_env_variable' -] 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 deleted file mode 100644 index 074d8be96d1f0dbe6d39346739ff7da40c2923e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1073 zcmb7DO>fgM7ZBbzF^cB)!7S(QwhxLr0oiJY`sCn3SF z;LeRdz@OmA2}_6r;=m2D!=#X)s;=B0p%6Sq8;G0aM zPn`!>uSM_{*Z=}su~ni_6{?DiV)3QrTyw9#qcxgseIv5T1*V$tLg z_Q-CFCUJB@`qSC6pwrNhk|w>RUXCknLFraZO$?I>E%pY`P@ZT&u tP{7Zaq9~ui&UbL{3wZDq>|d*zqF#dqYy#z_@(G|{@ik3-lVo?qpx^H5J2C(O 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 deleted file mode 100644 index d25d8eb6784a5a60c58c3ae99564f2b9e0b9dfa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22890 zcmc(HdsGxxx^Gp#U(i4|(7ang5rK$7jX{Yb55*uF#i;n`5UN!OuT*!dkS2#X$xNFu z5t2D0CT7GLbAm@F;ysy>Nz4g3NhZvlx$9J?dlr4_aMyU&5!AcxTGNTnnk?s!d%tg2 zS9Q}gXmamex0Kzx_Wt&>c76NzJ$C&*E>1_m_2rSy7UL$0`cJ$Oueea;?me2KPE$K6 zhH9i3nvu29jWqd{HOk1Zyirbm6^#n`mA5I|RgJ24b)&jn)2M0JHfq~-jXIjrQjCI8 zw&~jqjfVEP#yA@9Rc-O@#zrGe$tb(go_Gn`!mBZfQ8$_xO{1C7HYPK=#uP^1n93OJ zI=jW5W=}gPPXnrhGA8b2MPoV>Z&%qf?COIuOa&?p&}HC1fsW8B7$eYT%BYPLlkhUd zBwmtXOVCypBWucrGFeDx&ta0*!Y|a41@)VtjFrh|%=Qc>8E7&{3n@~}5ZUrU4U_t^ zvT*?;V`O&oK{;cI%#B$-&2E8~B6@#N$D~6Y3z>|_)F>JSlL`IGokgRKEE5`a7EtDO z%W^4uLLS8?!mQI#Pk|0Ug+0{CFOE{jZOWpwYNup zaO)?-Q}6$L+H>vJr7JqPx%uJip;VFX*8Asg_5X0{jg!+S-n)JNpW_+AG0^RoOauD_U;0j*I`~;=b=M(_7FBlQBSNFB4fux z5h}Ru&N0ZiBZq)jv3946?Px|)@zOv9Gw&{h1#%jetuwSJDasjIG8EOS5Xr;8t?F=G zj4`xRE5@DVWy3phnTtzMyzj-NmkxLzL zNnFw~C?nbe$`kWYMzjr-heXN(rHhBwbym1;zkY4%)K71J{Qm7LKfm+JYqvgr|IgPw zQ?I;q`~6XPQ*OO}W$NcYm^w9h=SQb*|HBV&z5UZaUq7KE1*!A(8od4f1?%!z<+d~(!=ja)t`29JwWTA1 z_R*49)YUB8$;KcrCB%I8{2k)WVt!=9d~!r;xB(W|bb*dnl0opYP6w~HcN}S9J3Dwe zyuG~K;beJBRdr3p&bkKMwykw_we=fqwe=0v+n=te<8@CrwYlu1K3)z(<5NV^Efu>7 zxvg^R&iV#EH8Q<=`}VEdg^U6@uYq!8MtN;}(=l5Y%$0-JVbqQj;c(#F4^Q*{vFfsY z-G_^tTK09Zovrp}=f1X{qs>h%#g3ltLt?zWxvSU#)!EtNF5p#7hwP!8Fq3k2+JI`V zWS6t0&9SektHsvb)?)8)KGN0An{3F*uxGFUZ1CI&+$JX=%a#3a%It^ z$#OR7T>6>xbJ=IIxh0$akn68M=-YhAmv-1^YVl}-8o9D?(wur$c20LjcP{=+JeOa7 z-Qlm?<6GD0v+VVm_j$BPR6LnHqo#GAoBY-Ld=>kBX$O4CHjgf-qcZb`n%-J_;+Vg< zx^Gh;Yw>X6Tl)vJ{*s!$+CWahaK&3M4y5}_H}`G6p*QvK8Pak3rJQ2vxAzuNhLmq8 z8hcy#swOCh*x&ByLW$73rLSr+u`rwny(xNChl$1a9V=kC$5&M*QNLMLnNNLMvZ`_| z^;wd!GEe?lE{$QnvGQ^GXRB!p*BYzh<)6pV5b_D)?!ej|U7Z~cd$YvJp-LtE5k7wy zQcp*4d3OY#7kQ?G?xx*TE0&467}=#zt-W-3r*PBZy<$ckbj&*BQXMjPYzl=)ag27r zCO!w1Ql#pY&q1LQDdw$5EmHL=+%h)LEoT?H6|B`M?Qm{s)DdaYC?e9Nej}&urVBK# z=V1bM$SrSv^m-&hn0oWIsSkg0bM%+fr}}}5T9-JSO-`4i$SOQVzzMb%rbyzBa8p34 znR?~=)ce1&Iy#!3v)h^hCRmqQo15D0ZIuxJ&%Wb?sGe6aP0l7>B~;2AA}i(f(p@dX z>qW)|E$}+=rj{vC@VXX9O9#}@(QJqM0LHK>Fc>x!f&wMG5aW3mEW%(h21_7t;Htp^ zTUjf__#{9rPPVDpX=`$HcQivY4z>s~*1@0SEeQH3@^)}3i!Uqu`r?4m6wqf(=ySaK zoFSD@pWjzCsZZ`_&Q|&L3n24GT;i$SCwBKA_QmD&)dWnbr&kZG9<2A73coVd3_GtJ zyLfCg&bO?luP%_3a(cp%2td0A$Vp`42z@AZ7EL_RBr!%PJ-Y@Khzbf2MCQs(gj_R|^fCoP ziV4CL#tdOK&;U0PG~V7PAg$y%phm z)b*X}P(mFNoD5g=HpQskkr*6}tW52C116iW7ks&=Jnu}s_v5LPKZWNlJy4wT zSEf(@8vfro{Q)#}>%BLiPk+9CQW!*O2_($uVbp3avq~?9uqiyKKCBShSvlqT;hi_m zMUKCorjrI@D-drQ={Hip36l+rd~xqPGu?&DPS<;!Cc*-ZuMLzQ*p6VMG>p9~Yz#s!ko z;YSz9dBmgeYci$ZWu=g8xTm7D##8zedM;(fNW-=LSND%><;+!nP4%QEUPxa)l6P(C z)ukhuocRgAW}TG2X0&Qz?Kbb)ZQPotxTm&r<{f|3G_b`uFYyq}KxW8l2s3j-YBSeT%}3ES5ALo1I*yFNM$=uqLU>*e&b;C4>w2qv;>LG4<-u?TGxP>KR_YpC>D4G0(g8X z%8+ns?TNLVK4&t+dT#BRwIhm&k}7XWl|Q4}Q+2WqAiyd22{)(7AoDq6DpA}bQ)z|B zY0&+|9_rgv3r&WL%7>5?OWx|K3m^1ocNYU=Dh9q#Knu&c0;nR#V<6(nVS5u}XB|D+ zvqu$~{~{JfEI`HoTEI}8fWLllDED$<1ODYh!krXrcVE378Spc7= z-867~d9MuA8&dh0uv_MqMePk?dTHN+mO~fWqeQ?XCc>|{kIt+MW?EACgo#^4y=pj# z!X34Bo_a4UL=mD_{(uoF-SSp^pQ5grJ&UL#)+klZM6o#3CaFEdw6OYWX01Sr_$KNS z=LSR;c`E|ts7uW6RYdek!V_U)Mope-Bo>(eS!tY7Yq3D3Tft}<-DUly5Uzv@_i1LNP!53vG7w zs+f4Usx>d1D#l_zdB#Y3sQ4Z|49{?^5hcvg3lw~!u7?>plh~_zQFSzO29O0rT`&WW z!we+hm>83@P|S8qf-SW5ea0H|X-IvtitRxAtD$}K94*Tpc9xms%PHbp4Y+pR)+JSA zs_R?mhXkXhhki5t+WQi2s;v?@x0G=6lk?LrU6IJ8$ZQBILO_s3BY;?Kl~e>#rF65M zD9xi#EMiw8!B7AF2=q!Ko>{V~#bJ*sSbeP7PRsxRS3`Tw)!QGui(vBAx2Jyep;S{( z{xV0m!)b3{CU$pAQwQkVmVvHpL$|o;)H2Kdf>Od>=#Ws~e(T2S1}hQ1A`>?W+f|tr zxAD;4^%vxPNF)qpzp&x|1^L>HRI#XQWmHbo6A+ZZ)1LmRSI?D2sxHd^f_ycy&JCSR zcbT>34!(02WuTzwaJX1Is0f-{T6k4U2Lq}>7j{n?mw=GVtbajq1*^LAlQXxkT-k4R zp;kg7o4SD0XGTsF3YovbaSzEQL~Kw+$8Th>Ag8;RKxwZ`f{s-eq?`&|g>=rU#UP~IV zwL1>+wB3O_DXw+6LTVmV;fgEO_G~x2TRnN=DupQ%AmSslsa}Sf9EGqB03fAPpt8vt z$m%z8IW=Q1jJ0#|4IX(=k+034G$fsR^2C!|`qJUj;YFOK#BW%RX>}*+xQvn!^ThHE z-sKy7%d5DOYA&tDZ`gPvHRJRP123HZyMez8B$xu0%n3_@*HYlK6b6-)CI2oZx5Rrk zf$k~JB-C6uylJ9ny|-vRS6IPWHuw#d;RZf1j~t#@RpVV%<6FLwE7`=Q)%p#aZy3xI zhDBb(BEKO&V9GdMK2ZMVI#1P2v9DXXZF|RiIm>>(;eb?E@nz;p>&4bl^LRDaxYz&4 zKCXB_mv+E!u-!1FdY=6DrqMiMT;w$_`i7Eg^8@LH!cSZK#UDwZqz>7r3-i zzhT8>QpRAdFKIDnSWL?0d5wABPzo$pGLm>L{c8He@{L~7<#G9V$T3So!rVAE^VXVuqmRIzmaAZc#R7pyEme{UOM{xN2^AcaF)v0^@lqORaioW zSA4KzH1VIF9@)fM%4ZiFWjuBG6e5ZS)i5Xb9VlVGS(R0_oceruMO^J;@-LpKDBM^e z|FR$jZoXW%VpFMnywm{k@pYxOkIH}ds1oA5ffZJ~4Yi}O(e}?lD>EP3en*B#8xU*= zNkK=XOG0;`40OvGIkBuOAg08aQy~(@G@Ytuq4W9a)zF6rYk%giL*vv-eTtz4f}U$H z>%@IK3f0VCv;F}!(||_}m*ZmN(Fe>_Jd8Xh>Sv6RIxC=(^k?EI!IT|C8wU~z7!YFD zdO6`zXrzGI=O1&TThVF`wo^Kkz7!lgpS3uIi(RrYgU*>jEU z&h@H$HN9H5nn^iAgJ~VoBYG4|zDsqh0XtjV8VJ+eT6h`~c7}POS|uzRo)@>qt-YKs zu6rUIEfjNONg^THC?g~r=^}cRFP01cGMR2&)S4&|bKN>|ra?9ex4Bfz5C0O`NY7-s z^{r@4iMnF1Lnb>U8-4d4hM#(@5#`L$3)JaET@N#ICb?Jtq8?%}>QwiLbJeXKE`hSVoXN+Yt7o>@3Y%d&@UnaRDJC$2SkzvgXSl8qL* z;^CFl2?)$;6_m)5C?uszk=a5}HB`Ioo&Zt(T`x z9S2IOZm|t_m%T%hmzISz*F6bMU0rQ0%}wZiwG6RzkD;;?ydpZB#SPtE z_8$4tWlMQgMKelfF63%^qvmtJ6-*Q$1R!soo!5XCEXXAq_E{w*!K(&Tqg$W?mo;WB zA!(3IZJCvQ5{THkdB`IS5J0{PC=297fM9oH(I+t2gh4F?F645N@>f(h1(}WA3{>?X zj;Uc98Sus6)vT+d10pVb|7MST`WJoEzxwf%X9T3C>0iDpR9a>g#Fp|N<4kLI`Th7A z2JP~Tvas2h%t}^+dywNOK+QTL61_l%%(ki(iFO-a3$4 zQqm_<^1UhfzLWyb#=zp$o{jzWUPErcx(H&MyoM|=%82sX@{!z;WX|%K->@1bBO)D^ zj#ORSe0B54TF$cGZ>W&cSAM{tR5O;!?b_#GwVzvgfV0^AhG!&#vg<28Df>;?SR2=L zkn8O7uX&y;V>ye%Z*YcbMpsO%+3H=hl`GrEZF`EdZ1)>>fW^X+iGt=tYQ8r$pNN_{ zf#d~5;4Jc5ih^3ovg9tMB+_QQ)cgPY(&ie!p&a{4MB=%nOqjv+qTwoE+H%k4fH93T zE(oM%^y_bwlz9!so|^u;p~8SMnae8m8cRWj?$?LJ*+9~Uu~lPB#+Px82f00mxI?ZW z1&*^DWbku@uEd|jD%m}`EG{1A#B7-`6?siXqeWbUjWZSbOwVwJXJ!evWuv8|AkD9z zE&Ro{3$TI5FQ9} zk6YlO$F4s@NDa%km7iCR)1TFht>!Fsvy06w-y-)OsCj&ohWb*k$1t@7!f{PS+?IIx z?~*GDpQPl!r&8eN_bDstbn-vw3=scAO6eA@{J&|H5GS0qIZ{YPVG-}|E&(t%pNJbW zD9Infpd_CO=KxN}aB7HU>_JD0`9u;hKFL~WFa?E1@IZr!5=0#2RZ&+|{}io~zj2sw zhM0sp$EZuppP}qFMG?U*6Jbh<1;E`(27GodYsE1|^$kQB@YqGoAh}|haYBNI&Q0dM zR~2D|LGTfEF?zRZW*bgPNDLbgK+p)cnH^OH+!8@U4Tc|et95p6>^TSX5B5;;J$fh; z+h~n7qWC#_Aqfg%X^MK7ku%0#^^0l{G{Q5G7p_5!5kW)EB;c5sM5iPGxh22B?>W|( zPebZcW<>j|d)1&Ch~2X6VP}~!T{cGu8uPX;2^z_+7oZ;!w4XlrE_i!K!o6Fs{(8Fa zCBd;90RmbdV&m{-0xn$kf&dHvjMp-~>qK-=%ap$mj`vC}_C%xlP%Uf?^ovEbRv;4; z=c=wIwyE7Apk9QsH4!K}7w$uy7f)^o_)h5mOoWLt-aN!}fXsS=9p@<*g7|a6O)|BS zotg`7nxpIsVVi$n90phiQ4ePHEC+sFYfz6y||EHk!!0C3G57Jz^y zZ|FHL_0gc3SOOFev;+iVYx*AoZh?m?DD@|!l=|hP&7(UXLRU`8MJV+*j4l7N?9;M| z%01r7J=}&yu5mwipo!c6EN5w+4KNTxJuL1aEk)?{E5|CwR*viZJD%ot?e^{1!)rw77FXLoTcCz?cgFIdhQdItN9<%$#?Nt%~5-TSR*+4%V?^F0v)k7<~&NLEv6mG08_j+kur*il*n z51B3$WP}r9x8D8$6-6^Q->LUcg6atzTOBUYzBwGr4mP#4*%|O}o%Z}x*ptZ_Dnb7w zRJ7kZb-fRKkA=I){jzMv;fZp#4I1y!7vZ5WassNnCk_r+NT;CSR=6XUZAUrV4)nYN z&GC1}Ag}K1dFN-3mXs{PLZI*}0F4)(ssfun97hwJ zYK5}5Kl$0MkA5k*6HH$|5Bk&FpZsX*!dqolp{NSF;y6^!%OMOOt8gJF3%7pxGK(%4 zBo!)=G;M4Pq{10eb!ZxSl`xMkG)GOJ{l{D9Lb^Gj|DdZx1(8&*&_mJlp$D{dk%Opr zq2?+y3ZixV^Yypj*+BoN&-P7Scu&X@26gkJ*PuP&v4WTI0w@{Qi1A9csRK@{Ix!U? zH?Kb0#G-zYXtKa90lXS`rK8Pm?-I3FVfcY~l|=!CMKckcVhK+)Pq*+2^lk{x+&tPV zwiC+tES`-*VyWDgBmDS=z>k|t%Y94Nc{TYSO@F~)V<08fqrWM50te!s7j&z&MRns1|GN8kySbJD%R+PRKyuDyrLeZg;b2T~UfwSJXaGMSyvEd{TH zhKX&vz1wzkdu+aK&v09txTZtg;kJpxUEaf8-1A3#hmUfHj&V!6ec3&nrDxJ?IlXyc z^WZVRc}c*Wc_TZgUys=8*uXJ3p_7{bRci6@qtMT^taFw#mZ2rSw8DPX&4iTG`T_l5 z+wk(?r!Ioe#_;naX(Rg4wd1O>V_fDof5KBYOlhcQGL=R)@NeeK0H#0cH9Z>0$a&*Q z&{w5exrM7nj*J}Utm}L!>$!yW!R0V(_cqX!5i~MeeDU?1rvBS|M#`M^4W)#MOvo5~ z-kV_U*H1z>;s@fn1*=CBN9ChWj2Dh)aXGvE3A+QP?4hM!nTi7Dw8;eXY2AQsaP>q& z-d72Ee+%lNj{A-xSSOzU5RY(XW`6+pJ*l(b1wwrQ7ylBV=%a4vje=WtZU7v#LB%*>$o3kt{RS&M zBu~|Ab>LKE$eZnw4K)Z*3>8B-4d8f96PK;&pIn(f`4X@{;LgXKz?RXwjlD=kcahiH zY+$Ofvv39=V|GWSOa?oP2n;%FGXX>M!4LmcN1+~L>2kKh631d9@!@&#WoIK|AXFyegHf!zp;M{uaYer)bT z4MH9s)O6q;5984oPU%kQP8tG6GZ|Kag@-yi;M||h#_Mdh_D;sthH-<<_Pnd9EtI3S z*_h5|NH&V+gBxnMRBzqcz*}~0-Tq`v-PT>U>fP0qI~%HPp&S;G5V0SyxT4s#5bzo} z5!?lQyIU|otjCNE7~pIQGA9m@WiWUi1H$5w&yopZe~7UkWAF+F#TZyISSAKLAmYe{ zA-_!hn<6Kup>p#BIk|xaC4mKxf343^?xb%dr4N=46bF@f0~+Q8%b@&3Z%|EcHB@>| zP)njZh~6b7g1{eC@j;nFnK`IDXE}U3n{d*+FwQYe29R5^M znp94mJ(*tUOE2~qC-v!^B3&3FvK~R#u^eJEL_B0CmYL(%ZBTNe7jh#c)D zf+p?eKYud)+O?@4eGD)Vz|?o}Y}O$YnQ6>FDt;YgE^p>JPuxwNx^kxvPOXIx)5ShW zH=B-hW0sje>=_$&iiXuu0>dW8(0Z^?lNX-w30w!jIcGj&!d7N-OM$Ckkse$%$SC@# zuPu}&VPNHCT=uYLROyd<0w9;RY-}+=2f^+%yw_)X{I7CI{2Fs$l_f0n8FMF$tGvcl zBQN-jm7J#XuWHC5CGR`*Q12Uw6)O3sDrJRH&D!CfSkapm5ECzH*^NSqwCoCIE<3jz z4qoBngbNm)0!$O3J0VR7zw5&5h0`u>WyHx;<@~kIyA-owD#=5PUW()+HjBi_ zuW9M*} z>Je7@n~4<(@=p_#6=`bXJ1_&fB8Vcq^}j&3@S8vYQXv+7F?0(h@=7>tO~<5>MbH3; zcBDQ)=@OyvX+hOUSfwV5Wk(db@}*G!7zO(10Ivs^P7;eeXoatM)CHQjx=3p`M|7 zt62t$7elY|MX(wQzY%JeIM@G#8pu)?zAhoX&mnEeM{iF5>@0kH!g{a;K1XGP^Y&dX zrxiTAP@wOB{pM)j&C!3Hy85%}zV|TkMBnt;6H|THNsT0!xdVyk3Pk;jUGiJ83Q7|6 z8H;sVhSW9dZJg05{6a=Vr*6M`{MPZ`Kz+CW`Axv&VyB4Q&l@635`ic(!FqG_!&?_W zyz{@{a-nD!`trsM8lf$r74aYod~K`Tg+N>=aEr_y9A04$Lz)ZCbg^m@j6`rWgt!RR z7wCnrXFLw%tQ!Je(S{!ZQ$Xzwa!N$-1tJz3Vypq>tKjAX942gac?*kb+L|0r{0bO; z(u+6-A@7K0FT7d&@9`+{YdsHF) z2$!;9Z1vbu&b-C1smD{&h7*SV7cVgHwVrPsNg1>I7uRu%ws6Vye$Cb!#$-?2w>S0X zZz!2EDMZYrmX1`6lyb?Bjpca^e$B>!G4VC+--1dYx$nq@ksM#UA)orBA|Jwk$=6n< z$p57zqcTzcnVyFDXNgJ(V>7w4@LJ8sBi@%MKYPsTZ?4tz;ml-m4JL?u^czTUg9)$@)~H$6U3Q|<>k-loDzc#f6xas z;rbb;24utyLnGCM8rY}{7`Kwx7!>env{6yp9qGIQ6jrdyg!_YgQPdT(xxiN)FUN~d znx>9Z-~d2QhKltTB)G0YQNk;uPoNQ;4gR{*gF{mzF9U;!%*fw-LF& zehxqA@$hU~=nHHY$lXP?zR>KH=k)FK!=zb(GwWMAV29a8?DM>=g&}r(-XMxD^`OY$ z6{xh}O=4P=-O&u6l|Y|eUeAPY;L}6DfaxGLuop1)4;cI$0vB%B0@oHgXC<3}b^4w2 zQU^ieg7ca#R3^uq%xJb9C$Mewz!&^9QFBgQYpyj>X^`%+En0f z4F0nbdT)7~y+d%;+zX`(1jxl}@f4Dc>;hVG)DB)FMtK97G@Ibnz^g*vMI)RZQ4ztB zqQN~4G&E>Hk#=62xGA$&f%d;5zh4gl@_VZ?9<;cp>jvtEQihX!<|2<4Sh*pkzvY72 zuU{NUTR5oq)ZQ?sc(efv9P>&9{q&|A8M*i|hT*so)|XM{shc#W4HjLN`;A3GnLKgv zWN!X@W#`L=+kCle`s=_@pPF;JZJ=#vg)e1EAbat-wli%2TC-ONtV`ZAoHq;~@LAV> zWo;PKd};X9F#fb}eM2CB*?aEuZf-@bFMsn_`7Psz|F!LRZQS0&zWSCxPT|*jRZ6_4 zCI}9cDHDcFuOV~r=|39sg1OMUdy7LK>gaF3P&usqMLm~W%9Yk|=8b;MrT}P&HI{Gh zWr3UKL~5ZowGckembf^OyXcKNP{=1P{@XqHWJoe@Hu2JCvts0_kxDLk_1FsBY&Hcn zab&BBzwaoA**snmU$v0>G%>kKE&Fuw1}ofru1>DXQGT8&!+180@fC{u)PrQDo`MzMMRD%xWxz>IouRjeC)gqz91-m zBo8+Q;VUe0!UtBw41~J8zP7!qlMS8kJc*>aaTvtasN)~|DDo8_$R9Lk5p+;p`;7`q}cr56$M}GD4n@~lTVw~rvl}l zDYefS$gpyh1#U8P;5U$#$x$hR^bC$l4XU!}!oi(GD~EH39YgE9S*3R=NC~c|6X}Fu z^KkP>>G0u;*+B|!M>mb_94{4gZsXOJ1IjdwBQuS<3-=;W^{41>osRLP1yW50z< zFZ3oB`JitqC7r=#7K3Lmo$+--0$oK9RR$^i9a%kEd9_@KgBp+9v(LM2f9SRa>@v@D zjw4*h5pT=UyM#Wtlmb;}26zn@f2~cU%i+Ui{f@!P{$m4=1m$plBOdNgvTh zNSD&_!;0Y@Be}!7FUAKcxE);))@XvS^F2)aHh46S(wN{S!RJx?C&dy#UUb2IM>tn<5|SpJ*e4klo`m3&Pd)d}>@d4XgNfc`?>&3I z=G^-`=iK|3*IR&Kv`HUJNAnPRO#ftt+a%Tq5TgjA4um=Ekhu;HR;TQUI6Isi!d}da zJa&nE7mwY8t_~M2wsAbbacAk|0mlRSd0h^iKgf6F;{vcRq&~2t%Z-bGSCDl0wBV)@KN0x`Kq-UoVQ#bXWN0(`IZGKM6J04f1STrPS9xQf+60)kfiKr$> zG|W*QbMUY_;kR}KrctDtY=n9o7HJVZE{n7X%=M#5Q@0)Y9f`j|R3N}ux_Vb1`9Z&X zYhiYL@sA6wAK7DO8f-dDx|INW`QB^n9P4Yv8+~X6>(U9Q=7=ep zM>O1N&Tdhi;0QfVg`ThQ>|9HrGkK;aBz49~tVay1o$~p_!|*VZM7icx$dq=+Yp z3@P<|cUp0_B7vrZND)j%G7&G3+D?kAsR*kg1`|XkJ3v%IHOpYi0e7yfI(2luuzrrO zC)+^$iqZAvpqnZE#o^zVZeG@>zghn77L@bnF*p;biO7~X4R}{d?R>POp-SMx5`cN-E#-3w{ebdH1GvIYgnzl z8CcNTt+qdDs;IQ_vte++C( zZPG9_OwvCN>bFPrp?f(%oC`(bvbcUJ7_CkCP@spj-s`ITmR+1kQMJm@`=T;)=s^ir ze(9&z3XBS2>EU(#;{5~($k%F>@drBmN7+`A2lv-tg`%tg+OvmXHvWI(Wc(yv{yAY;MjhzDjYF(X=XGe|KL zr`Npkt9+T`Z^gddf^YA>{q+q84mLK``HZA$^E*gBY>m)|AkgKMej6r*9-AN-h-}aG zOWp2C2?a}Y!c!+FVKkl=8%qrq3!%5ddJa9yI~0lGge*3b_d!5gf)a&k#mRBpKj@vu zDF0uyYt`-L_Kvl$AXrirYjjx^yj;VL3d(AjYd=MK4V2e(BTRWsclJ|O(~7X2bC2B8dq4*F7(S=>-hM)Hpw=(qMR2vrV47u75lK)$+!FR-woJy-biKFrV%yu?duyo_ zKZtA)bznqbb7Ppr>4sYM2iCwo)SvrP;}YEs%YN8f@RyC4n8=@dTOCj)KXgg^96Fh32t;J+lK{sDWK)O82b@79ko5v9g@k|`;{DlXKqG;E z^LiZ))&!)SGzuoNxEn@c2i~=)O=RbyRiZQz1Eb(9_(7-X_F!G?Wl<~8HEAM7w^&!Q zXtF3u9YbECCbDt73%Yeuu_o=XYA8QOb1`%ij2{0@pflnIL6q17D#0co!mtj4y=Vo3 z!)UFO*bBn25=cdvod9$!(NKP@0>bugvcUT*_ArJh*-@I;(GboJk}TEc`4O!y&(AcZ zowo-v`flRPhH1=TT8Z79yLRsJbbrp43O-ipTi~G}tYrN1+=ZV~ zV}r`zH^@cm%+=J%E69a15KB&-PmRWq7o|7;@W@f6Z)EP=MdS+|AUQRhiEZ}Ulrnbw zWsfdSWD6WNb3V3}mzw!dfN2s0UZ|y&-%e+~$YlJaa(Ptw{$grmG8Z?_K@ASGk#+M7 zo7c&vWPDng9!QOhr_S^hsx@`G*`S0zUu0j6GE;{~L*LHDVmad~XzFzNgFZH&_WJsI znsG@kdOPjoU5pclu)*%;10K#>gC}-J&H~;W6u5x20c+&fyX^bfh`pA!A8`3Xti3y@ zkOQyuf{VAoj!nHC5?Mjay35;roxjh!>>PvqZF-Bg2V8y@yKeAtZsht4)ny2O884JO zo9tRTvz!#CMXFEAj66H5uqK0hXM5hN4MmB$nv#8cyCix&6 zkV#wznZ)&xOBT^XCj6pTCK%o=6G2{-O*t~igeVCPN4`IaFo;wu<` zcl7f}Q*C=>U$u*C4+?w->z3Mm2g7a`S1lflcs1;I2dhz$4zNOX(BSO|`&eN!j4; zq&3I?&bMb)tKwv@-E#U3_o+{X|vEz0%!@qTLy3v_E>{&`jx?#M-TKM|^dn zyy;$PbE2sEsUBEM2dhSjaqr*7HE94%^wo-)GTX@3Q(H$rI$hV-_>4ens*yg+GeaIP zBrPTPEo<*u);<9gWuCE?-nXv1YhCxiS}|U6Vg0%F8fA1VGCAz*szES&Cr`oUz&xtF}+O|E!qu#6L@{XufCNl`!sFNE2B4T-<@q z-_x*c2MKQ3mN#rBZdIEw-c0R)hFf(c!nYxTd$Oz%5Vvhxc9sx#N|qsfr^>c-8*yhF zg|NdU8=Ou=3#U`S?~QE04DfMZW{D_8f{54xejEf$1p)uZpcdg5FpnU58Bq7!Sm)=N zkdLhwFw^k^FK$E~iWS0tK>0&p`4_DC50t0%@4yx~x@=S&Zyb%>UFk>zyl~O|R2_wN GNa=rd)2`6~ diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/logger_config.cpython-312.pyc b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/logger_config.cpython-312.pyc deleted file mode 100644 index 1da07dc8eb43a5ad2d4ecdf67a8f1dce5cd3b7d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7496 zcmcgxZ)_V!b{~>Ua!D@#$ofZ0q)aQKEJ~3q$4-2a9bXjxQJjy5Ho}) zgM`=d`lxPDN5A?(J^dO64e&KYnV50V7&8r;bl8rGnq%fcGo_7DHf9;L=!l;55#Dr_ z@aDUERr;V+PXz1T!cc=Yo)rwE?n1HbGpy$=f}OVt4&DYWn#x!Oa@fRkSB--%-VW`y zYQyl3tIS~4gg)pTKLQ=Cs}F82e{tpE%r95IntAl&pRWGq-ox2jD<92RAHMhg@^5df z{>=kuvHI)VE9twdzqnf7ZsjkhSMS|gegDs`k8lP9LA_FaG9iVfNPOgIBr3cbj`LAL zRNSW{F@dkZ3F;J<4@-gse4v@v!_xVo!eZ#hUMQ^B=x_rB0*WW7bWpgPHwX+Ug@Gj- zd81$g+5}58^Jak^-BvC}rFoXO@YZ1?sD|M=!31hD@ecSo1uOJtN9&&KGtBWWkk9c| z&}XYQ48IM=wOfdV@E*PzTGkXs*s2H1*Md~XmJ;=R9Y}P33%?%tF4)8dh3ij@j0obP zM0_|hGF}U7DBmM>gH}I#Frcu}#7Hv4N5rAR$)n3PK#sq47gcdeCss*7sVSwqTcA@U zRIH76mRd-R)>dmdYSL;--%52;`V?&Bq>Yer*(owwKD&a5QALp;33HGgp9bDa`lFTGKUtandAVk-{z57ez2Zo+ zr&vO+rtu!X+H>{By_Mhm?CZ}zUHR?R|N6V>M-Q%jeedJtFMj>#)|bF~n86$xEPwG6 zFg5J_@aKQA{NQg_KK`;OR$;LIG@6cZIg*q@i3^jj2in5PA!*z^S7H zL92)=6;VG58w#PIP)IS2g+(DQDaP0ZI9rMprWYDch_SGw*wDDOLVajNkV43%Qwj}> ziC9R8UsNo`I)w{G;t?qljz%U0K4?&wq!1le2js@Y#HcVNg~UWcO5%wH{eJUjM|$3z zINuqLyjfts8J)Z|6pnNzCnrWUIyN-cnH0o}g4j709=ZT4EO0`X65@s7XyQ^xN`#=* zR@LKDB$|8^rU`Zy8lr~QH8z1d!0CoUlePV2HrSol-b72={dG> zfo)%Eh^CooXRfg;%`C9)oX3~-JTH5mpZDy8dPmN?BhCDd^{m5?L@|kiG%m)6ie?Ly zc+jb_pM%;NQZnO8Mh78GuLY^mqG_RQHE9MkrIU)LHd-_#tra0fL9=2! zeB_ls=@We*S}~vLJvne}pig0tP>e@To;-00~#Gs(qi)I!|N+J}|F@$2_u`v*# z8I%YRh=_P_CtEh2>B`0%fR@QI$gYq@cf(D~bxWpW_Ts$zg{=F4>^?B>KA5&I*Lr8( z`rxg_n#P&_5BeADn`Ykm;2kWT_~681f#=?yb+^gxHYjIn19EL(&B=PKX?uPfarv@N zzwGp9e7`b(&V9zsJ71b(UlKhqs8Xj|`(`_i5RxH}@W`H4SXf{P{hf$gsn}!T%c|!} zf{#(SbKnrh$H1t^!G$@%wv!3)YKd`aY+M>D9&6}{Sdf~|G(@}^h*_ghu{nt~61Jgf zv=|w+8bUOaIYqu(yD3x3G^(M{k{FWG-z}K6jzEhFM#lqMZ6JrqTd?XWGqiy?yE&!I zB`MRjO!=HDm?EsWf{{UO1>@38V2TBP1t^x%-2jCAkshP}ltsexNmI&_GNc$9*$dj6 zvZkz0)qr&rkL{+EVoR%o_2YTjlv38R7F*AgDa_MWHc#6V^W;)orFlw4G&9<;X?Q4> zjnDlb*T9v@*m@1d!W!%ndL5clcHYFB0Tk;$vD_`V_bErpQE3$$&YRqZjg^Q*ggaq$G{}h1^f{emk zIxoZ(HWHTrM1`Z`^H43m0NEs~!NkeE-x-1i+KMpq^!T$Nr2viTJ+p|kSME(eOurAP zv>Z71_=7cK4UUc)Q!EgZ)F7p>Ngy$xDeQUm)|pi7q7X}5EZ#ybM=22R#Bx@6o*fuasXd%OcWd_c2E>VOq@F&r3Q5e4ymYk1MfKy zl6(dn;1!Yw{Oiivy#H$RE><^Wt6Svimig+|Ij(iN&X=v*Dc9|suM4JaIkq~>dS%vo z?TrO?=Td!hw!TBI@3_-DSKsyDL}%HR?#prYSuP-Rfy~J7xURebDj%B&;D(xjTocIf zw-dJ#_oeK4WoD;+e}oxqbUE{!~z@KBzt=>ZC_x$r3K&^YbL@qEH(wQO?%{~ zJ-<5i`D>rOHsAF9^obnTnB`hzt|jxv0=H|?zccIKE&F%pTt{=Rnykw&yZq}$!w$zB z=gYIi?#*&-GS@a2>{;OU)BS0dx#rCG7q~r3UO(PdJak~U%(~|q`~HFX7yCcj|Nfmh z&zS}GM@!CzIkrK?bGBH59|!z}c&+j)1v-qUU0h197N^J^9Xv}>cOf2tu0SK$5KrUL z#L(>btEajO@oiyr)hs8QHk|Ay_!B4sZ5yDkv#x+q8|R&!b8M%&8i13ru>@SOCAuK` z07J3o5PB0WA;_e_vg3W7wrtb)vx3apG$5jv)fc$?!5pD=d|?Was- zx4#Mi5+ql++dyrFuvvtA2sDdo!Xo`W* zXB+^i|2lbu`V-OJhc6Js3a2iZ?LBtyN6w zSx~l>wi0USCW^MZA%Ie*5pz;*n=>CU<8Rl|k~SH}{! zEz9|2&UbBOfoolI)~4? zV-AJ*#1Ie$cz+P_KOBkyFeQC$Q1_F*h%o08iKt@gr`Sk519YQxk zeXl_K6EQ>Ill&U8tK{F9?Q2Yxu{l${PT-SowHXi20u7(Mm$V+twRYZ)-iqehdvd|{ z+_T+lwj(-YV{UtM-hjlC|CziI35dgiw!E2=ENR)9w@}hbTHEuqOWsOec*Bk3{yPnG%;JXxjHgAM44ARk+M-EUuy}GZO zzHC~j#pjGhYi8f=gSQT@6Cm@BV)-$aRVC2%cc2nVfD><<8$u{ro*HL>fDj6~nnnxu zrG3^M7Z-cy9_$4t4Um?pj zch=S_+gdZ1?|bHL`{$VbbVUk75kR>+!Aa8T7Z3mx)l_-pWQoe>z-q}=>&t@M2I{@Y{BdZX7onlDwtYV0a_>N z-9kjwi|8#C^BDY#D;hbczNFKAqw}IBj4w*!1tKgespC%)ek_y3qq>UGvNLh~p?s(gRtxMGLn z*8Ykv7wrDjd&j$G(CZFl_T&kCa?iBpv6$=W{i^HBu63mIR-JD5`*^Z-yRV(OdG`9* J$4Jtd{V({qmb?G} diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/name_helper.cpython-312.pyc b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/__pycache__/name_helper.cpython-312.pyc deleted file mode 100644 index b61c36d17d72783c9902647126bf5c81590cd1cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1543 zcmah}U1(fI6rTJ4lTD*-(}b+jvRVyNBEEF}4G#-k>ye6DxBT|+LifKPtA(t+kTlr#U<xcR-br6-|fDamL8V!<{|L(w(N zBWR~lQl+bhds-rzA#Lpi64C^afL#j>00-EOm2r}W&2o#nS(_iHH99VAo+k)&{i5TH zL&e>*Hi^b)Yy*YWIET3gG}d9Sf$Quz=WzXCH?SKi>;R~1oVR^-ka$bo`n%V72M`Rw zp@bW5(ELg?ZCKNhl!R_Q^YHI4Hh@cp$uLuJ4yGYB$N(3f_rLJklq=f5eR z`)YaaYUz{dDq*G1&K~aF$Xa!{wtBuae?A=WNGX3hzdH3b;l`WtR5yxQVb5~0|G{K9 zu^y;oioKXB_nkU<1BoqcFLE8&c`)<&~?atNS*@ z8>BF2xpjpBrb3ufu#_>;iDARcC|;5N`<|uG?>@eFBODl`_XN@|d3yq@m##d%b0-{l zo4|>1R8{oku-O+Bkz0GnEiTOWy>;}>?jx9uo1~Tn1XD&-g;+W?s=}15n@P+YW-JB~ z<{&~Q3JQpGH@goKH&vg2V(6BQ<(fk-hke5)N<< z8`h9}zA^%h6htqkvUqC@CLxO2P-#Smc9PKCGPkCQIt422PjQ{gJD$sj?xkm)yUxQM zy}72P*0za(ivyGIF17}9a)k%&yCzOvJeju&J&SEG<$PqMEs&Qdox~WoQHYg3GO*jA1DT?ITs5MaBET6a{S~_?RJldWrcG ztPvj|_lsN?Ur(4?+JFa82XWoc%Q`?*g`p_w7nY{j-$g(@{}b48h~KT7`ZCI95B ei&DoazQR94b>`IyAl*!l*Uk0(=6Yy1f%pr=5z~VC_`5(UzvNjPGnN_}A{v zP%}pgj4YnhQ2PJ=u>;bq+y00&-^KY0Q;;V0uYKR09Xo;b zNq&Cs`@X;4_s8emKN=b=2*wTXQQp*m(0}lzGK}TL6BZUzh(!a4C0K)>2p9$o0djy0 zPy-Z!Sdt?-nlpF}JAsd-YIk0Wr7uteM%GwM1s)S;=4f34cuc@k7d1QTBD+9^#pyZ8 z1>DT)rK@T@p?rBw8U0e7n^i7PS=O*i4pJsx4@PC`Fz=CM>Nqb-vL);m+yPGF1W`5$ zoD>m)9s)C$2L=Bm{GObEX$r-VXk-Z<`4DTEA)QEC=Z&HH@_LwH$ry2(5NMW&!TJzL zvXoBY5l|?NLhBSCrqGxYBb>-#jGQ;XO&A!3g+HoSuNBjOT^s$w>iyfR(-)M9^XlXs z<@R*p-bMYA%rD;fC8OUI&KW)!j!1eGG!@#U-uhG-yAOhe#p%M*<<-egl-uL#WYlDu zX%C$k;yhCOqs5D534!}fgcmrrmr<5RmDzda)|7ht?@D~CI6GHn2@9bxCrCUe0@K|q z>d0sMy+E^WUrkRGGB@-i>?-!6n_)T8Bk*B~4+Xm!Z%7EZrEZ4%z#RztIgwetb+PJ1 z4Qti==?if4!u>z16BEUyJJ{Iu^Xioub*4tJuy9S8_)r=BqOkCBVPTAYcrT>$Q4xiCSBVP}hiIzWwcy-ofa}9ydP- z8gbnW`p=&6xcMIOY}8j-2Rz{(krPgHLQmN3Ipy|o#fhL#^!#N986T7vu!D#JOr+6SjbBb?_6tFpZ$cov40$$%vYE=UZCy%`J~j z)D}w#QFWH5jc8kQ((=H*XXH3Am>Uy=Inzt|wyt=6qCaPDT`}2{hjON_dRSTkrs!uMl{HDmZ5CTcVi zQsoIiHGb9sFRk$!V#HzeJ_LQ3jFBl@Qw*E5dHFyjJav}-De^q%X`q(gU}#EWSAPJYy6;}*sXsSX?L8b|r=u zP5POhmm&aw@0|#R{M`(+K6L$F-6udcbu!+~tX=w~m`oxS~^kL;<+hW*4e(q`{O95PH|+gkYJyRT=Cw1`DQ)_Z1aPmkuUf8dPARj;fGu z2ryqs%rjMxDeJ(@P`nkdu!U!MzROh&mO#fV8+B9wY_q3j?A0Esqmf1ss(rwiuE=g6 zbusG94P|Do))N4B{r!rVv5@wl0eh#$4qoer-{EPbMPuC%jha|G4DUqjz){YQUL^1Y z7njByv~1x;J}64=pobISYYfSxkAp89KmNj@d5H^%4x{h_9B44DWkIIBJm+WSIvh?{ z5WazAv$kY8uZY_h*BZkJHmt7cwA;_K&%h@B=z0}?q8X581U=mbu$l1Aun(caebJfS;auM0EYX;$WI;@4%Aaf7|IfCAxweB3+J5v6qUmEq z&b%vLmw2t#Omp&Qsb8m#WnXzW+tQ!4_b=D?uUNMxo0qLG;0LN_g;L0x21mKWuhzfy_7ijM(TsveI*3jJMU&*{o0;6m^*OfDc) 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: - """工作流API客户端""" - - def __init__( - self, - base_url: Optional[str] = None, - token: Optional[str] = None, - default_timeout: float = DEFAULT_TIMEOUT, - execute_timeout: float = WORKFLOW_EXECUTE_TIMEOUT - ): - """ - 初始化API客户端 - - Args: - base_url: API基础URL(默认从环境变量 backendBaseUrl 读取) - token: 认证令牌(默认从环境变量 workflowExecuteKey 读取) - default_timeout: 普通请求超时时间(秒),默认30秒 - execute_timeout: 工作流执行超时时间(秒),默认300秒(5分钟) - """ - if base_url is None: - base_url = get_backend_base_url() - - if token is None: - token = get_workflow_execute_key() - - self.base_url = base_url.rstrip('/') - self.token = token - self.default_timeout = default_timeout - self.execute_timeout = execute_timeout - self._client: Optional[httpx.Client] = None - - # 打印完整的配置信息用于调试 - logger.info(f"[客户端初始化] base_url={self.base_url}") - logger.info(f"[客户端初始化] token={self.token}") - logger.info(f"[客户端初始化] execute_timeout={self.execute_timeout}s") - - @property - def client(self) -> httpx.Client: - """懒加载 HTTP 客户端""" - if self._client is None: - self._client = httpx.Client(timeout=self.default_timeout) - return self._client - - def _get_headers(self) -> Dict[str, str]: - """获取请求头""" - return { - 'X-API-Key': f'{self.token}', - } - - def get_workflow_by_id(self, workflow_id: str) -> Dict[str, Any]: - """ - 根据工作流ID获取工作流信息 - - Args: - workflow_id: 工作流ID - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - url = f"{self.base_url}/system/workflowManage/getByWorkflowId/{workflow_id}" - - try: - logger.info(f"[API请求] GET {url}") - logger.debug(f"[API请求] Headers: {self._get_headers()}") - - response = self.client.get( - url, - headers=self._get_headers() - ) - - logger.info(f"[API响应] HTTP {response.status_code}") - logger.debug(f"[API响应] Headers: {dict(response.headers)}") - - response.raise_for_status() - data = response.json() - - logger.info(f"[API响应] 获取工作流配置成功: workflow_id={workflow_id}") - logger.debug(f"[API响应] Body: {json.dumps(data, ensure_ascii=False, indent=2)}") - - return data - - except httpx.TimeoutException: - error_msg = f"API请求超时: {url}" - logger.error(f"[API错误] {error_msg}") - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"API请求失败 (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"API请求异常: {url}, 错误: {str(e)}" - logger.error(f"[API错误] {error_msg}") - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理API响应时出错: {str(e)}" - logger.error(f"[API错误] {error_msg}", exc_info=True) - raise Exception(error_msg) - - def execute_workflow(self, request_data: Dict[str, Any]) -> Dict[str, Any]: - """ - 执行工作流 - - Args: - request_data: 请求数据,包含工作流执行所需的参数 - - Returns: - API响应数据 - - Raises: - Exception: 请求失败时抛出 - """ - url = f"{self.base_url}/open/workflow/execute" - - try: - headers = self._get_headers() - headers['Content-Type'] = 'application/json' - headers['Accept'] = '*/*' - - # 打印完整的请求信息 - logger.info(f"[执行工作流] URL: {url}") - logger.info(f"[执行工作流] Token: {self.token}") - logger.info(f"[执行工作流] Headers: {json.dumps(headers, ensure_ascii=False)}") - logger.info(f"[执行工作流] 请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}") - logger.info(f"[执行工作流] 超时时间: {self.execute_timeout}s") - - # 使用更长的超时时间执行工作流 - response = self.client.post( - url, - headers=headers, - json=request_data, - timeout=self.execute_timeout # 使用工作流执行专用超时时间 - ) - - logger.info(f"[API响应] HTTP {response.status_code}") - logger.debug(f"[API响应] Headers: {dict(response.headers)}") - - response.raise_for_status() - data = response.json() - - logger.info(f"[API响应] 执行工作流成功") - logger.debug(f"[API响应] Body: {json.dumps(data, ensure_ascii=False, indent=2)}") - - # 兼容:如果服务端返回 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}" - logger.error(f"[API错误] {error_msg}") - raise Exception(error_msg) - - except httpx.HTTPStatusError as e: - error_msg = f"执行工作流API请求失败 (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"执行工作流API请求异常: {url}, 错误: {str(e)}" - logger.error(f"[API错误] {error_msg}") - raise Exception(error_msg) - - except Exception as e: - error_msg = f"处理执行工作流API响应时出错: {str(e)}" - 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: - self._client.close() - self._client = None - - def __enter__(self): - """支持 context manager""" - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """退出时关闭客户端""" - self.close() - return False - - -# 懒加载的默认客户端 -_default_client: Optional[WorkflowAPIClient] = None - - -def get_default_client() -> WorkflowAPIClient: - """获取默认客户端(懒加载)""" - global _default_client - if _default_client is None: - _default_client = WorkflowAPIClient() - return _default_client - - -def get_workflow_by_id(workflow_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: - """ - 便捷函数:根据工作流ID获取工作流信息 - - Args: - workflow_id: 工作流ID - base_url: API基础URL(可选) - token: 认证令牌(可选) - - Returns: - API响应数据 - """ - if base_url or token: - with WorkflowAPIClient(base_url=base_url, token=token) as client: - return client.get_workflow_by_id(workflow_id) - else: - return get_default_client().get_workflow_by_id(workflow_id) - - -def execute_workflow( - request_data: Dict[str, Any], - base_url: Optional[str] = None, - token: Optional[str] = None, - timeout: float = WORKFLOW_EXECUTE_TIMEOUT -) -> Dict[str, Any]: - """ - 便捷函数:执行工作流 - - Args: - request_data: 请求数据 - base_url: API基础URL(可选) - token: 认证令牌(可选) - timeout: 超时时间(秒),默认300秒(5分钟) - - Returns: - API响应数据 - """ - if base_url or token: - with WorkflowAPIClient(base_url=base_url, token=token, execute_timeout=timeout) as client: - return client.execute_workflow(request_data) - else: - # 更新默认客户端的超时时间 - default_client = get_default_client() - default_client.execute_timeout = timeout - return default_client.execute_workflow(request_data) - - -def extract_final_output(response: Dict[str, Any]) -> Dict[str, Any]: - """ - 提取工作流执行结果中 final_output 的最后一个属性的所有值 - - Args: - response: execute_workflow 返回的响应数据 - - Returns: - final_output 中最后一个节点的输出值 - """ - try: - # 获取 data.final_output - data = response.get("data", {}) - final_output = data.get("final_output", {}) - - if not final_output: - logger.warning("final_output 为空") - return {} - - # 获取最后一个 key 的值 - last_key = list(final_output.keys())[-1] - result = final_output[last_key] - - logger.info(f"提取 final_output 最后节点成功: key={last_key}") - logger.debug(f"提取结果: {json.dumps(result, ensure_ascii=False)}") - - return result - - except Exception as e: - logger.error(f"提取 final_output 失败: {e}", exc_info=True) - return {} - - -def process_workflow_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: - """ - 处理API响应数据,映射为工作流配置格式 - - Args: - response: API原始响应数据 - - Returns: - 处理后的工作流配置列表 - """ - try: - data_list = response.get("data", []) - - # 如果 data 是单个对象而不是列表,转换为列表 - if isinstance(data_list, dict): - data_list = [data_list] - - workflows = [] - for workflow in data_list: - # 解析 inputParams 字符串为 JSON 对象 - input_params = workflow.get("inputParams", {}) - if isinstance(input_params, str): - try: - input_params = json.loads(input_params) - except json.JSONDecodeError: - input_params = {} - - # 映射字段 - config = { - "id": workflow.get("id"), - "workflowId": workflow.get("workflowId"), - "workflowName": workflow.get("workflowName") or workflow.get("name"), - "workflowDescription": workflow.get("workflowDescription") or workflow.get("description"), - "inputParams": input_params - } - workflows.append(config) - - logger.info(f"成功处理 {len(workflows)} 条工作流数据") - return workflows - - except Exception as e: - logger.error(f"处理API响应数据失败: {e}", exc_info=True) - raise diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/env_config.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/env_config.py deleted file mode 100644 index eac8f35..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/env_config.py +++ /dev/null @@ -1,85 +0,0 @@ -"""环境变量配置模块""" - -import os -from typing import Optional - - -def get_workflow_id(default: str = "") -> str: - """ - 获取工作流ID环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 工作流ID - - Environment Variables: - workflowId: 工作流ID - """ - return os.environ.get("workflowId", default) - - -def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: - """ - 获取后端API基础URL环境变量 - - Args: - default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086") - - Returns: - str: 后端API基础URL - - Environment Variables: - backendBaseUrl: 后端API基础URL - """ - return os.environ.get("backendBaseUrl", default) - - -def get_workflow_execute_key(default: str = "") -> str: - """ - 获取工作流执行密钥(Token)环境变量 - - Args: - default: 默认值(默认为 "") - - Returns: - str: 工作流执行密钥 - - Environment Variables: - workflowExecuteKey: 工作流执行密钥/Token - """ - return os.environ.get("workflowExecuteKey", default) - - -def get_env_config() -> dict: - """ - 获取所有环境配置 - - Returns: - dict: 包含所有配置的字典 - - Example: - config = get_env_config() - print(config['workflow_id']) # 输出: "" - print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086" - """ - return { - "workflow_id": get_workflow_id(), - "backend_base_url": get_backend_base_url(), - "workflow_execute_key": get_workflow_execute_key() - } - - -def set_env_variable(key: str, value: str) -> None: - """ - 设置环境变量(仅在当前进程中有效) - - Args: - key: 环境变量名 - value: 环境变量值 - - Example: - set_env_variable("workflowId", "1234567890") - """ - os.environ[key] = value diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/json_helper.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/json_helper.py deleted file mode 100644 index cd6894e..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/json_helper.py +++ /dev/null @@ -1,59 +0,0 @@ -"""JSON 文件读取工具""" - -import json -from pathlib import Path -from typing import Any, Union - - -def load_json(json_path: Union[str, Path]) -> Any: - """ - 读取 JSON 文件并返回其内容 - - Args: - json_path: JSON 文件的路径(支持字符串或 Path 对象) - - Returns: - JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型) - - Raises: - FileNotFoundError: 当文件不存在时 - json.JSONDecodeError: 当 JSON 格式无效时 - Exception: 其他读取错误 - - Example: - >>> data = load_json('config.json') - >>> print(data) - {'key': 'value'} - - >>> data = load_json(Path('data/users.json')) - >>> print(data) - [{'id': 1, 'name': 'Alice'}] - """ - try: - # 转换为 Path 对象 - path = Path(json_path) - - # 检查文件是否存在 - if not path.exists(): - raise FileNotFoundError(f"JSON 文件不存在: {json_path}") - - # 检查是否为文件 - if not path.is_file(): - raise ValueError(f"路径不是一个文件: {json_path}") - - # 读取并解析 JSON 文件 - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - - return data - - except json.JSONDecodeError as e: - raise json.JSONDecodeError( - f"JSON 格式错误: {e.msg}", - e.doc, - e.pos - ) - except FileNotFoundError: - raise - except Exception as e: - raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/logger_config.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/logger_config.py deleted file mode 100644 index 38efa16..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/logger_config.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -""" -统一日志配置模块 -提供系统级别的日志配置和管理 -""" - -import os -import sys -import logging -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from datetime import datetime -from pathlib import Path - - -class LoggerConfig: - """日志配置管理类""" - - def __init__(self, logs_dir: str = None): - """初始化日志配置 - - Args: - logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹 - """ - if logs_dir: - self.logs_dir = Path(logs_dir) - else: - project_root = Path(__file__).parent.parent - self.logs_dir = project_root / "logs" - - self.logs_dir.mkdir(exist_ok=True) - - self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' - self.date_format = '%Y-%m-%d %H:%M:%S' - self.log_level = self._get_log_level_from_env() - self._initialized = False - - def _get_log_level_from_env(self) -> int: - log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() - level_mapping = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'WARN': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, - 'FATAL': logging.CRITICAL - } - return level_mapping.get(log_level_str, logging.INFO) - - def setup_logging(self, - app_name: str = "lzwcai_workflow_to_mcp", - log_level: int = logging.INFO, - max_file_size: int = 10 * 1024 * 1024, - backup_count: int = 5, - console_output: bool = True) -> logging.Logger: - if self._initialized: - return logging.getLogger() - - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - formatter = logging.Formatter(self.log_format, self.date_format) - - # 1. 主日志文件 - 按大小滚动 - main_log_file = self.logs_dir / f"{app_name}.log" - file_handler = RotatingFileHandler( - main_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # 2. 错误日志文件 - error_log_file = self.logs_dir / f"{app_name}_error.log" - error_handler = RotatingFileHandler( - error_log_file, - maxBytes=max_file_size, - backupCount=backup_count, - encoding='utf-8' - ) - error_handler.setLevel(logging.ERROR) - error_handler.setFormatter(formatter) - root_logger.addHandler(error_handler) - - # 3. 按日期滚动的日志文件 - daily_log_file = self.logs_dir / f"{app_name}_daily.log" - daily_handler = TimedRotatingFileHandler( - daily_log_file, - when='midnight', - interval=1, - backupCount=30, - encoding='utf-8' - ) - daily_handler.setLevel(log_level) - daily_handler.setFormatter(formatter) - daily_handler.suffix = "%Y-%m-%d" - root_logger.addHandler(daily_handler) - - # 4. 控制台输出 (MCP协议使用stdio时,必须将日志输出到stderr) - if console_output: - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setLevel(log_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - self.date_format - ) - console_handler.setFormatter(console_formatter) - root_logger.addHandler(console_handler) - - self._initialized = True - root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") - - return root_logger - - def get_module_logger(self, module_name: str) -> logging.Logger: - return logging.getLogger(module_name) - - def create_component_logger(self, - component_name: str, - log_file: str = None, - log_level: int = None) -> logging.Logger: - logger = logging.getLogger(component_name) - - if log_file: - component_log_file = self.logs_dir / log_file - handler = RotatingFileHandler( - component_log_file, - maxBytes=5 * 1024 * 1024, - backupCount=3, - encoding='utf-8' - ) - - formatter = logging.Formatter(self.log_format, self.date_format) - handler.setFormatter(formatter) - - if log_level: - handler.setLevel(log_level) - - logger.addHandler(handler) - - return logger - - def setup_mcp_logging(self) -> logging.Logger: - return self.create_component_logger( - "mcp_services", - "mcp_services.log", - logging.DEBUG - ) - - def setup_api_logging(self) -> logging.Logger: - return self.create_component_logger( - "api_requests", - "api_requests.log", - logging.INFO - ) - - -# 全局日志配置实例 -logger_config = LoggerConfig() - - -def setup_system_logging(app_name: str = "lzwcai_workflow_to_mcp", - log_level: int = logging.INFO) -> logging.Logger: - return logger_config.setup_logging(app_name, log_level) - - -def get_logger(name: str) -> logging.Logger: - return logger_config.get_module_logger(name) diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/name_helper.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/name_helper.py deleted file mode 100644 index 7a5499d..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/name_helper.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -""" -名称生成工具模块 -""" - -from pypinyin import lazy_pinyin, Style -import logging - -logger = logging.getLogger(__name__) - - -def generate_tool_name(business_name: str, tool_id: str) -> str: - """ - 根据业务名称和ID生成工具名称 - 格式: tool_拼音_id - - Args: - business_name: 业务名称(中文) - tool_id: 工具ID - - Returns: - str: 格式化的工具名称 - """ - try: - # 将中文转换为拼音(无音调,小写) - pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL) - # 拼接拼音 - pinyin_str = ''.join(pinyin_list) - - # 将 ID 中的 '-' 替换为 '_' - formatted_id = tool_id.replace('-', '_') - - # 组合成最终的工具名称 - tool_name = f"workflow_{pinyin_str}_{formatted_id}" - - return tool_name - except Exception as e: - logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True) - # 降级处理:如果拼音转换失败,使用 ID - return f"workflow_{tool_id.replace('-', '_')}" diff --git a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/schema_helper.py b/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/schema_helper.py deleted file mode 100644 index ee92867..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/utils/schema_helper.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Schema 生成工具模块 -""" - -from typing import Any, Dict, List - - -def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]: - """ - 从查询配置的参数定义生成 MCP 工具的 inputSchema - - 此函数会保留完整的 JSON Schema 信息,包括: - - type: Schema 类型(通常是 "object") - - required: 必填字段列表 - - properties: 属性定义(包括每个属性的 type, description, format, examples 等) - - description: Schema 的整体描述(如果有) - - 以及其他任何 JSON Schema 标准字段 - - Args: - parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象 - - Returns: - Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象 - """ - # 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用 - # 但确保至少包含 type 和 properties - if not parameters: - # 如果 parameters 为空,返回一个空的 object schema - return { - "type": "object", - "properties": {}, - "required": [] - } - - # 深拷贝 parameters 以避免修改原始数据 - input_schema = dict(parameters) - - # 确保必需的字段存在 - if "type" not in input_schema: - input_schema["type"] = "object" - - if "properties" not in input_schema: - input_schema["properties"] = {} - - if "required" not in input_schema: - input_schema["required"] = [] - - return input_schema - - -def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: - """ - 验证 inputSchema 是否符合基本的 JSON Schema 规范 - - Args: - schema: 要验证的 schema 对象 - - Returns: - tuple[bool, str]: (是否有效, 错误消息或成功消息) - """ - if not isinstance(schema, dict): - return False, "Schema 必须是一个字典对象" - - if schema.get("type") != "object": - return False, "Schema 的 type 字段必须是 'object'" - - if "properties" not in schema: - return False, "Schema 必须包含 properties 字段" - - if not isinstance(schema.get("properties"), dict): - return False, "Schema 的 properties 字段必须是一个字典对象" - - # 验证 required 字段(如果存在) - if "required" in schema: - required = schema["required"] - if not isinstance(required, list): - return False, "Schema 的 required 字段必须是一个列表" - - # 验证所有 required 的字段都在 properties 中定义 - properties = schema["properties"] - for field in required: - if field not in properties: - return False, f"必填字段 '{field}' 未在 properties 中定义" - - # 验证 properties 中每个字段的定义 - for prop_name, prop_def in schema["properties"].items(): - if not isinstance(prop_def, dict): - return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" - - if "type" not in prop_def: - return False, f"属性 '{prop_name}' 必须包含 type 字段" - - return True, "Schema 验证通过" 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 deleted file mode 100644 index c042932..0000000 --- a/lzwcai_workflow_to_mcp/lzwcai_workflow_to_mcp/workflow-execution-api.md +++ /dev/null @@ -1,321 +0,0 @@ -# 工作流执行接口文档 - -## 概述 -工作流执行分为**同步执行**和**异步执行**两种模式,通过 `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 deleted file mode 100644 index 18c43bd..0000000 --- a/lzwcai_workflow_to_mcp/main.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Entry point for lzwcai-workflow-to-mcp -Runs the MCP server for workflow execution -""" - -import os - -if __name__ == "__main__": - # 设置环境变量 - os.environ["workflowId"] = "2037527155235225601" - os.environ["workflowExecuteKey"] = "wf_bd39a583670c42ceab48b3353bf2ba43" - os.environ["backendBaseUrl"] = "http://192.168.2.236:8088" - - # Import and run the actual MCP server - from lzwcai_workflow_to_mcp.main import main - main() diff --git a/lzwcai_workflow_to_mcp/pyproject.toml b/lzwcai_workflow_to_mcp/pyproject.toml deleted file mode 100644 index 8389973..0000000 --- a/lzwcai_workflow_to_mcp/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-workflow-to-mcp" -version = "0.1.9" -description = "MCP server for executing business SQL queries with dynamic tool generation" -readme = "README.md" -requires-python = ">=3.10" -license = {text = "MIT"} -authors = [ - {name = "lzwcai", email = "your-email@example.com"}, -] -keywords = ["mcp", "sql", "executor", "server"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] -dependencies = [ - "httpx>=0.28.1", - "mcp[cli]>=1.10.1", - "pypinyin>=0.53.0", -] - -[project.scripts] -lzwcai-workflow-to-mcp = "lzwcai_workflow_to_mcp.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_workflow_to_mcp"] - -[tool.hatch.build.targets.wheel.force-include] -"lzwcai_workflow_to_mcp/businessQueries.json" = "lzwcai_workflow_to_mcp/businessQueries.json"