From 3ea772c3be89bc319bd23a59fafadb6edb63cce2 Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Wed, 14 Jan 2026 11:56:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(mfg-data-agent):=20=E6=B7=BB=E5=8A=A0HTML?= =?UTF-8?q?=E5=8F=AF=E8=A7=86=E5=8C=96=E4=BB=AA=E8=A1=A8=E7=9B=98=E5=92=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增6个HTML可视化仪表盘组件用于数据展示 * 人效产值损耗三维模型仪表盘 * 指标趋势分析与拐点预警仪表盘 * 一页式决策简报仪表盘 * 订单延迟预警分析仪表盘 * 供应链风险预警仪表盘 * 工单执行进度与异常节点仪表盘 - 添加VSCode工作区配置文件 - 更新businessQueries.json业务查询配置 - 优化api_client.py API客户端实现 - 更新pyproject.toml项目依赖版本 - 重组SQL查询文件结构 - 删除v2版本冗余文档配置 - 添加v2版本技能清单文档 - 更新日志文件记录 --- .vscode/settings.json | 2 + .../html/EfficiencyOutputLossDashboard.html | 543 ++++++++++ .../MetricTrendAndTurningPointWarning.html | 791 ++++++++++++++ .../html/OnePageDecisionBrief.html | 567 ++++++++++ .../html/OrderDelayWarningAnalysis.html | 626 +++++++++++ .../html/SupplyChainRiskWarning.html | 680 ++++++++++++ .../WorkOrderProgressAndAnomalyNodes.html | 696 +++++++++++++ .../businessQueries.json | 10 +- .../logs/lzwcai_mcp_sqlexecutor.log | 184 ++++ .../logs/lzwcai_mcp_sqlexecutor_daily.log | 371 +------ ...zwcai_mcp_sqlexecutor_daily.log.2026-01-08 | 0 ...zwcai_mcp_sqlexecutor_daily.log.2026-01-09 | 139 +++ .../logs/mcp_services.log | 49 + .../utils/api_client.py | 34 +- .../pyproject.toml | 2 +- .../{ => sql}/一页式决策简报.sql | 0 .../{ => sql}/人效产值损耗三维模型仪表盘.sql | 0 .../{ => sql}/供应链风险预警.sql | 0 .../{ => sql}/工单执行进度与异常节点.sql | 0 .../{ => sql}/指标趋势分析与拐点预警.sql | 0 .../{ => sql}/订单延迟预警分析.sql | 0 .../businessQueries.json | 6 +- .../logs/lzwcai_mcp_sqlexecutor.log | 969 +++++++----------- .../logs/lzwcai_mcp_sqlexecutor_daily.log | 445 +++++++- .../logs/lzwcai_mcp_sqlexecutor_error.log | 48 - .../logs/mcp_services.log | 332 +++--- .../文档.json | 62 -- .../pyproject.toml | 2 +- lzwcai_mcpskills_mfg_data_agentv2/技能清单.md | 10 + 29 files changed, 5255 insertions(+), 1313 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html create mode 100644 lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html create mode 100644 lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html create mode 100644 lzwcai_mcpskills_mfg_data_agent/html/OrderDelayWarningAnalysis.html create mode 100644 lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html create mode 100644 lzwcai_mcpskills_mfg_data_agent/html/WorkOrderProgressAndAnomalyNodes.html rename {lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2 => lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent}/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 (100%) create mode 100644 lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 rename lzwcai_mcpskills_mfg_data_agent/{ => sql}/一页式决策简报.sql (100%) rename lzwcai_mcpskills_mfg_data_agent/{ => sql}/人效产值损耗三维模型仪表盘.sql (100%) rename lzwcai_mcpskills_mfg_data_agent/{ => sql}/供应链风险预警.sql (100%) rename lzwcai_mcpskills_mfg_data_agent/{ => sql}/工单执行进度与异常节点.sql (100%) rename lzwcai_mcpskills_mfg_data_agent/{ => sql}/指标趋势分析与拐点预警.sql (100%) rename lzwcai_mcpskills_mfg_data_agent/{ => sql}/订单延迟预警分析.sql (100%) delete mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/技能清单.md diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html b/lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html new file mode 100644 index 0000000..49013b8 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/html/EfficiencyOutputLossDashboard.html @@ -0,0 +1,543 @@ + + + + + + 人效-产值-损耗三维模型仪表盘 + + + + +
+ + + diff --git a/lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html b/lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html new file mode 100644 index 0000000..4667d07 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/html/MetricTrendAndTurningPointWarning.html @@ -0,0 +1,791 @@ + + + + + + 指标趋势分析与拐点预警 + + + + + + + + +
+ + diff --git a/lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html b/lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html new file mode 100644 index 0000000..6ea2528 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/html/OnePageDecisionBrief.html @@ -0,0 +1,567 @@ + + + + + + 一页式决策简报 + + + + +
+ + + \ 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 new file mode 100644 index 0000000..0fa2b22 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/html/OrderDelayWarningAnalysis.html @@ -0,0 +1,626 @@ + + + + + + 订单延迟预警分析 + + + + + + + + +
+ + diff --git a/lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html b/lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html new file mode 100644 index 0000000..3aa9caa --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/html/SupplyChainRiskWarning.html @@ -0,0 +1,680 @@ + + + + + + 供应链风险预警 + + + + + + + + + + + + +
+ + diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json index 18f9008..5bf8a91 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/businessQueries.json @@ -12,7 +12,7 @@ "businessName": "WorkOrderProgressAndAnomalyNodes", "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", "datasourceId": "19", - "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "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": {} }, { @@ -20,7 +20,7 @@ "businessName": "SupplyChainRiskWarning", "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", "datasourceId": "19", - "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", + "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": {} }, { @@ -28,7 +28,7 @@ "businessName": "EfficiencyOutputLossDashboard", "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", "datasourceId": "19", - "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC", + "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": {} }, { @@ -36,7 +36,7 @@ "businessName": "OnePageDecisionBrief", "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", "datasourceId": "19", - "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "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": {} }, { @@ -44,7 +44,7 @@ "businessName": "MetricTrendAndTurningPointWarning", "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", "datasourceId": "19", - "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "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": {} } ] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log index 83e454e..e9ed7b4 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor.log @@ -484,3 +484,187 @@ 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调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log index b3fb093..4466214 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -1,362 +1,45 @@ -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] - 请求参数: { +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 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", + "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-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] - 请求参数: { +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", + "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-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] - 请求参数: { +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", + "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-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-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_agentv2/lzwcai_mcpskills_mfg_data_agentv2/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 similarity index 100% rename from lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 rename to lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 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 new file mode 100644 index 0000000..0976602 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-09 @@ -0,0 +1,139 @@ +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/mcp_services.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log index c00ee3f..1360088 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log @@ -157,3 +157,52 @@ 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调用成功 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 index cf49a0a..279d800 100644 --- 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 @@ -128,7 +128,7 @@ class DataSourceAPIClient: Exception: 请求失败时抛出 """ try: - url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema" + url = f"{self.base_url}/datasource/sqlExecutionLog/testBatchSqlWithSchema" # 构建请求头(包含Content-Type) headers = self._get_headers() @@ -154,22 +154,36 @@ class DataSourceAPIClient: logger.info(f"测试SQL API调用成功") logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") - # 处理返回数据结构: {code, data: {errorMessage, data}, msg} + # 处理返回数据结构: {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 是一个数组,每个元素包含 errorMessage 和 data + data_list = result.get("data", []) - # 返回 data.data - return inner_data.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}" diff --git a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml b/lzwcai_mcpskills_mfg_data_agent/pyproject.toml index d6139e9..038d392 100644 --- a/lzwcai_mcpskills_mfg_data_agent/pyproject.toml +++ b/lzwcai_mcpskills_mfg_data_agent/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-mcpskills-mfg-data-agent" -version = "0.1.1" +version = "0.1.2" description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" readme = "README.md" requires-python = ">=3.13" diff --git a/lzwcai_mcpskills_mfg_data_agent/一页式决策简报.sql b/lzwcai_mcpskills_mfg_data_agent/sql/一页式决策简报.sql similarity index 100% rename from lzwcai_mcpskills_mfg_data_agent/一页式决策简报.sql rename to lzwcai_mcpskills_mfg_data_agent/sql/一页式决策简报.sql diff --git a/lzwcai_mcpskills_mfg_data_agent/人效产值损耗三维模型仪表盘.sql b/lzwcai_mcpskills_mfg_data_agent/sql/人效产值损耗三维模型仪表盘.sql similarity index 100% rename from lzwcai_mcpskills_mfg_data_agent/人效产值损耗三维模型仪表盘.sql rename to lzwcai_mcpskills_mfg_data_agent/sql/人效产值损耗三维模型仪表盘.sql diff --git a/lzwcai_mcpskills_mfg_data_agent/供应链风险预警.sql b/lzwcai_mcpskills_mfg_data_agent/sql/供应链风险预警.sql similarity index 100% rename from lzwcai_mcpskills_mfg_data_agent/供应链风险预警.sql rename to lzwcai_mcpskills_mfg_data_agent/sql/供应链风险预警.sql diff --git a/lzwcai_mcpskills_mfg_data_agent/工单执行进度与异常节点.sql b/lzwcai_mcpskills_mfg_data_agent/sql/工单执行进度与异常节点.sql similarity index 100% rename from lzwcai_mcpskills_mfg_data_agent/工单执行进度与异常节点.sql rename to lzwcai_mcpskills_mfg_data_agent/sql/工单执行进度与异常节点.sql diff --git a/lzwcai_mcpskills_mfg_data_agent/指标趋势分析与拐点预警.sql b/lzwcai_mcpskills_mfg_data_agent/sql/指标趋势分析与拐点预警.sql similarity index 100% rename from lzwcai_mcpskills_mfg_data_agent/指标趋势分析与拐点预警.sql rename to lzwcai_mcpskills_mfg_data_agent/sql/指标趋势分析与拐点预警.sql diff --git a/lzwcai_mcpskills_mfg_data_agent/订单延迟预警分析.sql b/lzwcai_mcpskills_mfg_data_agent/sql/订单延迟预警分析.sql similarity index 100% rename from lzwcai_mcpskills_mfg_data_agent/订单延迟预警分析.sql rename to lzwcai_mcpskills_mfg_data_agent/sql/订单延迟预警分析.sql diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json index 0475227..7ea5f89 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json @@ -4,7 +4,7 @@ "businessName": "SupplierEvaluationAndSmartReplenishment", "businessDescription": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", "datasourceId": "19", - "sqlTemplate": "WITH mat_out AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM mat_out GROUP BY product_id ) SELECT p.product_code, p.product_name, p.uom_code, s.avg_daily_consume, s.std_daily_consume FROM stats s JOIN dim_product p ON p.product_id = s.product_id AND p.is_current = TRUE WHERE p.product_type = 'RAW' ; WITH lead_time AS ( SELECT po.supplier_id, (DATE(pr.doc_date_utc) - DATE(po.doc_date_utc)) AS lead_days, CASE WHEN po.delivery_date_utc IS NOT NULL AND pr.doc_date_utc IS NOT NULL THEN CASE WHEN DATE(pr.doc_date_utc) <= DATE(po.delivery_date_utc) THEN 1 ELSE 0 END ELSE NULL END AS is_ontime FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL ), quality AS ( SELECT supplier_id, SUM(COALESCE(pass_qty, 0)) AS pass_qty, SUM(COALESCE(fail_qty, 0)) AS fail_qty FROM fact_quality_inspection WHERE qc_type = 'INCOMING' AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY supplier_id ), agg AS ( SELECT supplier_id, AVG(lead_days) AS avg_lead_days, STDDEV_POP(lead_days) AS std_lead_days, AVG(is_ontime) AS ontime_rate FROM lead_time WHERE lead_days IS NOT NULL AND lead_days >= 0 GROUP BY supplier_id ) SELECT s.supplier_id, s.supplier_name, a.avg_lead_days, a.std_lead_days, a.ontime_rate, CASE WHEN (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) > 0 THEN COALESCE(q.pass_qty, 0) / (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) ELSE NULL END AS incoming_pass_rate FROM agg a LEFT JOIN dim_supplier s ON s.supplier_id = a.supplier_id AND s.is_current = TRUE LEFT JOIN quality q ON q.supplier_id = a.supplier_id ORDER BY s.supplier_name ; WITH params AS ( SELECT 1.65::numeric AS z, 7::int AS review_days ), consume_daily AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), consume_stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM consume_daily GROUP BY product_id ), lead_time AS ( SELECT pr.supplier_id, AVG((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS avg_lead_days, STDDEV_POP((DATE(pr.doc_date_utc) - DATE(po.doc_date_utc))) AS std_lead_days FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL GROUP BY pr.supplier_id ), latest_onhand_row AS ( SELECT m.product_id, m.warehouse_id, m.onhand_qty_after, ROW_NUMBER() OVER (PARTITION BY m.product_id, m.warehouse_id ORDER BY m.event_time_utc DESC, m.inventory_movement_id DESC) AS rn FROM fact_inventory_movement m WHERE m.product_id IS NOT NULL ), onhand AS ( SELECT product_id, SUM(COALESCE(onhand_qty_after, 0)) AS onhand_qty FROM latest_onhand_row WHERE rn = 1 GROUP BY product_id ), product_supplier AS ( SELECT p.product_id, p.product_code, p.product_name, b.supplier_name FROM dim_product p LEFT JOIN dim_bom b ON b.product_code = p.product_code AND b.is_current = TRUE WHERE p.is_current = TRUE AND p.product_type = 'RAW' ), supplier_id_map AS ( SELECT supplier_id, supplier_name FROM dim_supplier WHERE is_current = TRUE ), merged AS ( SELECT ps.product_id, ps.product_code, ps.product_name, sm.supplier_id, COALESCE(cs.avg_daily_consume, 0) AS avg_daily_consume, COALESCE(cs.std_daily_consume, 0) AS std_daily_consume, COALESCE(lt.avg_lead_days, 0) AS avg_lead_days, COALESCE(lt.std_lead_days, 0) AS std_lead_days, COALESCE(oh.onhand_qty, 0) AS onhand_qty FROM product_supplier ps LEFT JOIN supplier_id_map sm ON sm.supplier_name IS NOT DISTINCT FROM ps.supplier_name LEFT JOIN consume_stats cs ON cs.product_id = ps.product_id LEFT JOIN lead_time lt ON lt.supplier_id = sm.supplier_id LEFT JOIN onhand oh ON oh.product_id = ps.product_id ) SELECT product_code, product_name, onhand_qty, avg_daily_consume, std_daily_consume, avg_lead_days, (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS safety_stock_qty, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) AS reorder_point_qty, GREATEST( 0, (avg_daily_consume * (avg_lead_days + params.review_days)) + (params.z * std_daily_consume * SQRT(GREATEST(avg_lead_days, 0))) - onhand_qty ) AS suggest_order_qty, supplier_id FROM merged CROSS JOIN params ORDER BY suggest_order_qty DESC, product_code ;", + "sqlTemplate": "WITH mat_out AS ( SELECT product_id, DATE(event_time_utc) AS d, SUM(CASE WHEN doc_type IN ('OUT','OTHER_OUT') THEN ABS(COALESCE(movement_qty, 0)) ELSE 0 END) AS daily_consume FROM fact_inventory_movement WHERE event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '90 days' GROUP BY product_id, DATE(event_time_utc) ), stats AS ( SELECT product_id, AVG(daily_consume) AS avg_daily_consume, STDDEV_POP(daily_consume) AS std_daily_consume FROM mat_out GROUP BY product_id ) SELECT p.product_code, p.product_name, p.uom_code, s.avg_daily_consume, s.std_daily_consume FROM stats s JOIN dim_product p ON p.product_id = s.product_id AND p.is_current = TRUE WHERE p.product_type = '标准产品' ; WITH lead_time AS ( SELECT po.supplier_id, (DATE(pr.doc_date_utc) - DATE(po.doc_date_utc)) AS lead_days, CASE WHEN po.delivery_date_utc IS NOT NULL AND pr.doc_date_utc IS NOT NULL THEN CASE WHEN DATE(pr.doc_date_utc) <= DATE(po.delivery_date_utc) THEN 1 ELSE 0 END ELSE NULL END AS is_ontime FROM fact_purchase_order po JOIN fact_purchase_receipt pr ON pr.purchase_order_number = po.purchase_order_number WHERE po.doc_date_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND pr.doc_date_utc IS NOT NULL ), quality AS ( SELECT supplier_id, SUM(COALESCE(pass_qty, 0)) AS pass_qty, SUM(COALESCE(fail_qty, 0)) AS fail_qty FROM fact_quality_inspection WHERE qc_type = 'INCOMING' AND event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' GROUP BY supplier_id ), agg AS ( SELECT supplier_id, AVG(lead_days) AS avg_lead_days, STDDEV_POP(lead_days) AS std_lead_days, AVG(is_ontime) AS ontime_rate FROM lead_time WHERE lead_days IS NOT NULL AND lead_days >= 0 GROUP BY supplier_id ) SELECT s.supplier_id, s.supplier_name, a.avg_lead_days, a.std_lead_days, a.ontime_rate, CASE WHEN (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) > 0 THEN COALESCE(q.pass_qty, 0) / (COALESCE(q.pass_qty, 0) + COALESCE(q.fail_qty, 0)) ELSE NULL END AS incoming_pass_rate FROM agg a LEFT JOIN dim_supplier s ON s.supplier_id = a.supplier_id AND s.is_current = TRUE LEFT JOIN quality q ON q.supplier_id = a.supplier_id ORDER BY s.supplier_name ; 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": {} }, { @@ -36,7 +36,7 @@ "businessName": "SmartCostPredictionModel", "businessDescription": "智能成本预测模型:基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演。", "datasourceId": "19", - "sqlTemplate": "WITH raw_in AS ( SELECT m.product_id, SUM(COALESCE(m.movement_qty, 0)) AS in_qty, SUM(COALESCE(m.movement_qty, 0) * COALESCE(m.unit_price, 0)) AS in_amount FROM fact_inventory_movement m WHERE m.event_time_utc >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '180 days' AND COALESCE(m.movement_qty, 0) > 0 AND COALESCE(m.unit_price, 0) > 0 GROUP BY m.product_id ) SELECT p.product_code, p.product_name, CASE WHEN ri.in_qty > 0 THEN ri.in_amount / ri.in_qty ELSE NULL END AS base_unit_cost FROM dim_product p LEFT JOIN raw_in ri ON ri.product_id = p.product_id WHERE p.is_current = TRUE AND p.product_type = '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 ;", + "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": {} }, { @@ -44,7 +44,7 @@ "businessName": "HumanResourcesAnalytics", "businessDescription": "人力资源数据分析:提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析。", "datasourceId": "19", - "sqlTemplate": "WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ) SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, p.position, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department, p.position ORDER BY ym, p.department, p.position ; SELECT to_char(employment_day, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS hire_count FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL AND employment_day >= (CURRENT_DATE - INTERVAL '12 months') GROUP BY to_char(employment_day, 'YYYY-MM'), department, position ORDER BY ym, department, position ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ) SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department, position ORDER BY ym, department, position ; WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ), headcount AS ( SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' WHERE p.department LIKE '%产线%' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department ), ordered AS ( SELECT person_id, department, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person WHERE department LIKE '%产线%' ), exit_event AS ( SELECT person_id, department, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), exit_cnt AS ( SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department ), rate AS ( SELECT h.ym, h.department, COALESCE(e.exit_count, 0) AS exit_count, h.headcount_active, CASE WHEN h.headcount_active > 0 THEN COALESCE(e.exit_count, 0) / h.headcount_active ELSE NULL END AS monthly_turnover_rate FROM headcount h LEFT JOIN exit_cnt e ON e.ym = h.ym AND e.department IS NOT DISTINCT FROM h.department ), flag AS ( SELECT r.*, LAG(monthly_turnover_rate, 1) OVER (PARTITION BY department ORDER BY ym) AS r_m1, LAG(monthly_turnover_rate, 2) OVER (PARTITION BY department ORDER BY ym) AS r_m2 FROM rate r ) SELECT ym, department, exit_count, headcount_active, monthly_turnover_rate FROM flag WHERE monthly_turnover_rate IS NOT NULL AND r_m1 IS NOT NULL AND r_m2 IS NOT NULL AND monthly_turnover_rate > r_m1 AND r_m1 > r_m2 ORDER BY department, ym ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), hire_event AS ( SELECT department, position, employment_day AS hire_date, person_id FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL ), matched AS ( SELECT e.department, e.position, e.exit_time, ( SELECT MIN(h.hire_date) FROM hire_event h WHERE h.department IS NOT DISTINCT FROM e.department AND h.position IS NOT DISTINCT FROM e.position AND h.hire_date > DATE(e.exit_time) ) AS next_hire_date FROM exit_event e ) SELECT department, position, DATE(exit_time) AS exit_date, next_hire_date, (next_hire_date - DATE(exit_time)) AS vacancy_days FROM matched WHERE next_hire_date IS NOT NULL ORDER BY vacancy_days DESC ;", + "sqlTemplate": "WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ) SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, p.position, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department, p.position ORDER BY ym, p.department, p.position ; SELECT to_char(employment_day, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS hire_count FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL AND employment_day >= (CURRENT_DATE - INTERVAL '12 months') GROUP BY to_char(employment_day, 'YYYY-MM'), department, position ORDER BY ym, department, position ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ) SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, position, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department, position ORDER BY ym, department, position ; WITH months AS ( SELECT generate_series( date_trunc('month', CURRENT_DATE) - INTERVAL '11 months', date_trunc('month', CURRENT_DATE), INTERVAL '1 month' )::date AS month_start ), month_end AS ( SELECT month_start, (month_start + INTERVAL '1 month' - INTERVAL '1 day')::date AS month_end FROM months ), headcount AS ( SELECT to_char(me.month_start, 'YYYY-MM') AS ym, p.department, COUNT(DISTINCT p.person_id) AS headcount_active FROM month_end me JOIN dim_person p ON p.row_valid_from <= me.month_end AND p.row_valid_to > me.month_end AND p.status = 'ACTIVE' GROUP BY to_char(me.month_start, 'YYYY-MM'), p.department ), ordered AS ( SELECT person_id, department, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), exit_cnt AS ( SELECT to_char(exit_time, 'YYYY-MM') AS ym, department, COUNT(DISTINCT person_id) AS exit_count FROM exit_event WHERE exit_time >= (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') - INTERVAL '12 months' GROUP BY to_char(exit_time, 'YYYY-MM'), department ), rate AS ( SELECT h.ym, h.department, COALESCE(e.exit_count, 0) AS exit_count, h.headcount_active, CASE WHEN h.headcount_active > 0 THEN COALESCE(e.exit_count, 0)::numeric / h.headcount_active ELSE NULL END AS monthly_turnover_rate FROM headcount h LEFT JOIN exit_cnt e ON e.ym = h.ym AND e.department IS NOT DISTINCT FROM h.department ), flag AS ( SELECT r.*, LAG(monthly_turnover_rate, 1) OVER (PARTITION BY department ORDER BY ym) AS r_m1, LAG(monthly_turnover_rate, 2) OVER (PARTITION BY department ORDER BY ym) AS r_m2 FROM rate r ) SELECT ym, department, exit_count, headcount_active, monthly_turnover_rate FROM flag WHERE monthly_turnover_rate IS NOT NULL AND r_m1 IS NOT NULL AND r_m2 IS NOT NULL AND monthly_turnover_rate > r_m1 AND r_m1 > r_m2 ORDER BY department, ym ; WITH ordered AS ( SELECT person_id, department, position, status, row_valid_from, LAG(status) OVER (PARTITION BY person_id ORDER BY row_valid_from) AS prev_status FROM dim_person ), exit_event AS ( SELECT person_id, department, position, row_valid_from AS exit_time FROM ordered WHERE status = 'INACTIVE' AND COALESCE(prev_status, 'UNKNOWN') <> 'INACTIVE' ), hire_event AS ( SELECT department, position, employment_day AS hire_date, person_id FROM dim_person WHERE is_current = TRUE AND employment_day IS NOT NULL ), matched AS ( SELECT e.department, e.position, e.exit_time, ( SELECT MIN(h.hire_date) FROM hire_event h WHERE h.department IS NOT DISTINCT FROM e.department AND h.position IS NOT DISTINCT FROM e.position AND h.hire_date > DATE(e.exit_time) ) AS next_hire_date FROM exit_event e ) SELECT department, position, DATE(exit_time) AS exit_date, next_hire_date, (next_hire_date - DATE(exit_time)) AS vacancy_days FROM matched WHERE next_hire_date IS NOT NULL ORDER BY vacancy_days DESC ;", "parameters": {} } ] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log index 791a20d..2d05afb 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log @@ -1,551 +1,27 @@ -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 18:27:37 - 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:27:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 18:27:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 18:27:39 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:27:39 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment -2026-01-09 18:27:39 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:27:39 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-09 18:27:39 - 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:27:40 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:168] - 接口业务错误: Multiple ResultSets were returned by the query. -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:192] - 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. -2026-01-09 18:27:40 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:27:46 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 18:27:46 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +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客户洞察。", @@ -553,29 +29,374 @@ Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned "parameters": {}, "testParams": {} } -2026-01-09 18:27:46 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:168] - 接口业务错误: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:192] - 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. +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_daily.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log index dde9a60..2d05afb 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -1,27 +1,102 @@ -2026-01-09 18:27:37 - 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:27:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 18:27:38 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 18:27:39 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-09 18:27:39 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment -2026-01-09 18:27:39 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:27:39 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema -2026-01-09 18:27:39 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:139] - 请求参数: { +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": "供应商评估与智能补货:基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量。", @@ -29,29 +104,299 @@ "parameters": {}, "testParams": {} } -2026-01-09 18:27:40 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:168] - 接口业务错误: Multiple ResultSets were returned by the query. -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:192] - 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. -2026-01-09 18:27:40 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. +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 index cb14ef8..e69de29 100644 --- 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 @@ -1,48 +0,0 @@ -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:168] - 接口业务错误: Multiple ResultSets were returned by the query. -2026-01-09 18:27:40 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:192] - 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. -2026-01-09 18:27:40 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:168] - 接口业务错误: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - lzwcai_mcpskills_mfg_data_agentv2.utils.api_client - ERROR - [api_client.py:192] - 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log index 0bdf0b1..f967584 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log @@ -1,215 +1,117 @@ -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 18:27:37 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:313] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:316] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:324] - ============================================================ -2026-01-09 18:27:37 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 -2026-01-09 18:27:38 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 -2026-01-09 18:27:39 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment -2026-01-09 18:27:39 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:27:40 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -2026-01-09 18:27:46 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform -2026-01-09 18:27:46 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... -2026-01-09 18:27:46 - mcp_services - ERROR - [main.py:238] - 调用测试SQL API失败: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. -Traceback (most recent call last): - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 169, in test_sql_with_schema - raise Exception(error_msg) -Exception: Multiple ResultSets were returned by the query. - -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_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\main.py", line 226, in handle_call_tool - api_response = test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 251, in test_sql_with_schema - return default_client.test_sql_with_schema(request_data) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agentv2\lzwcai_mcpskills_mfg_data_agentv2\utils\api_client.py", line 193, in test_sql_with_schema - raise Exception(error_msg) -Exception: 处理测试SQL API响应时出错: Multiple ResultSets were returned by the query. +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调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json deleted file mode 100644 index af1779d..0000000 --- a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json +++ /dev/null @@ -1,62 +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 = '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": { - - } - }, - { - "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 ; 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": { - - } - }, - { - "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 ; 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": { - - } - }, - { - "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 ; 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": { - - } - }, - { - "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 = '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": { - - } - }, - { - "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 ; 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": { - - } - } -] \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml index ecfcf7e..85f9e02 100644 --- a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml +++ b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-mcpskills-mfg-data-agentv2" -version = "0.1.1" +version = "0.1.3" description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" readme = "README.md" requires-python = ">=3.13" diff --git a/lzwcai_mcpskills_mfg_data_agentv2/技能清单.md b/lzwcai_mcpskills_mfg_data_agentv2/技能清单.md new file mode 100644 index 0000000..61e84aa --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/技能清单.md @@ -0,0 +1,10 @@ +# 制造业数据智能分析技能清单 + +| 技能名称 | 编码 | 技能描述 | +|---------|------|---------| +| 供应商评估与智能补货 | SupplierEvaluationAndSmartReplenishment | 基于物料消耗、交付周期、到货准时率与来料质检结果,输出供应商绩效指标与安全库存/再订货点/建议补货量 | +| 销售BI智能分析平台 | SalesBIIntelligentAnalyticsPlatform | 面向订单、发货/退货、客户复购与毛利贡献等核心指标,提供多维汇总、趋势分析与TOP客户洞察 | +| 销售业绩智能统计系统 | SalesPerformanceIntelligentStatistics | 基于订单成交与合同归属,计算首触/跟进/协同的贡献度分摊,并据此估算推荐佣金与人员贡献排名 | +| 财务数据分析看板 | FinancialAnalyticsDashboard | 覆盖应收/应付月度趋势、现金流收支、逾期应收预警,以及销售毛利与毛利率的月度拆解 | +| 智能成本预测模型 | SmartCostPredictionModel | 基于历史入库单价估算原材料基准成本,结合BOM用量拆解标准成本结构,并支持原材涨跌价情景推演 | +| 人力资源数据分析 | HumanResourcesAnalytics | 提供月度在岗人数、招聘/离职趋势、产线部门离职率连续上升预警,以及岗位空缺天数分析 |