From 118f1561f33be654df0e32bd8b93b62e06d5592b Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Fri, 9 Jan 2026 18:35:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(mfg-data-agent):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=88=B6=E9=80=A0=E4=B8=9A=E6=95=B0=E6=8D=AE=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93v2=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增基于MCP协议的制造业数据智能体服务器,支持从JSON配置文件动态生成查询工具, 具备灵活配置、完整日志、中文支持等功能特性,专注于制造业数据分析与智能决策场景。 包含详细的README文档、依赖配置和使用示例。 --- lzwcai_mcpskills_mfg_data_agentv2/README.md | 138 +++ .../.gitignore | 10 + .../.python-version | 1 + .../README.md | 154 +++ .../__init__.py | 9 + .../businessQueries.json | 50 + .../logs/lzwcai_mcp_sqlexecutor.log | 581 +++++++++ .../logs/lzwcai_mcp_sqlexecutor_daily.log | 57 + ...zwcai_mcp_sqlexecutor_daily.log.2026-01-08 | 362 ++++++ .../logs/lzwcai_mcp_sqlexecutor_error.log | 48 + .../logs/mcp_services.log | 215 ++++ .../lzwcai_mcpskills_mfg_data_agentv2/main.py | 373 ++++++ .../pyproject.toml | 35 + .../lzwcai_mcpskills_mfg_data_agentv2/sql11 | 169 +++ .../utils/__init__.py | 25 + .../utils/api_client.py | 332 +++++ .../utils/env_config.py | 106 ++ .../utils/json_helper.py | 60 + .../utils/logger_config.py | 489 ++++++++ .../utils/name_helper.py | 41 + .../utils/schema_helper.py | 166 +++ .../lzwcai_mcpskills_mfg_data_agentv2/uv.lock | 497 ++++++++ .../文档.json | 62 + lzwcai_mcpskills_mfg_data_agentv2/main.py | 13 + .../manufacturing_data_model_v1.0.0.md | 1094 +++++++++++++++++ .../pyproject.toml | 35 + 26 files changed, 5122 insertions(+) create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/README.md create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_error.log create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/main.py create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md create mode 100644 lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml diff --git a/lzwcai_mcpskills_mfg_data_agentv2/README.md b/lzwcai_mcpskills_mfg_data_agentv2/README.md new file mode 100644 index 0000000..46ac7be --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/README.md @@ -0,0 +1,138 @@ +# lzwcai-mcpskills-mfg-data-agent (制造业数据智能体) + +一个基于 MCP (Model Context Protocol) 的制造业数据智能体服务器,支持从 JSON 配置文件动态生成查询工具。 + +## 功能特性 + +- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 +- 🔧 灵活配置:支持自定义业务查询和参数验证 +- 📝 完整日志:详细的操作日志记录 +- 🌐 中文支持:工具名称自动转换为拼音 +- 🏭 制造业场景:专注于制造业数据分析与智能决策 + +## 安装 + +### 使用 pip 安装 + +```bash +pip install lzwcai-mcpskills-mfg-data-agent +``` + +### 从源码安装 + +```bash +git clone +cd lzwcai_mcpskills_mfg_data_agent +pip install -e . +``` + +### 使用 uv 安装(推荐) + +```bash +uv pip install lzwcai-mcpskills-mfg-data-agent +``` + +## 使用方法 + +### 命令行启动 + +安装后,可以直接通过命令启动: + +```bash +lzwcai-mcpskills-mfg-data-agent +``` + +### 作为 Python 模块运行 + +```bash +python -m lzwcai_mcpskills_mfg_data_agent.main +``` + +### 配置到 MCP 客户端 + +在你的 MCP 客户端配置文件中添加: + +```json +{ + "mcpServers": { + "mfg-data-agent": { + "command": "lzwcai-mcpskills-mfg-data-agent" + } + } +} +``` + +## 配置说明 + +### businessQueries.json + +在 `businessQueries.json` 中定义你的业务查询: + +```json +[ + { + "id": "query-001", + "businessName": "生产订单查询", + "businessDescription": "根据订单ID查询生产订单信息", + "sqlTemplate": "SELECT * FROM production_orders WHERE order_id = {{orderId}}", + "parameters": { + "type": "object", + "required": ["orderId"], + "properties": { + "orderId": { + "type": "string", + "description": "生产订单的唯一标识符", + "examples": ["PO-2024-001"] + } + } + } + } +] +``` + +## 开发 + +### 依赖项 + +- Python >= 3.13 +- httpx >= 0.28.1 +- mcp[cli] >= 1.10.1 +- pypinyin >= 0.53.0 + +### 本地开发 + +```bash +# 克隆仓库 +git clone +cd lzwcai_mcpskills_mfg_data_agent + +# 安装开发依赖 +pip install -e . + +# 运行服务器 +python -m lzwcai_mcpskills_mfg_data_agent.main +``` + +## 构建与发布 + +### 使用 build 构建 + +```bash +pip install build +python -m build +``` + +### 发布到 PyPI + +```bash +pip install twine +twine upload dist/* +``` + +## 许可证 + +MIT License + +## 作者 + +lzwcai diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md new file mode 100644 index 0000000..0ec9671 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/README.md @@ -0,0 +1,154 @@ +# lzwcai-mcpskills-analyzeWorkOrder + +一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。 + +## 功能特性 + +- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具 +- 🔧 灵活配置:支持自定义业务查询和参数验证 +- 📝 完整日志:详细的操作日志记录(仅输出到文件,不干扰MCP通信) +- 🌐 中文支持:工具名称自动转换为拼音 + +## 安装 + +### 使用 pip 安装 + +```bash +pip install lzwcai-mcpskills-analyzeWorkOrder +``` + +### 从源码安装 + +```bash +git clone +cd lzwcai_mcp_sqlexecutor +pip install -e . +``` + +### 使用 uv 安装(推荐) + +```bash +uv pip install lzwcai-mcpskills-analyzeWorkOrder +``` + +## 使用方法 + +### 命令行启动 + +安装后,可以直接通过命令启动: + +```bash +lzwcai-mcpskills-analyzeWorkOrder +``` + +### 作为 Python 模块运行 + +```bash +python -m lzwcai_mcp_sqlexecutor.main +``` + +### 配置到 MCP 客户端 + +在你的 MCP 客户端配置文件中添加: + +```json +{ + "mcpServers": { + "lzwcai-sqlexecutor": { + "command": "lzwcai-mcpskills-analyzeWorkOrder" + } + } +} +``` + +## 配置说明 + +### businessQueries.json + +在 `businessQueries.json` 中定义你的业务查询: + +```json +[ + { + "id": "query-001", + "businessName": "用户订单查询", + "businessDescription": "根据用户ID查询订单信息", + "sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}", + "parameters": { + "type": "object", + "required": ["userId"], + "properties": { + "userId": { + "type": "integer", + "description": "用户的唯一标识符", + "examples": [10086] + } + } + } + } +] +``` + +## 开发 + +### 依赖项 + +- Python >= 3.13 +- httpx >= 0.28.1 +- mcp[cli] >= 1.10.1 +- pypinyin >= 0.53.0 + +### 本地开发 + +```bash +# 克隆仓库 +git clone +cd lzwcai_mcp_sqlexecutor + +# 安装开发依赖 +pip install -e . + +# 运行服务器 +python -m lzwcai_mcp_sqlexecutor.main +``` + +## 构建与发布 + +### 使用 build 构建 + +```bash +pip install build +python -m build +``` + +### 发布到 PyPI + +```bash +pip install twine +twine upload dist/* +``` + +## 常见问题 + +### MCP Inspector 显示 JSON 解析错误 + +如果在使用 MCP Inspector 测试时遇到 `SyntaxError: Unexpected non-whitespace character after JSON` 错误,这是因为: + +1. **原因**:MCP 协议使用 stdio(标准输入输出)进行 JSON-RPC 通信,任何输出到 stdout 的内容(如 print 语句或控制台日志)都会破坏 JSON 格式。 + +2. **解决方案**:本服务器已将所有日志输出配置为仅写入文件(位于 `logs/` 目录),不输出到控制台。日志文件包括: + - `lzwcai_mcp_sqlexecutor.log` - 主日志文件 + - `lzwcai_mcp_sqlexecutor_error.log` - 错误日志 + - `lzwcai_mcp_sqlexecutor_daily.log` - 按日期滚动的日志 + - `mcp_services.log` - MCP 服务专用日志 + +3. **查看日志**:如果需要调试,请查看 `logs/` 目录下的日志文件。 + +## 许可证 + +MIT License + +## 作者 + +lzwcai + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py new file mode 100644 index 0000000..790ee8d --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/__init__.py @@ -0,0 +1,9 @@ +""" +lzwcai-mcpskills-mfg-data-agentv2 - MCP server for manufacturing data intelligence +""" + +__version__ = "0.1.2" +__author__ = "lzwcai" + +__all__ = [] + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json new file mode 100644 index 0000000..0475227 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json @@ -0,0 +1,50 @@ +[ + { + "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/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 new file mode 100644 index 0000000..791a20d --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor.log @@ -0,0 +1,581 @@ +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] - 请求参数: { + "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: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. 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 new file mode 100644 index 0000000..dde9a60 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -0,0 +1,57 @@ +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. 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_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 new file mode 100644 index 0000000..b3fb093 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_daily.log.2026-01-08 @@ -0,0 +1,362 @@ +2026-01-08 00:15:16 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_analyzeOrder\lzwcai_mcpskills_analyzeOrder\logs +2026-01-08 00:15:16 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-08 00:15:16 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-08 00:15:17 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-08 00:15:17 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-08 00:15:17 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-08 00:15:17 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-08 00:15:17 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-08 00:15:17 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-08 00:15:19 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:15:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 00:15:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS \"订单编号\", c.customer_name AS \"客户名称\", so.order_date_utc::DATE AS \"订单日期\", so.deal_amount AS \"订单金额\", so.payment_status AS \"付款状态\", ROUND(gm.avg_production_days::NUMERIC, 1) AS \"历史平均生产周期(天)\", ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS \"历史平均物流延迟(天)\", COALESCE(lds.delay_count, 0)::INT AS \"历史延误次数\", ROUND(gm.defect_rate_pct::NUMERIC, 2) AS \"产品不良率%\", ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS \"报废率%\", gm.active_wo_count::INT AS \"进行中工单数\", gm.lagging_wo_count::INT AS \"滞后工单数\", ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS \"延迟概率%\", CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN '红色' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN '黄色' ELSE '绿色' END AS \"预警等级\", CASE WHEN gm.lagging_wo_count >= 2 THEN '生产严重滞后' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN '物流延误风险高' WHEN gm.avg_production_days > 15 THEN '生产周期过长' WHEN gm.defect_rate_pct > 10 THEN '质量问题突出' ELSE '正常' END AS \"主要风险因素\" FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY \"延迟概率%\" DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:15:19 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:15:19 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:15:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:15:27 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:15:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 00:15:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:15:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:15:27 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→待生产, STARTED→生产中, CLOSED→已完成),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN '待生产' WHEN 'STARTED' THEN '生产中' WHEN 'CLOSED' THEN '已完成' ELSE '未知' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN '进度严重滞后' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN '质量异常' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '报工停滞' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number AS \"工单号\", product_name AS \"产品名称\", product_category AS \"产品类别\", status_name AS \"状态\", planned_qty AS \"计划数量\", completed_qty AS \"完成数量\", remaining_qty AS \"剩余数量\", completion_rate AS \"完成率(%)\", worker_count AS \"参与人数\", ROUND(total_work_minutes / 60.0, 1) AS \"累计工时(小时)\", elapsed_hours AS \"已用时间(小时)\", COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS \"异常标记\", CASE WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' WHEN efficiency_anomaly IS NOT NULL THEN '低' ELSE '-' END AS \"风险等级\" FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:15:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:15:28 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:15:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:15:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:15:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 00:15:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:15:37 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:15:37 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT '经营决策简报' AS \"报告标题\", CURRENT_DATE AS \"报告日期\", ss.total_orders AS \"销售订单总数\", ROUND(ss.total_sales_amount, 2) AS \"销售总金额(元)\", ROUND(ss.avg_order_amount, 2) AS \"平均订单金额(元)\", ss.paid_orders AS \"已付款订单\", ss.partial_orders AS \"部分付款订单\", ss.unpaid_orders AS \"未付款订单\", ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS \"付款完成率(%)\", ROUND(ss.unpaid_amount, 2) AS \"待收款金额(元)\", ps.total_work_orders AS \"工单总数\", ps.closed_orders AS \"已完成工单\", ps.started_orders AS \"进行中工单\", ps.open_orders AS \"待开始工单\", ROUND(ps.total_planned_qty, 0) AS \"计划产量\", ROUND(ps.total_completed_qty, 0) AS \"完成产量\", ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS \"生产完成率(%)\", ls.worker_count AS \"活跃员工数\", ROUND(ls.total_work_minutes / 60.0, 1) AS \"总工时(小时)\", ROUND(ls.total_output_qty, 0) AS \"总产出量\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS \"人均产量\", ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS \"时均产量(件/小时)\", qs.inspection_count AS \"质检批次\", ROUND(qs.total_pass_qty, 0) AS \"合格数量\", ROUND(qs.total_fail_qty, 0) AS \"不合格数量\", ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS \"合格率(%)\", ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS \"不良率(%)\", scr.scrap_count AS \"报废记录数\", ar.receipt_count AS \"收款笔数\", ROUND(ar.total_receipt_amount, 2) AS \"收款总额(元)\", ap.payment_count AS \"付款笔数\", ROUND(ap.total_payment_amount, 2) AS \"付款总额(元)\", ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS \"净现金流(元)\", inv.invoice_count AS \"开票数量\", ROUND(inv.total_invoice_amount, 2) AS \"开票总额(元)\", sh.shipment_count AS \"发货单数\", ROUND(sh.total_shipment_amount, 2) AS \"发货总金额(元)\", pur.purchase_order_count AS \"采购订单数\", ret.return_count AS \"退货单数\", ROUND(ret.total_return_amount, 2) AS \"退货总金额(元)\", ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS \"退货率(%)\", CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN '优秀 - 各项指标健康' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN '良好 - 部分指标需关注' ELSE '预警 - 需要立即改善' END AS \"经营健康度\" FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:15:38 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:15:38 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:15:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:15:58 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:15:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-08 00:15:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:15:58 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:15:58 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "EfficiencyOutputLossDashboard", + "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department AS \"部门(产品类别)\", ls.worker_count AS \"人员数\", ROUND(ls.total_work_minutes / 60.0, 2) AS \"总工时(小时)\", ls.total_output_qty AS \"总产量\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS \"人均产量\", ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS \"时均产量(件/小时)\", ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS \"人效指数(产量/人/小时)\", ws.total_planned_qty AS \"计划产量\", ws.total_completed_qty AS \"完成产量\", ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS \"计划完成率(%)\", ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS \"估算产值(元)\", ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS \"人均产值(元)\", qs.total_pass_qty AS \"质检合格数\", qs.total_fail_qty AS \"质检不合格数\", ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS \"不良率(%)\", ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS \"生产损耗率(%)\", ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS \"综合绩效得分\", CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN '红色预警' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN '黄色预警' ELSE '正常' END AS \"状态预警\" FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY \"综合绩效得分\" DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:15:59 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:15:59 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:15:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:16:07 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:16:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-08 00:16:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplyChainRiskWarning", + "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN '高风险' WHEN delivery_risk_score + quality_risk_score >= 50 THEN '中风险' ELSE '低风险' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN '交期异常+质量问题' WHEN delivery_risk_score >= 50 THEN '交期异常' WHEN quality_risk_score >= 30 THEN '质量问题' ELSE '正常' END AS risk_pattern FROM supplier_risk) SELECT supplier_name AS \"供应商名称\", supplier_category AS \"供应商类别\", order_count AS \"订单数\", receipt_count AS \"收货数\", avg_delivery_days AS \"平均交期(天)\", max_delivery_days AS \"最长交期(天)\", delivery_volatility AS \"交期波动\", total_receipts AS \"收货批次\", return_count AS \"退货次数\", quality_rate AS \"合格率(%)\", delivery_risk_score AS \"交期风险分\", quality_risk_score AS \"质量风险分\", total_risk_score AS \"综合风险分\", risk_level AS \"风险等级\", risk_pattern AS \"风险模式\" FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:16:07 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:16:07 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:16:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:16:13 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:16:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 00:16:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→待生产, STARTED→生产中, CLOSED→已完成),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN '待生产' WHEN 'STARTED' THEN '生产中' WHEN 'CLOSED' THEN '已完成' ELSE '未知' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN '进度严重滞后' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN '质量异常' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '报工停滞' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number AS \"工单号\", product_name AS \"产品名称\", product_category AS \"产品类别\", status_name AS \"状态\", planned_qty AS \"计划数量\", completed_qty AS \"完成数量\", remaining_qty AS \"剩余数量\", completion_rate AS \"完成率(%)\", worker_count AS \"参与人数\", ROUND(total_work_minutes / 60.0, 1) AS \"累计工时(小时)\", elapsed_hours AS \"已用时间(小时)\", COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS \"异常标记\", CASE WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN '高' WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN '中' WHEN efficiency_anomaly IS NOT NULL THEN '低' ELSE '-' END AS \"风险等级\" FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:16:13 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:16:13 - lzwcai_mcpskills_analyzeOrder.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:16:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:38:55 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-08 00:38:55 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-08 00:38:55 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-08 00:38:57 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-08 00:38:57 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-08 00:38:57 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-08 00:38:57 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-08 00:38:57 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-08 00:38:57 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-08 00:38:59 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:38:59 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 00:38:59 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:38:59 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:38:59 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:38:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:52:34 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-08 00:52:34 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-08 00:52:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-08 00:52:36 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-08 00:52:36 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-08 00:52:36 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-08 00:52:36 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-08 00:52:36 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-08 00:52:36 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-08 00:52:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:52:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 00:52:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:52:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:52:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:52:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:53:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:53:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 00:53:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:53:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:53:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:53:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 00:56:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "parameters": {}, + "testParams": {} +} +2026-01-08 00:56:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭 +2026-01-08 10:00:42 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-08 10:00:42 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-08 10:00:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-08 10:02:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:22:23 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:22:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:22:24 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:57:20 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:05:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplyChainRiskWarning", + "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:05:48 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:05:48 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:13:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "EfficiencyOutputLossDashboard", + "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:13:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:13:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:34:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:34:56 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:34:56 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:59:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_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 new file mode 100644 index 0000000..cb14ef8 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/lzwcai_mcp_sqlexecutor_error.log @@ -0,0 +1,48 @@ +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 new file mode 100644 index 0000000..0bdf0b1 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/logs/mcp_services.log @@ -0,0 +1,215 @@ +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. diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py new file mode 100644 index 0000000..3a4efb3 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/main.py @@ -0,0 +1,373 @@ +from pathlib import Path +from typing import Any +import asyncio +import logging + +# 支持直接运行和模块导入两种方式 +try: + from .utils import load_json, generate_tool_name, generate_input_schema + from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema + from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config + from .utils.logger_config import logger_config +except ImportError: + from utils import load_json, generate_tool_name, generate_input_schema + from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema + from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config + from utils.logger_config import logger_config + +from mcp.server.models import InitializationOptions +from mcp.server import NotificationOptions, Server +import mcp.types as types + +# 初始化 MCP 专用日志器 +mcp_logger = logger_config.setup_mcp_logging() + +# ========== 数据源配置 ========== +# 数据源类型常量 +DATA_SOURCE_API = "api" # 仅使用API数据 +DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据 +DATA_SOURCE_BOTH = "both" # 合并本地和API数据 + +# 默认数据源(可修改) +DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL +# ================================ + + +def get_queries(): + """ + 获取业务查询配置 + + Returns: + list: 包含所有业务查询配置的列表 + """ + try: + # 获取当前文件所在目录 + current_dir = Path(__file__).parent + + # 构建 businessQueries.json 的路径 + json_path = current_dir / "businessQueries.json" + + mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}") + + # 使用 load_json 方法读取 JSON 文件 + queries = load_json(json_path) + + mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置") + + return queries + except Exception as e: + mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True) + raise + + +def generate_tool_schema_from_query(query: dict) -> types.Tool: + """ + 根据查询配置生成 MCP 工具模式 + + Args: + query: 单个查询配置字典 + + Returns: + types.Tool: MCP 工具对象 + """ + try: + # 获取参数定义并生成 inputSchema + parameters = query.get('parameters', {}) + input_schema = generate_input_schema(parameters) + + # 生成工具名称(格式: tool_拼音_id) + # tool_name = generate_tool_name(query['businessName'], query['id']) + tool_name = query['businessName'] + # 构建工具描述,包含业务名称和业务描述 + description = f"{query['businessName']}: {query['businessDescription']}" + + mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}") + + return types.Tool( + name=tool_name, + description=description, + inputSchema=input_schema + ) + except Exception as e: + mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True) + raise + + +# 创建 MCP 服务器实例 +server = Server("lzwcai-mcpskills-mfg-data-agentv2") + +# 缓存查询配置,避免重复加载 +_queries_cache = None + + +async def get_queries_cache(source: str = None): + """ + 获取或初始化查询配置缓存 + + Args: + source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE) + - "api": 仅使用API数据 + - "local": 仅使用本地JSON数据 + - "both": 合并本地和API数据 + + Returns: + 查询配置列表 + """ + global _queries_cache + if _queries_cache is None: + source = source or DEFAULT_DATA_SOURCE + mcp_logger.info(f"初始化查询配置(数据源: {source})...") + + if source == DATA_SOURCE_LOCAL: + _queries_cache = get_queries() + mcp_logger.info(f"本地配置: {len(_queries_cache)} 条") + + elif source == DATA_SOURCE_API: + try: + _queries_cache = await call_third_party_api() + mcp_logger.info(f"API配置: {len(_queries_cache)} 条") + mcp_logger.info(f"API配置数组: {_queries_cache}") + except Exception as e: + mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}") + _queries_cache = get_queries() + + else: # DATA_SOURCE_BOTH + local = get_queries() + try: + api = await call_third_party_api() + except Exception as e: + mcp_logger.warning(f"API获取失败: {e}") + api = [] + _queries_cache = local + api + mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})") + + return _queries_cache + + +@server.list_tools() +async def handle_list_tools() -> list[types.Tool]: + """ + 列出所有动态生成的 MCP 工具 + + Returns: + list[types.Tool]: 所有可用的工具列表 + """ + try: + mcp_logger.info("收到列出工具请求") + + queries = await get_queries_cache() + tools = [] + + for query in queries: + tool = generate_tool_schema_from_query(query) + tools.append(tool) + + mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具") + mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}") + + return tools + except Exception as e: + mcp_logger.error(f"列出工具失败: {e}", exc_info=True) + raise + + +@server.call_tool() +async def handle_call_tool( + name: str, + arguments: dict[str, Any] | None +) -> list[types.TextContent]: + """ + 处理工具调用请求 + + Args: + name: 工具名称 + arguments: 工具参数 + + Returns: + list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置) + """ + try: + mcp_logger.info(f"收到工具调用请求: {name}") + mcp_logger.debug(f"工具参数: {arguments}") + + # 获取查询配置缓存 + queries = await get_queries_cache() + + # 根据工具名称查找对应的 item(接口配置) + tool_item = None + for query in queries: + # tool_name = generate_tool_name(query['businessName'], query['id']) + tool_name = query['businessName'] + if tool_name == name: + tool_item = query + break + + # 构建返回结果 + import json + + if tool_item: + request_data = { + "datasourceId": get_datasource_id(), + "businessName": tool_item.get("businessName"), + "businessDescription": tool_item.get("businessDescription"), + "sqlTemplate": tool_item.get("sqlTemplate"), + "parameters": tool_item.get("parameters"), + "testParams": arguments or {} + } + + # 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data + if arguments and arguments.get("targetDatabaseName"): + request_data["targetDatabaseName"] = arguments["targetDatabaseName"] + mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}") + + # 调用测试SQL API + try: + mcp_logger.info("正在调用测试SQL API...") + api_response = test_sql_with_schema(request_data) + mcp_logger.info("测试SQL API调用成功") + + # 返回包含 data 字段的结果 + result = { + "success": True, + "data": api_response + } + result_text = json.dumps(result, ensure_ascii=False, indent=2) + + except Exception as e: + error_msg = f"调用测试SQL API失败: {str(e)}" + mcp_logger.error(error_msg, exc_info=True) + result = { + "success": False, + "error": error_msg, + "data": None + } + result_text = json.dumps(result, ensure_ascii=False, indent=2) + else: + error_msg = f"未找到工具 {name} 对应的配置" + result = { + "success": False, + "error": error_msg, + "data": None + } + result_text = json.dumps(result, ensure_ascii=False, indent=2) + + mcp_logger.debug(f"工具调用结果: {result_text}") + + return [ + types.TextContent( + type="text", + text=result_text + ) + ] + except Exception as e: + error_msg = f"工具调用失败: {name}, 错误: {e}" + mcp_logger.error(error_msg, exc_info=True) + return [ + types.TextContent( + type="text", + text=f"错误: {error_msg}" + ) + ] + + +async def call_third_party_api(skill_id: str = None) -> list: + """ + 调用第三方API获取技能信息并返回处理后的数据 + + Args: + skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178) + + Returns: + 处理后的查询配置列表(businessQueries格式) + + Example: + queries = await call_third_party_api() + # 返回: [{"id": "...", "businessName": "...", ...}, ...] + """ + try: + # 如果没有传入 skill_id,则从环境变量读取 + if skill_id is None: + skill_id = get_skill_id() + + mcp_logger.info(f"调用第三方API,skill_id: {skill_id}") + + # 获取原始数据 + raw_result = get_skill_by_id(skill_id) + + mcp_logger.info(f"成功{raw_result}") + + # 处理并返回 + processed_queries = process_skill_response(raw_result) + + mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据") + return processed_queries + + except Exception as e: + mcp_logger.error(f"API调用失败: {e}", exc_info=True) + raise + + +async def async_main(): + """MCP 服务器异步主函数""" + try: + mcp_logger.info("=" * 60) + mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder") + mcp_logger.info("版本: 0.1.0") + mcp_logger.info("=" * 60) + + # 输出环境配置信息 + env_config = get_env_config() + mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}") + mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}") + mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}") + mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}") + mcp_logger.info("=" * 60) + + from mcp.server.stdio import stdio_server + + async with stdio_server() as (read_stream, write_stream): + mcp_logger.info("MCP 服务器已启动,等待客户端连接...") + + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="lzwcai-mcpskills-analyzeOrder", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + mcp_logger.info("MCP 服务器已关闭") + + except Exception as e: + mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True) + raise + + +def main(): + """入口点函数(用于 console_scripts)""" + try: + # 初始化系统日志 + # MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信 + logger_config.setup_logging( + app_name="lzwcai_mcp_sqlexecutor", + log_level=logging.INFO, + console_output=False # 禁用控制台输出 + ) + + mcp_logger.info("开始运行 MCP SQL Executor 服务器") + asyncio.run(async_main()) + + except KeyboardInterrupt: + mcp_logger.info("收到中断信号,正在关闭服务器...") + except Exception as e: + mcp_logger.error(f"程序运行失败: {e}", exc_info=True) + raise + + +if __name__ == "__main__": + main() diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml new file mode 100644 index 0000000..ecfcf7e --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "lzwcai-mcpskills-mfg-data-agentv2" +version = "0.1.1" +description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" +readme = "README.md" +requires-python = ">=3.13" +license = {text = "MIT"} +authors = [ + {name = "lzwcai", email = "your-email@example.com"}, +] +keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "httpx>=0.28.1", + "mcp[cli]>=1.10.1", + "pypinyin>=0.53.0", +] + +[project.scripts] +lzwcai-mcpskills-mfg-data-agentv2 = "lzwcai_mcpskills_mfg_data_agentv2.main:main" + +[tool.hatch.build.targets.wheel] +packages = ["lzwcai_mcpskills_mfg_data_agentv2"] + +[tool.hatch.build.targets.wheel.force-include] +"lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 new file mode 100644 index 0000000..39b7319 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/sql11 @@ -0,0 +1,169 @@ +-- ===================================================== +-- 交付风险预测:延迟概率与红/黄/绿预警等级 +-- 基于历史订单的生产周期、物流延误、设备故障等特征 +-- ===================================================== + +WITH +-- 1. 全局生产特征(汇总所有工单) +global_production AS ( + SELECT + COUNT(*) AS total_wo_count, + AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate, + SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count, + SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count + FROM fact_work_order +), + +-- 2. 全局质检特征 +global_quality AS ( + SELECT + COUNT(*) AS total_inspection_count, + SUM(COALESCE(pass_qty, 0)) AS total_pass_qty, + SUM(COALESCE(fail_qty, 0)) AS total_fail_qty, + CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0 + THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) + ELSE 1 END AS qc_pass_rate + FROM fact_quality_inspection +), + +-- 3. 全局工序不良特征 +global_operation AS ( + SELECT + COUNT(*) AS total_task_count, + SUM(COALESCE(good_qty, 0)) AS total_good_qty, + SUM(COALESCE(bad_qty, 0)) AS total_bad_qty, + CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0 + THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) + ELSE 0 END AS operation_defect_rate + FROM fact_operation_task +), + +-- 4. 客户级别发货统计 +customer_shipment AS ( + SELECT + customer_id, + COUNT(*) AS shipment_count, + SUM(COALESCE(amount, 0)) AS total_shipment_amount + FROM fact_sales_shipment + GROUP BY customer_id +), + +-- 5. 客户级别退货统计 +customer_return AS ( + SELECT + customer_id, + COUNT(*) AS return_count, + SUM(COALESCE(amount, 0)) AS total_return_amount + FROM fact_sales_return + GROUP BY customer_id +), + +-- 6. 订单风险评估 +order_risk AS ( + SELECT + so.sales_order_id, + so.sales_order_number, + c.customer_name, + so.order_date_utc, + so.deal_amount, + so.payment_status, + + -- 全局生产指标 + gp.avg_completion_rate AS production_completion_rate, + gp.pending_wo_count, + gp.total_wo_count AS work_order_count, + + -- 全局质检指标 + gq.qc_pass_rate, + gq.total_fail_qty, + + -- 全局工序指标 + go.operation_defect_rate, + + -- 客户级别指标 + COALESCE(cs.shipment_count, 0) AS shipment_count, + COALESCE(cr.return_count, 0) AS return_count, + CASE WHEN COALESCE(cs.shipment_count, 0) > 0 + THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count + ELSE 0 END AS return_rate + + FROM fact_sales_order so + LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true + CROSS JOIN global_production gp + CROSS JOIN global_quality gq + CROSS JOIN global_operation go + LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id + LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id +) + +-- 7. 最终输出 +SELECT + sales_order_id, + sales_order_number, + customer_name, + order_date_utc, + deal_amount, + payment_status, + + -- 风险特征 + work_order_count, + ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate, + pending_wo_count, + ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate, + ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate, + return_count, + ROUND(return_rate::NUMERIC, 4) AS return_rate, + + -- 延迟概率 + ROUND(( + CASE WHEN production_completion_rate < 0.3 THEN 0.30 + WHEN production_completion_rate < 0.5 THEN 0.20 + WHEN production_completion_rate < 0.8 THEN 0.10 + ELSE 0 END + + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 + WHEN qc_pass_rate < 0.9 THEN 0.15 + WHEN qc_pass_rate < 0.95 THEN 0.08 + ELSE 0 END + + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 + WHEN operation_defect_rate > 0.05 THEN 0.12 + WHEN operation_defect_rate > 0.02 THEN 0.05 + ELSE 0 END + + CASE WHEN return_rate > 0.1 THEN 0.15 + WHEN return_rate > 0.05 THEN 0.08 + WHEN return_rate > 0.02 THEN 0.03 + ELSE 0 END + + CASE WHEN payment_status = 'UNPAID' THEN 0.10 + WHEN payment_status = 'PARTIAL' THEN 0.05 + ELSE 0 END + )::NUMERIC, 2) AS delay_probability, + + -- 红/黄/绿预警 + CASE + WHEN ( + CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END + + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END + + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END + + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END + + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END + ) >= 0.50 THEN 'RED' + WHEN ( + CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END + + CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END + + CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END + + CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END + + CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END + ) >= 0.25 THEN 'YELLOW' + ELSE 'GREEN' + END AS risk_level, + + -- 风险原因 + CONCAT_WS(' | ', + CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END, + CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END, + CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END, + CASE WHEN return_rate > 0.05 THEN '历史退货率高' END, + CASE WHEN payment_status = 'UNPAID' THEN '未付款' END + ) AS risk_reasons + +FROM order_risk +ORDER BY delay_probability DESC, deal_amount DESC; diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py new file mode 100644 index 0000000..3df2b44 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/__init__.py @@ -0,0 +1,25 @@ +"""Utils package for lzwcai_mcp_sqlexecutor""" + +from .json_helper import load_json +from .name_helper import generate_tool_name +from .schema_helper import generate_input_schema, validate_input_schema +from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema +from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable + +__all__ = [ + 'load_json', + 'generate_tool_name', + 'generate_input_schema', + 'validate_input_schema', + 'DataSourceAPIClient', + 'get_skill_by_id', + 'process_skill_response', + 'test_sql_with_schema', + 'get_database_id', + 'get_datasource_id', + 'get_skill_id', + 'get_backend_base_url', + 'get_env_config', + 'set_env_variable' +] + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py new file mode 100644 index 0000000..279d800 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/api_client.py @@ -0,0 +1,332 @@ +""" +第三方API调用客户端 +用于调用外部数据源接口 +""" + +import httpx +import logging +import json +from typing import Dict, Any, Optional, List + +# 支持直接运行和模块导入两种方式 +try: + from .env_config import get_backend_base_url +except ImportError: + from env_config import get_backend_base_url + +# 获取日志记录器 +logger = logging.getLogger(__name__) + + +class DataSourceAPIClient: + """数据源API客户端""" + + def __init__( + self, + base_url: Optional[str] = None, + token: Optional[str] = None + ): + """ + 初始化API客户端 + + Args: + base_url: API基础URL(默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088) + token: 认证令牌(Bearer Token) + """ + # 如果没有传入 base_url,则从环境变量读取 + if base_url is None: + base_url = get_backend_base_url() + + self.base_url = base_url.rstrip('/') + self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw" + self.client = httpx.Client(timeout=30.0) + + def _get_headers(self) -> Dict[str, str]: + """ + 获取请求头 + + Returns: + 请求头字典 + """ + return { + 'Authorization': f'Bearer {self.token}', + } + + def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]: + """ + 根据技能ID获取技能信息 + + Args: + skill_id: 技能ID + + Returns: + API响应数据 + + Raises: + Exception: 请求失败时抛出 + """ + try: + url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}" + + logger.info(f"正在调用API: {url}") + logger.info(f"请求参数 - skill_id: {skill_id}") + + response = self.client.get( + url, + headers=self._get_headers() + ) + + # 检查HTTP状态码 + response.raise_for_status() + + # 解析JSON响应 + data = response.json() + + logger.info(f"API调用成功: {url}") + logger.debug(f"响应数据: {data}") + + return data + + except httpx.TimeoutException: + error_msg = f"API请求超时: {url}" + logger.error(error_msg) + raise Exception(error_msg) + + except httpx.HTTPStatusError as e: + error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}" + logger.error(error_msg) + logger.error(f"错误响应: {e.response.text}") + raise Exception(error_msg) + + except httpx.RequestError as e: + error_msg = f"API请求异常: {url}, 错误: {str(e)}" + logger.error(error_msg) + raise Exception(error_msg) + + except Exception as e: + error_msg = f"处理API响应时出错: {str(e)}" + logger.error(error_msg, exc_info=True) + raise Exception(error_msg) + + def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]: + """ + 测试SQL语句并返回执行结果 + + Args: + request_data: 请求数据,包含以下字段: + - datasourceId: 数据源ID + - businessName: 业务名称 + - businessDescription: 业务描述 + - sqlTemplate: SQL模板 + - parameters: 参数定义 + - testParams: 测试参数 + + Returns: + API响应数据 + + Raises: + Exception: 请求失败时抛出 + """ + try: + url = f"{self.base_url}/datasource/sqlExecutionLog/testBatchSqlWithSchema" + + # 构建请求头(包含Content-Type) + headers = self._get_headers() + headers['Content-Type'] = 'application/json' + headers['Accept'] = '*/*' + + logger.info(f"正在调用测试SQL API: {url}") + logger.info(f"请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}") + + # 发送POST请求 + response = self.client.post( + url, + headers=headers, + json=request_data + ) + + # 检查HTTP状态码 + response.raise_for_status() + + # 解析JSON响应 + result = response.json() + + logger.info(f"测试SQL API调用成功") + logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") + + # 处理返回数据结构: {code, data: [{errorMessage, data}, ...], msg} + # 检查外层 code + if result.get("code") != 200: + error_msg = result.get("msg", "接口返回错误") + logger.error(f"接口返回错误: code={result.get('code')}, msg={error_msg}") + raise Exception(error_msg) + + # data 是一个数组,每个元素包含 errorMessage 和 data + data_list = result.get("data", []) + + # 如果不是数组,兼容旧格式 + if isinstance(data_list, dict): + if data_list.get("errorMessage"): + error_msg = data_list.get("errorMessage") + logger.error(f"接口业务错误: {error_msg}") + raise Exception(error_msg) + return data_list.get("data") + + # 处理数组格式,提取每个 item 的 data 并组合 + combined_data = [] + for item in data_list: + if item.get("errorMessage"): + error_msg = item.get("errorMessage") + logger.error(f"接口业务错误: {error_msg}") + raise Exception(error_msg) + item_data = item.get("data", []) + combined_data.append(item_data) + + # 返回组合后的数据 [data1, data2, ...] + return combined_data + + except httpx.TimeoutException: + error_msg = f"测试SQL API请求超时: {url}" + logger.error(error_msg) + raise Exception(error_msg) + + except httpx.HTTPStatusError as e: + error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}" + logger.error(error_msg) + logger.error(f"错误响应: {e.response.text}") + raise Exception(error_msg) + + except httpx.RequestError as e: + error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}" + logger.error(error_msg) + raise Exception(error_msg) + + except Exception as e: + error_msg = f"处理测试SQL API响应时出错: {str(e)}" + logger.error(error_msg, exc_info=True) + raise Exception(error_msg) + + def close(self): + """关闭HTTP客户端""" + self.client.close() + + +# 创建默认客户端实例 +default_client = DataSourceAPIClient() + + +def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: + """ + 便捷函数:根据技能ID获取技能信息 + + Args: + skill_id: 技能ID + base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) + token: 认证令牌(可选,使用默认值) + + Returns: + API响应数据 + """ + if base_url or token: + client = DataSourceAPIClient( + base_url=base_url, + token=token + ) + return client.get_skill_by_id(skill_id) + else: + return default_client.get_skill_by_id(skill_id) + + +def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: + """ + 便捷函数:测试SQL语句并返回执行结果 + + Args: + request_data: 请求数据,包含以下字段: + - datasourceId: 数据源ID + - businessName: 业务名称 + - businessDescription: 业务描述 + - sqlTemplate: SQL模板 + - parameters: 参数定义 + - testParams: 测试参数 + base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取) + token: 认证令牌(可选,使用默认值) + + Returns: + API响应数据 + """ + if base_url or token: + client = DataSourceAPIClient( + base_url=base_url, + token=token + ) + return client.test_sql_with_schema(request_data) + else: + return default_client.test_sql_with_schema(request_data) + + +def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + 处理API响应数据,映射为businessQueries格式 + + Args: + response: API原始响应数据 + + Returns: + 处理后的查询配置列表 + """ + try: + # 提取data数组 + data_list = response.get("data", []) + + # 默认的员工ID参数schema + default_employee_schema = { + "type": "object", + "required": ["employeeId"], + "properties": { + "employeeId": { + "type": "number", + "description": "员工ID,用于标识员工的唯一数字标识符", + "examples": [1001, 2002] + } + } + } + + # 映射每个skill为businessQuery格式 + queries = [] + for skill in data_list: + # 解析sqlParams字符串为JSON对象 + sql_params = json.loads(skill.get("sqlParams", "{}")) + + # 判断sqlParams是否为空对象 + is_empty_params = ( + not sql_params.get("properties") or + len(sql_params.get("properties", {})) == 0 + ) and ( + not sql_params.get("required") or + len(sql_params.get("required", [])) == 0 + ) + + # 如果是空对象,使用默认的员工ID参数 + if is_empty_params: + logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空,使用默认员工ID参数") + sql_params = default_employee_schema + + # 映射字段 + query = { + "id": skill.get("id"), + "businessName": skill.get("name"), + "businessDescription": skill.get("description"), + "sqlTemplate": skill.get("sqlTemplate"), + "parameters": sql_params, + "datasourceId": skill.get("datasourceId") + } + queries.append(query) + + logger.info(f"成功处理 {len(queries)} 条技能数据") + return queries + + except Exception as e: + logger.error(f"处理API响应数据失败: {e}", exc_info=True) + raise + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py new file mode 100644 index 0000000..585b0a6 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/env_config.py @@ -0,0 +1,106 @@ +"""环境变量配置模块""" + +import os +from typing import Optional + + +def get_database_id(default: str = "29") -> str: + """ + 获取数据库ID环境变量 + + Args: + default: 默认值(默认为 "29") + + Returns: + str: 数据库ID + + Environment Variables: + databaseId: 数据库ID + """ + return os.environ.get("databaseId", default) + + +def get_datasource_id(default: str = "") -> str: + """ + 获取数据源ID环境变量 + + Args: + default: 默认值(默认为 "") + + Returns: + str: 数据源ID + + Environment Variables: + datasourceId: 数据源ID + """ + return os.environ.get("datasourceId", default) + + +def get_skill_id(default: str = "") -> str: + """ + 获取技能ID环境变量 + + Args: + default: 默认值(默认为 "") + + Returns: + str: 技能ID + + Environment Variables: + skillId: 技能ID + """ + return os.environ.get("skillId", default) + + +def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: + """ + 获取后端API基础URL环境变量 + + Args: + default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086") + + Returns: + str: 后端API基础URL + + Environment Variables: + backendBaseUrl: 后端API基础URL + """ + return os.environ.get("backendBaseUrl", default) + + +def get_env_config() -> dict: + """ + 获取所有环境配置 + + Returns: + dict: 包含所有配置的字典 + + Example: + config = get_env_config() + print(config['database_id']) # 输出: "29" + print(config['datasource_id']) # 输出: "" + print(config['skill_id']) # 输出: "" + print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086" + """ + return { + "database_id": get_database_id(), + "datasource_id": get_datasource_id(), + "skill_id": get_skill_id(), + "backend_base_url": get_backend_base_url() + } + + +def set_env_variable(key: str, value: str) -> None: + """ + 设置环境变量(仅在当前进程中有效) + + Args: + key: 环境变量名 + value: 环境变量值 + + Example: + set_env_variable("databaseId", "30") + set_env_variable("skillId", "1234567890") + """ + os.environ[key] = value + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py new file mode 100644 index 0000000..1a6fc95 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/json_helper.py @@ -0,0 +1,60 @@ +"""JSON 文件读取工具""" + +import json +from pathlib import Path +from typing import Any, Union + + +def load_json(json_path: Union[str, Path]) -> Any: + """ + 读取 JSON 文件并返回其内容 + + Args: + json_path: JSON 文件的路径(支持字符串或 Path 对象) + + Returns: + JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型) + + Raises: + FileNotFoundError: 当文件不存在时 + json.JSONDecodeError: 当 JSON 格式无效时 + Exception: 其他读取错误 + + Example: + >>> data = load_json('config.json') + >>> print(data) + {'key': 'value'} + + >>> data = load_json(Path('data/users.json')) + >>> print(data) + [{'id': 1, 'name': 'Alice'}] + """ + try: + # 转换为 Path 对象 + path = Path(json_path) + + # 检查文件是否存在 + if not path.exists(): + raise FileNotFoundError(f"JSON 文件不存在: {json_path}") + + # 检查是否为文件 + if not path.is_file(): + raise ValueError(f"路径不是一个文件: {json_path}") + + # 读取并解析 JSON 文件 + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + + return data + + except json.JSONDecodeError as e: + raise json.JSONDecodeError( + f"JSON 格式错误: {e.msg}", + e.doc, + e.pos + ) + except FileNotFoundError: + raise + except Exception as e: + raise Exception(f"读取 JSON 文件时发生错误: {str(e)}") + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py new file mode 100644 index 0000000..290964f --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/logger_config.py @@ -0,0 +1,489 @@ +# -*- coding: utf-8 -*- +""" +统一日志配置模块 +提供系统级别的日志配置和管理 +""" + +import os +import sys +import logging +from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler +from datetime import datetime +from pathlib import Path + + +class LoggerConfig: + """日志配置管理类""" + + def __init__(self, logs_dir: str = None): + """初始化日志配置 + + Args: + logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹 + """ + # 确定日志目录 + if logs_dir: + self.logs_dir = Path(logs_dir) + else: + # 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录) + project_root = Path(__file__).parent.parent + self.logs_dir = project_root / "logs" + + # 创建日志目录 + self.logs_dir.mkdir(exist_ok=True) + + # 日志格式 + self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s' + self.date_format = '%Y-%m-%d %H:%M:%S' + + # 从环境变量获取日志级别,默认为INFO + self.log_level = self._get_log_level_from_env() + + # 是否已初始化 + self._initialized = False + + def _get_log_level_from_env(self) -> int: + """从环境变量获取日志级别 + + Returns: + int: 日志级别 + """ + log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper() + + # 日志级别映射 + level_mapping = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'WARN': logging.WARNING, # 兼容性别名 + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL, + 'FATAL': logging.CRITICAL # 兼容性别名 + } + + return level_mapping.get(log_level_str, logging.INFO) + + def setup_logging(self, + app_name: str = "lzwcai_mcp_sqlexecutor", + log_level: int = logging.INFO, + max_file_size: int = 10 * 1024 * 1024, # 10MB + backup_count: int = 5, + console_output: bool = True) -> logging.Logger: + """设置系统日志配置 + + Args: + app_name: 应用名称,用于日志文件命名 + log_level: 日志级别 + max_file_size: 单个日志文件最大大小(字节) + backup_count: 保留的备份文件数量 + console_output: 是否输出到控制台 + + Returns: + logging.Logger: 配置好的根日志器 + """ + if self._initialized: + return logging.getLogger() + + # 设置根日志器 + root_logger = logging.getLogger() + root_logger.setLevel(log_level) + + # 清除现有的处理器 + for handler in root_logger.handlers[:]: + root_logger.removeHandler(handler) + + # 创建格式化器 + formatter = logging.Formatter(self.log_format, self.date_format) + + # 1. 主日志文件 - 按大小滚动 + main_log_file = self.logs_dir / f"{app_name}.log" + file_handler = RotatingFileHandler( + main_log_file, + maxBytes=max_file_size, + backupCount=backup_count, + encoding='utf-8' + ) + file_handler.setLevel(log_level) + file_handler.setFormatter(formatter) + root_logger.addHandler(file_handler) + + # 2. 错误日志文件 - 只记录ERROR及以上级别 + error_log_file = self.logs_dir / f"{app_name}_error.log" + error_handler = RotatingFileHandler( + error_log_file, + maxBytes=max_file_size, + backupCount=backup_count, + encoding='utf-8' + ) + error_handler.setLevel(logging.ERROR) + error_handler.setFormatter(formatter) + root_logger.addHandler(error_handler) + + # 3. 按日期滚动的日志文件 + daily_log_file = self.logs_dir / f"{app_name}_daily.log" + daily_handler = TimedRotatingFileHandler( + daily_log_file, + when='midnight', + interval=1, + backupCount=30, # 保留30天 + encoding='utf-8' + ) + daily_handler.setLevel(log_level) + daily_handler.setFormatter(formatter) + daily_handler.suffix = "%Y-%m-%d" + root_logger.addHandler(daily_handler) + + # 4. 控制台输出 + # 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信 + if console_output: + console_handler = logging.StreamHandler(sys.stderr) + console_handler.setLevel(log_level) + console_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + self.date_format + ) + console_handler.setFormatter(console_formatter) + root_logger.addHandler(console_handler) + + self._initialized = True + + # 记录初始化信息 + root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}") + root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}") + + return root_logger + + def get_module_logger(self, module_name: str) -> logging.Logger: + """获取模块专用日志器 + + Args: + module_name: 模块名称 + + Returns: + logging.Logger: 模块日志器 + """ + return logging.getLogger(module_name) + + def create_component_logger(self, + component_name: str, + log_file: str = None, + log_level: int = None) -> logging.Logger: + """为特定组件创建独立的日志器 + + Args: + component_name: 组件名称 + log_file: 独立日志文件名(可选) + log_level: 日志级别(可选) + + Returns: + logging.Logger: 组件日志器 + """ + logger = logging.getLogger(component_name) + + if log_file: + # 为组件创建独立的日志文件 + component_log_file = self.logs_dir / log_file + handler = RotatingFileHandler( + component_log_file, + maxBytes=5 * 1024 * 1024, # 5MB + backupCount=3, + encoding='utf-8' + ) + + formatter = logging.Formatter(self.log_format, self.date_format) + handler.setFormatter(formatter) + + if log_level: + handler.setLevel(log_level) + + logger.addHandler(handler) + logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}") + + return logger + + def setup_mqtt_logging(self) -> logging.Logger: + """设置MQTT专用日志 + + Returns: + logging.Logger: MQTT日志器 + """ + return self.create_component_logger( + "mqtt_communication", + "mqtt_communication.log", + logging.DEBUG + ) + + def setup_mcp_logging(self) -> logging.Logger: + """设置MCP专用日志 + + Returns: + logging.Logger: MCP日志器 + """ + return self.create_component_logger( + "mcp_services", + "mcp_services.log", + logging.DEBUG + ) + + def setup_api_logging(self) -> logging.Logger: + """设置API专用日志 + + Returns: + logging.Logger: API日志器 + """ + return self.create_component_logger( + "api_requests", + "api_requests.log", + logging.INFO + ) + + def get_logs_info(self) -> dict: + """获取日志系统信息 + + Returns: + dict: 日志系统信息 + """ + log_files = [] + if self.logs_dir.exists(): + for log_file in self.logs_dir.glob("*.log*"): + stat = log_file.stat() + log_files.append({ + "name": log_file.name, + "size": stat.st_size, + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat() + }) + + return { + "logs_directory": str(self.logs_dir), + "initialized": self._initialized, + "log_files": log_files, + "total_files": len(log_files) + } + + def cleanup_old_logs(self, days: int = 30): + """清理旧日志文件 + + Args: + days: 保留天数 + """ + if not self.logs_dir.exists(): + return + + from datetime import timedelta + cutoff_time = datetime.now() - timedelta(days=days) + + cleaned_files = [] + for log_file in self.logs_dir.glob("*.log*"): + if log_file.stat().st_mtime < cutoff_time.timestamp(): + try: + log_file.unlink() + cleaned_files.append(log_file.name) + except Exception as e: + logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}") + + if cleaned_files: + logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}") + + def set_log_level(self, level: int, logger_name: str = None): + """动态调整日志级别 + + Args: + level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) + logger_name: 指定日志器名称,None表示调整根日志器 + """ + if logger_name: + logger = logging.getLogger(logger_name) + else: + logger = logging.getLogger() + + old_level = logger.level + logger.setLevel(level) + + # 同时调整所有处理器的级别 + for handler in logger.handlers: + if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr): + # 不调整控制台处理器的级别,保持原有设置 + handler.setLevel(level) + + level_name = logging.getLevelName(level) + old_level_name = logging.getLevelName(old_level) + target = logger_name or "根日志器" + + logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}") + + def set_temporary_log_level(self, level: int, logger_name: str = None): + """临时调整日志级别(会保存原始级别用于恢复) + + Args: + level: 临时日志级别 + logger_name: 指定日志器名称,None表示调整根日志器 + """ + if not hasattr(self, '_original_levels'): + self._original_levels = {} + + target_name = logger_name or 'root' + + if logger_name: + logger = logging.getLogger(logger_name) + else: + logger = logging.getLogger() + + # 保存原始级别 + if target_name not in self._original_levels: + self._original_levels[target_name] = logger.level + + # 设置新级别 + self.set_log_level(level, logger_name) + + level_name = logging.getLevelName(level) + target = logger_name or "根日志器" + logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)") + + def restore_log_level(self, logger_name: str = None): + """恢复日志级别到调整前的状态 + + Args: + logger_name: 指定日志器名称,None表示恢复根日志器 + """ + if not hasattr(self, '_original_levels'): + logging.warning("没有找到保存的原始日志级别") + return + + target_name = logger_name or 'root' + + if target_name not in self._original_levels: + logging.warning(f"没有找到 {target_name} 的原始日志级别") + return + + original_level = self._original_levels[target_name] + self.set_log_level(original_level, logger_name) + + # 清除保存的级别 + del self._original_levels[target_name] + + target = logger_name or "根日志器" + level_name = logging.getLevelName(original_level) + logging.info(f"已恢复日志级别: {target} -> {level_name}") + + def get_current_log_levels(self) -> dict: + """获取当前所有日志器的级别信息 + + Returns: + dict: 日志器级别信息 + """ + levels_info = {} + + # 根日志器 + root_logger = logging.getLogger() + levels_info['root'] = { + 'level': root_logger.level, + 'level_name': logging.getLevelName(root_logger.level), + 'handlers_count': len(root_logger.handlers) + } + + # 其他已创建的日志器 + for name, logger in logging.Logger.manager.loggerDict.items(): + if isinstance(logger, logging.Logger): + levels_info[name] = { + 'level': logger.level, + 'level_name': logging.getLevelName(logger.level), + 'handlers_count': len(logger.handlers) + } + + return levels_info + + +# 全局日志配置实例 +logger_config = LoggerConfig() + + +def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor", + log_level: int = logging.INFO) -> logging.Logger: + """系统日志初始化快捷函数 + + Args: + app_name: 应用名称 + log_level: 日志级别 + + Returns: + logging.Logger: 根日志器 + """ + return logger_config.setup_logging(app_name, log_level) + + +def get_logger(name: str) -> logging.Logger: + """获取日志器的快捷函数 + + Args: + name: 日志器名称 + + Returns: + logging.Logger: 日志器实例 + """ + return logger_config.get_module_logger(name) + + +def set_log_level(level: int, logger_name: str = None): + """动态调整日志级别的快捷函数 + + Args: + level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) + logger_name: 指定日志器名称,None表示调整根日志器 + + Examples: + # 调整根日志器为DEBUG级别 + set_log_level(logging.DEBUG) + + # 调整特定模块日志器为WARNING级别 + set_log_level(logging.WARNING, "agent_ontology.core") + """ + logger_config.set_log_level(level, logger_name) + + +def set_temporary_log_level(level: int, logger_name: str = None): + """临时调整日志级别的快捷函数 + + Args: + level: 临时日志级别 + logger_name: 指定日志器名称,None表示调整根日志器 + + Examples: + # 临时调整为DEBUG级别进行调试 + set_temporary_log_level(logging.DEBUG) + # ... 进行调试 ... + # 恢复原始级别 + restore_log_level() + """ + logger_config.set_temporary_log_level(level, logger_name) + + +def restore_log_level(logger_name: str = None): + """恢复日志级别的快捷函数 + + Args: + logger_name: 指定日志器名称,None表示恢复根日志器 + """ + logger_config.restore_log_level(logger_name) + + +def get_current_log_levels() -> dict: + """获取当前日志级别信息的快捷函数 + + Returns: + dict: 日志器级别信息 + + Examples: + levels = get_current_log_levels() + print(f"根日志器级别: {levels['root']['level_name']}") + """ + return logger_config.get_current_log_levels() + + +# 便捷的日志级别常量 +class LogLevel: + """日志级别常量类""" + DEBUG = logging.DEBUG + INFO = logging.INFO + WARNING = logging.WARNING + ERROR = logging.ERROR + CRITICAL = logging.CRITICAL \ No newline at end of file diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py new file mode 100644 index 0000000..d66cf32 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/name_helper.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +""" +名称生成工具模块 +""" + +from pypinyin import lazy_pinyin, Style +import logging + +logger = logging.getLogger(__name__) + + +def generate_tool_name(business_name: str, tool_id: str) -> str: + """ + 根据业务名称和ID生成工具名称 + 格式: tool_拼音_id + + Args: + business_name: 业务名称(中文) + tool_id: 工具ID + + Returns: + str: 格式化的工具名称 + """ + try: + # 将中文转换为拼音(无音调,小写) + pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL) + # 拼接拼音 + pinyin_str = ''.join(pinyin_list) + + # 将 ID 中的 '-' 替换为 '_' + formatted_id = tool_id.replace('-', '_') + + # 组合成最终的工具名称 + tool_name = f"tool_{pinyin_str}_{formatted_id}" + + return tool_name + except Exception as e: + logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True) + # 降级处理:如果拼音转换失败,使用 ID + return f"tool_{tool_id.replace('-', '_')}" + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py new file mode 100644 index 0000000..626559f --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/utils/schema_helper.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +""" +Schema 生成工具模块 +""" + +from typing import Any, Dict, List + + +def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]: + """ + 从查询配置的参数定义生成 MCP 工具的 inputSchema + + 此函数会保留完整的 JSON Schema 信息,包括: + - type: Schema 类型(通常是 "object") + - required: 必填字段列表 + - properties: 属性定义(包括每个属性的 type, description, format, examples 等) + - description: Schema 的整体描述(如果有) + - 以及其他任何 JSON Schema 标准字段 + + 此函数还会自动添加以下字段(如果原始 parameters 中未定义): + - targetDatabaseName: 目标数据库名称(非必填,默认为空字符串) + + Args: + parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象 + + Returns: + Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象 + + Example: + >>> params = { + ... "type": "object", + ... "required": ["userId", "startTime"], + ... "properties": { + ... "userId": { + ... "type": "integer", + ... "description": "用户的唯一标识符", + ... "examples": [10086] + ... }, + ... "startTime": { + ... "type": "string", + ... "format": "date-time", + ... "description": "查询的起始时间", + ... "examples": ["2023-01-01 00:00:00"] + ... } + ... } + ... } + >>> schema = generate_input_schema(params) + >>> # schema 将包含所有原始信息,包括 format 和 examples + >>> # 同时会自动添加 targetDatabaseName 字段 + """ + # 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用 + # 但确保至少包含 type 和 properties + if not parameters: + # 如果 parameters 为空,返回一个空的 object schema + return { + "type": "object", + "properties": {}, + "required": [] + } + + # 检查 parameters 是否已经是完整的 JSON Schema 格式 + # 完整格式应该有 "type": "object" 和 "properties" 字段 + if "type" in parameters and parameters.get("type") == "object" and "properties" in parameters: + # 已经是完整的 JSON Schema 格式,直接使用 + input_schema = dict(parameters) + else: + # parameters 是简化格式(直接是参数定义),需要转换为 JSON Schema 格式 + # 例如: {"workOrderNumber": {"type": "string", "description": "...", "required": true}} + properties = {} + required = [] + + for param_name, param_def in parameters.items(): + if isinstance(param_def, dict): + # 提取 required 标记 + if param_def.get("required", False): + required.append(param_name) + + # 复制参数定义,但移除 required 字段(它应该在顶层的 required 数组中) + prop_def = {k: v for k, v in param_def.items() if k != "required"} + properties[param_name] = prop_def + + input_schema = { + "type": "object", + "properties": properties, + "required": required + } + + # 确保必需的字段存在 + if "type" not in input_schema: + input_schema["type"] = "object" + + if "properties" not in input_schema: + input_schema["properties"] = {} + + if "required" not in input_schema: + input_schema["required"] = [] + + # 添加 targetDatabaseName 字段(如果不存在) + if "targetDatabaseName" not in input_schema["properties"]: + input_schema["properties"]["targetDatabaseName"] = { + "type": "string", + "description": "目标数据库名称", + "default": "" + } + + # 保留所有其他字段,如 description, examples, format 等 + # JSON Schema 标准支持的字段都会被保留: + # - additionalProperties + # - patternProperties + # - minProperties / maxProperties + # - dependencies + # - 等等 + + return input_schema + + +def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: + """ + 验证 inputSchema 是否符合基本的 JSON Schema 规范 + + Args: + schema: 要验证的 schema 对象 + + Returns: + tuple[bool, str]: (是否有效, 错误消息或成功消息) + + Example: + >>> schema = {"type": "object", "properties": {"id": {"type": "string"}}} + >>> is_valid, msg = validate_input_schema(schema) + >>> print(is_valid, msg) + True, "Schema 验证通过" + """ + if not isinstance(schema, dict): + return False, "Schema 必须是一个字典对象" + + if schema.get("type") != "object": + return False, "Schema 的 type 字段必须是 'object'" + + if "properties" not in schema: + return False, "Schema 必须包含 properties 字段" + + if not isinstance(schema.get("properties"), dict): + return False, "Schema 的 properties 字段必须是一个字典对象" + + # 验证 required 字段(如果存在) + if "required" in schema: + required = schema["required"] + if not isinstance(required, list): + return False, "Schema 的 required 字段必须是一个列表" + + # 验证所有 required 的字段都在 properties 中定义 + properties = schema["properties"] + for field in required: + if field not in properties: + return False, f"必填字段 '{field}' 未在 properties 中定义" + + # 验证 properties 中每个字段的定义 + for prop_name, prop_def in schema["properties"].items(): + if not isinstance(prop_def, dict): + return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" + + if "type" not in prop_def: + return False, f"属性 '{prop_name}' 必须包含 type 字段" + + return True, "Schema 验证通过" + diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock new file mode 100644 index 0000000..017ec10 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/uv.lock @@ -0,0 +1,497 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" }, +] + +[[package]] +name = "lzwcai-mcpskills-analyzeWorkOrder" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "httpx" }, + { name = "mcp", extra = ["cli"] }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.28.1" }, + { name = "mcp", extras = ["cli"], specifier = ">=1.10.1" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" }, +] + +[[package]] +name = "mcp" +version = "1.10.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" }, +] + +[package.optional-dependencies] +cli = [ + { name = "python-dotenv" }, + { name = "typer" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, +] + +[[package]] +name = "pydantic" +version = "2.12.2" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.11.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" }, + { url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" }, +] + +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" }, +] + +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" }, +] + +[[package]] +name = "typer" +version = "0.19.2" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" }, +] + +[[package]] +name = "uvicorn" +version = "0.37.0" +source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" } +wheels = [ + { url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" }, +] diff --git a/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json new file mode 100644 index 0000000..af1779d --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/lzwcai_mcpskills_mfg_data_agentv2/文档.json @@ -0,0 +1,62 @@ +[ + { + "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/main.py b/lzwcai_mcpskills_mfg_data_agentv2/main.py new file mode 100644 index 0000000..f0da2de --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/main.py @@ -0,0 +1,13 @@ +""" +Entry point for lzwcai-mcpskills-mfg-data-agentv2 (制造业数据智能体) +Runs the MCP server for manufacturing data intelligence +""" +import os + +os.environ["databaseId"] = "19" +os.environ["datasourceId"] = "19" +os.environ["backendBaseUrl"] = "http://192.168.11.24:8088" +if __name__ == "__main__": + # Import and run the actual MCP server + from lzwcai_mcpskills_mfg_data_agentv2.main import main + main() diff --git a/lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md b/lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md new file mode 100644 index 0000000..02b9ec4 --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/manufacturing_data_model_v1.0.0.md @@ -0,0 +1,1094 @@ +# manufacturing_data_model_v1.0.0 + +## 需求概述 + +本模型基于 `module/` 目录下 52 份业务导出 Excel,面向制造企业端到端经营与生产分析(线索→商机→合同→订单→采购/生产→入出库→质检→开票/收付款/核销→资金流→分佣),抽象形成可扩展的分析型数据模型。 + +设计目标: + +- 兼容 ISA-95 / IEC 62264:区分 Level 3(生产执行)与 Level 4(经营管理)数据边界 +- 分析型优先:星型模型 + 事实星座(Conformed Dimensions) +- 主数据一致性:维度表按 SCD Type 2 管理(`RowValidFrom/RowValidTo/IsCurrent`) +- 事务可追溯:事实表以 `EventTimeUTC` 作为事件时间分区/水印字段 + +范围说明: + +- 当前 Excel 以“编号/名称”作为主要业务键(例如 `产品编码`、`合同编号`、`工单编号`)。分析层统一引入代理主键 `{Entity}Id`。 +- 多数单据缺少“行明细”粒度(例如销售订单/采购订单仅见单头字段),模型保留单头事实为主,后续如补充明细可平滑扩展。 + +## 术语表 + +| 术语 | 含义 | +|---|---| +| 业务键(Business Key) | 来自源系统的编码/编号/单号等唯一标识,如 `ProductCode`、`ContractNumber` | +| 代理键(Surrogate Key) | 分析层自增/雪花主键,如 `ProductId` | +| SCD Type 2 | 维度历史保留:通过 `RowValidFrom/RowValidTo/IsCurrent` 记录有效期 | +| 退化维度(Degenerate Dimension) | 直接保存在事实表中的单据号、状态等维度属性 | +| EventTimeUTC | 统一事件时间(UTC),作为事实表分区/增量水印字段 | + +## 主题域总览(ISA-95 视角) + +| 主题域 | ISA-95 层级 | 覆盖 Excel | +|---|---|---| +| 主数据(产品/BOM/工艺/资源) | L3/L4 共用 | 产品列表、物料清单、工艺路线、工序、仓库、供应商、客户、设备、质检原因 | +| 生产执行 | Level 3 | 工单、任务、报工、装配工单 | +| 质量 | Level 3 | 质检记录、质检不通过原因 | +| 库存与仓储作业 | Level 3 | 出入库明细、盘点、调拨、其他出入库、报废 | +| 销售与履约 | Level 4 | 销售报价、销售订单、销售出库、销售退货 | +| 采购与供应 | Level 4 | 采购申请、采购订单、采购入库、采购退货 | +| 财务与资金 | Level 4 | 发票、回款、收款/付款、预收/预付、核销、资金流向、资金转账、其他收支、分佣 | +| CRM(市场到合同) | Level 4 | 线索、商机、合同 | + +## 实体清单(维度/事实) + +维度(SCD2): + +- `DimPerson`(人员维度) +- `DimProduct`(产品维度) +- `DimCustomer`(客户维度) +- `DimSupplier`(供应商维度) +- `DimWarehouse`(仓库维度) +- `DimBom`(物料清单维度) +- `DimRouting`(工艺路线维度) +- `DimOperation`(工序维度) +- `DimEquipment`(设备维度) +- `DimQcReason`(质检不通过原因维度) +- `DimContract`(合同维度,可选:合同也可作为事实) + +事实(按 `EventTimeUTC` 分区): + +- `FactInventoryMovement`(出入库流水事实) +- `FactWorkOrder`(工单事实) +- `FactOperationTask`(工序任务事实) +- `FactLaborReport`(报工事实) +- `FactQualityInspection`(质检事实) +- `FactSalesOrder`(销售订单事实) +- `FactSalesShipment`(销售出库事实) +- `FactSalesReturn`(销售退货事实) +- `FactPurchaseRequest`(采购申请事实) +- `FactPurchaseOrder`(采购订单事实) +- `FactPurchaseReceipt`(采购入库事实) +- `FactPurchaseReturn`(采购退货事实) +- `FactInventoryTransfer`(库存调拨事实) +- `FactInventoryCount`(库存盘点事实) +- `FactScrap`(报废事实) +- `FactArReceipt`(应收收款事实) +- `FactApPayment`(应付付款事实) +- `FactInvoice`(开票申请事实) +- `FactWriteOff`(核销事实) +- `FactCashFlow`(资金流水事实) +- `FactCommission`(分佣事实) +- `FactLead`(线索事实) +- `FactOpportunity`(商机事实) +- `FactProductionPlan`(生产计划/装配工单汇总事实,可选) + +## ER 图(事实星座) + +```mermaid +erDiagram + DimProduct ||--o{ FactInventoryMovement : "by_product (关联产品)" + DimWarehouse ||--o{ FactInventoryMovement : "by_warehouse (关联仓库)" + DimBom ||--o{ FactInventoryMovement : "by_bom (关联BOM)" + DimPerson ||--o{ FactInventoryMovement : "operated_by (操作人)" + + DimProduct ||--o{ FactWorkOrder : "produces (生产产品)" + DimSupplier ||--o{ FactWorkOrder : "outsourced_to (委外供应商)" + DimRouting ||--o{ FactWorkOrder : "uses_routing (使用工艺)" + DimPerson ||--o{ FactWorkOrder : "managed_by (生产主管)" + + DimOperation ||--o{ FactOperationTask : "executes (执行工序)" + DimProduct ||--o{ FactOperationTask : "for_product (关联产品)" + DimPerson ||--o{ FactOperationTask : "worker (执行工人)" + + DimProduct ||--o{ FactLaborReport : "reports_on (报工产品)" + DimOperation ||--o{ FactLaborReport : "at_operation (报工工序)" + DimPerson ||--o{ FactLaborReport : "reported_by (报工人员)" + + DimQcReason ||--o{ FactQualityInspection : "fail_reason (不合格原因)" + DimProduct ||--o{ FactQualityInspection : "inspects (质检产品)" + DimPerson ||--o{ FactQualityInspection : "inspected_by (质检员)" + + DimCustomer ||--o{ FactSalesOrder : "buys (购买)" + DimPerson ||--o{ FactSalesOrder : "sold_by (销售员)" + DimCustomer ||--o{ FactSalesShipment : "receives (收货)" + DimPerson ||--o{ FactSalesShipment : "shipped_by (发货员)" + DimCustomer ||--o{ FactSalesReturn : "returns (退货)" + DimPerson ||--o{ FactSalesReturn : "handled_by (处理人)" + + DimSupplier ||--o{ FactPurchaseOrder : "supplies (供应)" + DimPerson ||--o{ FactPurchaseOrder : "purchased_by (采购员)" + DimSupplier ||--o{ FactPurchaseReceipt : "delivers (送货)" + DimPerson ||--o{ FactPurchaseReceipt : "received_by (收货员)" + DimSupplier ||--o{ FactPurchaseReturn : "takes_back (退回)" + + DimContract ||--o{ FactInvoice : "invoiced_for (开票合同)" + DimContract ||--o{ FactArReceipt : "cash_in (收款合同)" + DimContract ||--o{ FactApPayment : "cash_out (付款合同)" + DimContract ||--o{ FactWriteOff : "writeoff (核销合同)" + DimPerson ||--o{ FactContract : "owned_by (合同负责人)" + DimPerson ||--o{ FactLead : "owned_by (线索负责人)" + DimPerson ||--o{ FactOpportunity : "owned_by (商机负责人)" +``` + +## 业务数据流转全景图 + +本流程图展示了从商机线索到最终产品交付的端到端数据流转过程,以及各环节涉及的关键数据实体。 + +```mermaid +flowchart TD + %% 节点样式定义 + classDef process fill:#e1f5fe,stroke:#01579b,stroke-width:2px; + classDef decision fill:#fff9c4,stroke:#fbc02d,stroke-width:2px; + classDef table fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px,stroke-dasharray: 5 5; + + %% 1. CRM阶段 + subgraph Stage_CRM [CRM与销售阶段] + direction TB + Node_Lead[("线索处理
In: FactLead
Logic: 线索清洗/转化")]:::process + Node_Opp[("商机确认
In: FactLead
Out: FactOpportunity")]:::process + Decision_Win{商机赢单?}:::decision + Node_Contract[("合同签订
In: FactOpportunity
Out: DimContract")]:::process + Node_SO[("销售订单生成
In: DimContract
Out: FactSalesOrder")]:::process + + Table_Lead[("FactLead")]:::table + Table_Opp[("FactOpportunity")]:::table + Table_SO[("FactSalesOrder")]:::table + end + + %% 2. 计划阶段 + subgraph Stage_Plan [计划与排程阶段] + direction TB + Node_MPS[("主生产计划(MPS)
In: FactSalesOrder
Logic: 需求合并/产能平衡
Out: FactProductionPlan")]:::process + Node_MRP[("MRP运算/BOM展开
In: FactProductionPlan, DimBom
Logic: 净需求计算
Out: FactWorkOrder(Planned)")]:::process + Decision_MakeBuy{自制/外购?}:::decision + Node_PR[("采购申请生成
Out: FactPurchaseRequest")]:::process + + Table_Plan[("FactProductionPlan")]:::table + Table_Bom[("DimBom")]:::table + Table_Stock[("FactInventoryMovement
(当前库存)")]:::table + end + + %% 新增:3. 采购与供应阶段 + subgraph Stage_Procure [采购与供应阶段] + direction TB + Node_PO[("采购订单(PO)
In: FactPurchaseRequest
Out: FactPurchaseOrder")]:::process + Node_Receive[("收货与IQC
Logic: 验收/质检
Out: FactQualityInspection")]:::process + Decision_IQC{IQC合格?}:::decision + Node_Return[("采购退货
Out: FactPurchaseReturn")]:::process + Node_Mat_In[("原材料入库
Logic: 批次录入/更新库存
Out: FactPurchaseReceipt
FactInventoryMovement")]:::process + Node_AP[("应付挂账(AP)
Logic: 三单匹配
Out: FactApPayment")]:::process + + Table_PO[("FactPurchaseOrder")]:::table + Table_Receipt[("FactPurchaseReceipt")]:::table + end + + %% 4. 生产执行阶段 (原 Stage_Make) + subgraph Stage_Make [生产执行阶段] + direction TB + Node_WO_Release[("工单下达
In: FactWorkOrder(Planned)
Logic: 物料齐套/锁定资源
Out: FactWorkOrder(Released)")]:::process + Node_Task[("工序任务派工
In: FactWorkOrder, DimRouting
Out: FactOperationTask")]:::process + Node_Labor[("生产报工
In: FactOperationTask
Logic: 投入/产出/工时记录
Out: FactLaborReport")]:::process + + Table_WO[("FactWorkOrder")]:::table + Table_Task[("FactOperationTask")]:::table + Table_Labor[("FactLaborReport")]:::table + end + + %% 5. 质量与入库阶段 (原 Stage_QC) + subgraph Stage_QC [质量与仓储阶段] + direction TB + Node_QC[("成品检验(FQC)
In: FactLaborReport
Logic: 检验标准比对
Out: FactQualityInspection")]:::process + Decision_Pass{质检合格?}:::decision + Node_Scrap[("报废处理
Logic: 记录不良原因
Out: FactScrap")]:::process + Node_FG_In[("成品入库
In: FactWorkOrder(Closed)
Logic: 更新库存余额
Out: FactInventoryMovement(IN)")]:::process + + Table_QC[("FactQualityInspection")]:::table + Table_Inv[("FactInventoryMovement")]:::table + end + + %% 6. 交付阶段 (原 Stage_Deliver) + subgraph Stage_Deliver [交付阶段] + direction TB + Node_Ship[("销售发货
In: FactSalesOrder, FactInventoryMovement
Logic: 拣货/出库扣减
Out: FactSalesShipment")]:::process + Node_End((交付完成)) + + Table_Ship[("FactSalesShipment")]:::table + end + + %% 新增:7. 财务与资金阶段 + subgraph Stage_Finance [财务与资金阶段] + direction TB + %% AR Flow + Node_Invoice[("销售开票
In: FactSalesShipment
Out: FactInvoice")]:::process + Node_ArRec[("销售收款
Out: FactArReceipt")]:::process + Node_ArOff[("应收核销
Logic: 核销发票与收款
Out: FactWriteOff")]:::process + + %% AP Flow + Node_ApPay[("采购付款
Out: FactApPayment")]:::process + Node_ApOff[("应付核销
Logic: 核销应付与付款
Out: FactWriteOff")]:::process + + %% Cash Flow + Node_Cash[("资金流水
Logic: 账户变动记录
Out: FactCashFlow")]:::process + + Table_Inv[("FactInvoice")]:::table + Table_Ar[("FactArReceipt")]:::table + Table_Ap[("FactApPayment")]:::table + Table_Cash[("FactCashFlow")]:::table + end + + %% 关系连线 + Node_Lead --> Node_Opp + Node_Opp --> Decision_Win + Decision_Win -- Yes --> Node_Contract + Node_Contract --> Node_SO + Node_SO --> Node_MPS + + Node_MPS --> Node_MRP + Node_MRP --> Decision_MakeBuy + Decision_MakeBuy -- 外购 --> Node_PR + Decision_MakeBuy -- 自制 --> Node_WO_Release + + %% 采购流程连线 + Node_PR --> Node_PO + Node_PO --> Node_Receive + Node_Receive --> Decision_IQC + Decision_IQC -- No --> Node_Return + Decision_IQC -- Yes --> Node_Mat_In + Node_Mat_In --> Node_AP + Node_Mat_In --> Node_WO_Release + + Node_WO_Release --> Node_Task + Node_Task --> Node_Labor + Node_Labor --> Node_QC + + Node_QC --> Decision_Pass + Decision_Pass -- No --> Node_Scrap + Decision_Pass -- Yes --> Node_FG_In + + Node_FG_In --> Node_Ship + Node_Ship --> Node_End + + %% 财务流程连线 + Node_Ship --> Node_Invoice + Node_Invoice --> Node_ArRec + Node_ArRec --> Node_ArOff + + Node_AP --> Node_ApPay + Node_ApPay --> Node_ApOff + + Node_ArRec --> Node_Cash + Node_ApPay --> Node_Cash + + %% 数据表关联(虚线) + Table_Lead -.-> Node_Lead + Node_Opp -.-> Table_Opp + Node_Contract -.-> Table_SO + Table_SO -.-> Node_MPS + Node_MPS -.-> Table_Plan + Table_Plan -.-> Node_MRP + Table_Bom -.-> Node_MRP + Table_Stock -.-> Node_MRP + + Node_PR -.-> Table_PO + Node_PO -.-> Table_PO + Node_Mat_In -.-> Table_Receipt + Node_Mat_In -.-> Table_Inv + + Node_WO_Release -.-> Table_WO + Node_Task -.-> Table_Task + Node_Labor -.-> Table_Labor + Node_QC -.-> Table_QC + Node_Receive -.-> Table_QC + Node_FG_In -.-> Table_Inv + Node_Ship -.-> Table_Ship + + Node_Invoice -.-> Table_Inv + Node_ArRec -.-> Table_Ar + Node_ApPay -.-> Table_Ap + Node_Cash -.-> Table_Cash +``` + +### 关键业务规则说明 + +1. **商机转订单**: + * 当 `FactOpportunity` 状态确认为“赢单”且 `DimContract` 签订后,系统生成 `FactSalesOrder`。 + * 此时确立销售对象(Customer)、产品(Product)及交付日期(DeliveryDate)。 + +2. **计划运算 (MPS/MRP)**: + * `FactProductionPlan` 汇总销售订单需求,结合 `FactInventoryMovement` 计算出的当前库存快照,进行净需求计算。 + * 利用 `DimBom` 展开物料清单,区分自制件(生成 `FactWorkOrder`)与采购件(生成 `FactPurchaseRequest`)。 + +3. **采购闭环管理**: + * **采购订单 (PO)**:`FactPurchaseOrder` 经审批后生效,明确供应商、价格、交期。 + * **入库验收 (IQC)**:物料到达后必须进行 IQC(`FactQualityInspection`),合格品方可办理入库(`FactPurchaseReceipt`)并增加库存(`FactInventoryMovement`)。 + * **财务对接 (AP)**:系统自动匹配 PO(采购单)、Receipt(入库单)和供应商发票,生成应付账款(`FactApPayment`),确保“三单一致”。 + * **供应商管理**:IQC 合格率与交期准确率回写至 `DimSupplier`,用于季度评估与优选名录维护。 + +4. **生产执行闭环**: + * 工单下达前需校验物料齐套性(依赖采购入库的 `FactInventoryMovement`)。 + * 现场工人通过 `FactLaborReport` 上报实际投入工时与产出数量,该数据是成本核算的核心依据。 + +5. **质量控制 (FQC)**: + * `FactQualityInspection` 记录成品检验结果。 + * 质检合格触发成品入库,不合格则根据原因(`DimQcReason`)触发返工或报废(`FactScrap`)。 + +6. **库存与交付**: + * 所有实物库存变动(入库、出库、报废)统一写入 `FactInventoryMovement`,确保库存台账的一致性。 + * `FactSalesShipment` 记录发货动作,作为收入确认(Revenue Recognition)的前置事件。 + +7. **财务资金流转**: + * **应收 (AR)**:销售发货后生成发票申请(`FactInvoice`),回款后(`FactArReceipt`)进行核销(`FactWriteOff`)。 + * **应付 (AP)**:采购入库确认应付后(`FactApPayment` 中的挂账状态),执行付款(`FactApPayment` 中的实付)并核销。 + * **资金 (Cash)**:所有收付款动作最终汇总至 `FactCashFlow`,提供实时的资金日记账分析。 + +## 表结构详设 + +类型说明(可按目标数据库微调): + +- `bigint`:代理主键/外键 +- `varchar(n)`:业务键、名称、状态等 +- `decimal(18,4)`:数量与金额(金额可按需要改 `decimal(18,2)`) +- `datetime2`:统一时间字段(UTC) + +### DimPerson(人员维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| PersonId | bigint | N | autoincr | 主键 | | + | PersonCode | varchar(64) | N | | 人员编码(业务键) | 来源:用户ID/工号 | + | PersonName | varchar(255) | N | | 姓名 | | + | Department | varchar(100) | Y | | 部门 | | + | Position | varchar(100) | Y | | 职位/岗位 | | + | Phone | varchar(32) | Y | | 电话 | | + | Email | varchar(100) | Y | | 邮箱 | | + | Status | varchar(20) | Y | | 状态 | ACTIVE, INACTIVE | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | +| Birthday | date | Y | | 出生日期 | | +| Employment_day | date | Y | | 入职日期 | | +| Job_description | varchar(255) | Y | | 职务描述 | | + +### DimProduct(产品维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| ProductId | bigint | N | autoincr | 主键 | | + | ProductCode | varchar(64) | N | | 产品编码(业务键) | 来源:产品列表.产品编码 | + | ProductName | varchar(255) | N | | 产品名称 | | + | ProductSpec | varchar(255) | Y | | 产品规格 | | + | ProductCategory | varchar(100) | Y | | 产品类别 | | + | ProductType | varchar(100) | Y | | 产品类型 | | + | UomCode | varchar(16) | Y | | 主单位 | kg,pcs,L,m | + | Brand | varchar(100) | Y | | 品牌 | | + | Origin | varchar(100) | Y | | 产地 | | + | TransportCondition | varchar(100) | Y | | 运输条件 | | + | Barcode | varchar(64) | Y | | 条形码 | | + | DrawingNumber | varchar(64) | Y | | 图号 | | + | Material | varchar(100) | Y | | 材料 | | + | SurfaceTreatment | varchar(100) | Y | | 表面处理 | | + | MinStockQty | decimal(18,4) | Y | | 最低库存数量 | | + | SafetyStockQty | decimal(18,4) | Y | | 安全库存数量 | | + | MaxStockQty | decimal(18,4) | Y | | 最高库存数量 | | + | SourceSystem | varchar(50) | Y | | 数据来源 | 来源:数据来源 | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimCustomer(客户维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| CustomerId | bigint | N | autoincr | 主键 | | + | CustomerName | varchar(255) | N | | 客户名称(业务键) | 来源:客户列表.客户名称 | + | CustomerSource | varchar(100) | Y | | 客户来源 | | + | Phone | varchar(32) | Y | | 手机 | | + | Telephone | varchar(32) | Y | | 电话 | | + | Email | varchar(100) | Y | | 邮箱 | | + | CustomerLevel | varchar(50) | Y | | 客户级别 | | + | Industry | varchar(100) | Y | | 客户行业 | | + | Address | varchar(500) | Y | | 详细地址 | | + | IsLocked | bit | Y | | 锁定状态 | 0,1 | + | DealStatus | varchar(50) | Y | | 成交状态 | | + | LastFollowUpTimeUTC | datetime2 | Y | | 最后跟进时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimSupplier(供应商维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| SupplierId | bigint | N | autoincr | 主键 | | + | SupplierCode | varchar(64) | N | | 供应商编码(业务键) | 来源:供应商列表.供应商编码 | + | SupplierName | varchar(255) | N | | 供应商名称 | | + | SupplierCategory | varchar(100) | Y | | 供应商分类 | | + | SupplierLevel | varchar(50) | Y | | 供应商级别 | | + | Address | varchar(500) | Y | | 地址 | | + | DefaultCurrencyCode | varchar(16) | Y | | 默认币别 | CNY,USD,EUR | + | SettlementTerm | varchar(50) | Y | | 结算期限 | | + | ContractExpiryDateUTC | datetime2 | Y | | 合同到期日(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimWarehouse(仓库维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| WarehouseId | bigint | N | autoincr | 主键 | | + | WarehouseCode | varchar(64) | N | | 仓库编码(业务键) | 来源:仓库列表.仓库编码 | + | WarehouseName | varchar(255) | N | | 仓库名称 | | + | WarehouseType | varchar(50) | Y | | 仓库类型 | | + | Status | varchar(20) | Y | | 仓库状态 | ENABLED,DISABLED | + | Address | varchar(500) | Y | | 仓库地址 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimBom(物料清单维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| BomId | bigint | N | autoincr | 主键 | | + | BomNumber | varchar(64) | N | | 清单编号(业务键) | 来源:物料清单.清单编号 | + | BomTopic | varchar(100) | Y | | 清单主题 | 来源:清单主题 | +| IsPrimary | bit | Y | | 主辅清单标识 | 0,1 | +| LevelNumber | int | Y | | 层级 | | + | ProductCode | varchar(64) | Y | | 关联产品编码 | 来源:产品编号 | + | UnitUsageQty | decimal(18,4) | Y | | 单位用量 | | + | UomCode | varchar(16) | Y | | 产品单位 | | + | SupplierName | varchar(255) | Y | | 默认供应商(退化属性) | 来源:供应商 | +| Remark | varchar(500) | Y | | 备注 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimRouting(工艺路线维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| RoutingId | bigint | N | autoincr | 主键 | | + | RoutingNumber | varchar(64) | N | | 工艺路线编号(业务键) | 来源:工艺路线.工艺路线编号 | + | RoutingName | varchar(255) | N | | 工艺路线名称 | | + | OperationListText | varchar(2000) | Y | | 工序/工艺路线列表(原文) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimOperation(工序维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| OperationId | bigint | N | autoincr | 主键 | | + | OperationNumber | varchar(64) | N | | 工序编号(业务键) | 来源:工序.工序编号 | + | OperationName | varchar(255) | N | | 工序名称 | | + | ReportPermission | varchar(50) | Y | | 报工权限 | | + | ReportRatio | decimal(18,6) | Y | | 报工数配比 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimEquipment(设备维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| EquipmentId | bigint | N | autoincr | 主键 | | + | EquipmentNumber | varchar(64) | N | | 设备编号(业务键) | 来源:设备.设备编号 | + | EquipmentName | varchar(255) | N | | 设备名称 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimQcReason(质检不通过原因维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| QcReasonId | bigint | N | autoincr | 主键 | | + | QcReasonNumber | varchar(64) | N | | 不通过原因编号(业务键) | 来源:质检原因.不通过原因编号 | + | QcReasonName | varchar(255) | N | | 不通过原因 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### DimContract(合同维度) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| ContractId | bigint | N | autoincr | 主键 | | + | ContractNumber | varchar(64) | N | | 合同编号(业务键) | 来源:合同.合同编号 | + | ContractName | varchar(255) | Y | | 合同名称 | | + | OwnerId | bigint | Y | | 负责人 (外键 → DimPerson) | | + | CustomerName | varchar(255) | Y | | 客户名称(退化) | | + | CustomerId | bigint | Y | | 关联客户(外键 → DimCustomer) | | + | ContractAmount | decimal(18,4) | Y | | 合同金额 | | + | OrderTimeUTC | datetime2 | Y | | 下单时间(UTC) | | +| PaidAmount | decimal(18,4) | Y | | 回款金额 | | +| UnpaidAmount | decimal(18,4) | Y | | 未回款金额 | | +| Stage | varchar(50) | Y | | 当前阶段 | | +| Result | varchar(50) | Y | | 结果 | | + | ContractType | varchar(50) | Y | | 合同类型 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| RowValidFrom | datetime2 | N | | SCD2 生效时间(UTC) | | +| RowValidTo | datetime2 | N | | SCD2 失效时间(UTC) | | +| IsCurrent | bit | N | 1 | 是否当前版本 | 0,1 | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactInventoryMovement(出入库流水事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| InventoryMovementId | bigint | N | autoincr | 主键 | | + | EventTimeUTC | datetime2 | N | | 出入库事件时间(UTC) | 来源:出入库日期/入库时间/出库时间 | + | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | + | WarehouseId | bigint | Y | | 关联DimWarehouse (外键 → DimWarehouse) | | + | BomId | bigint | Y | | 关联DimBom (外键 → DimBom) | | +| OperatorId | bigint | Y | | 外键 → DimPerson(操作人) | | +|SalesOrderId | bigint | Y | | 关联FactSalesOrder (外键 → FactSalesOrder) | | +|PurchaseOrderId | bigint | Y | | 关联FactPurchaseOrder (外键 → FactPurchaseOrder) | | + | DocNumber | varchar(64) | N | | 出入库单号 | 来源:出入库单号/入库单编号/出库单编号 | + | DocType | varchar(30) | N | | 单据类型 | IN,OUT,TRANSFER,OTHER_IN,OTHER_OUT | + | MovementQty | decimal(18,4) | N | 0 | 出入库数量(入为正,出为负) | | +| UomCode | varchar(16) | Y | | 单位 | | +| UnitPrice | decimal(18,4) | Y | | 单价 | | +| DiscountUnitPrice | decimal(18,4) | Y | | 折后单价 | | +| Amount | decimal(18,4) | Y | | 总价 | | +| DiscountAmount | decimal(18,4) | Y | | 折后总价 | | +| OnhandQtyAfter | decimal(18,4) | Y | | 发生后库存量(源字段) | | +| ResponsiblePerson | varchar(100) | Y | | 负责人 | | +| AuditStatus | varchar(20) | Y | | 审核状态 | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactWorkOrder(工单事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| WorkOrderId | bigint | N | autoincr | 主键 | | + | WorkOrderNumber | varchar(64) | N | | 工单编号(退化维度) | 来源:工单编号 | + | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | +| SupplierId | bigint | Y | | 外键 → DimSupplier(委外时) | | +| RoutingId | bigint | Y | | 外键 → DimRouting | | +| ProductionManagerId | bigint | Y | | 外键 → DimPerson(生产主管) | | + | PlannedQty | decimal(18,4) | Y | | 计划生产数 | | + | CompletedQty | decimal(18,4) | Y | | 完成数 | | +| OperationGoodQty | decimal(18,4) | Y | | 工序良品数 | | +| OperationBadQty | decimal(18,4) | Y | | 工序不良品数 | | +| QcGoodQty | decimal(18,4) | Y | | 工单质检良品数 | | +| QcBadQty | decimal(18,4) | Y | | 工单质检不良品数 | | + | Status | varchar(30) | Y | | 工单状态 | OPEN,STARTED,CLOSED,SCRAP | + | EventTimeUTC | datetime2 | N | | 工单状态变更/抽取时间(UTC) | | +| SourceDocNumber | varchar(64) | Y | | 单据编号(退化) | 来源:单据编号 | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactOperationTask(工序任务事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| OperationTaskId | bigint | N | autoincr | 主键 | | +| WorkOrderNumber | varchar(64) | N | | 工单编号(退化维度) | 来源:工单编号 | +| OperationId | bigint | Y | | 关联DimOperation (外键 → DimOperation) | | +| ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | +| PlannedStartTimeUTC | datetime2 | Y | | 计划开始时间(UTC) | | +| PlannedEndTimeUTC | datetime2 | Y | | 计划结束时间(UTC) | | +| ActualStartTimeUTC | datetime2 | Y | | 实际开始时间(UTC) | | +| ActualEndTimeUTC | datetime2 | Y | | 实际结束时间(UTC) | | +| PlannedQty | decimal(18,4) | Y | | 计划数 | | +| GoodQty | decimal(18,4) | Y | | 良品数 | | +| BadQty | decimal(18,4) | Y | | 不良品数 | | +| BadReasonText | varchar(500) | Y | | 不良品项(原文) | | +| EventTimeUTC | datetime2 | N | | 任务事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactLaborReport(报工事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| LaborReportId | bigint | N | autoincr | 主键 | | + | WorkOrderNumber | varchar(64) | N | | 工单(退化维度) | 来源:报工列表.工单 | +| TaskName | varchar(255) | Y | | 任务(退化维度) | 来源:报工列表.任务 | + | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | +| OperationStatus | varchar(30) | Y | | 工序状态 | | + | WorkerName | varchar(100) | Y | | 生产人员 | | + | ReportQty | decimal(18,4) | N | 0 | 报工数 | | + | GoodQty | decimal(18,4) | Y | | 良品数 | | + | BadQty | decimal(18,4) | Y | | 良品数 | | +| DurationMinutes | decimal(18,2) | Y | | 报工时长(分钟) | | +| UomCode | varchar(16) | Y | | 单位 | | +| BadReasonText | varchar(500) | Y | | 不良品项(原文) | | + | EventTimeUTC | datetime2 | N | | 报工事件时间(UTC) | | +| SupplierId | bigint | Y | | 外键 → DimSupplier(委外时) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactQualityInspection(质检事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| QualityInspectionId | bigint | N | autoincr | 主键 | | + | InspectionNumber | varchar(64) | N | | 质检记录编号(退化维度) | 来源:质检记录编号 | +| InspectionBatchNumber | varchar(64) | Y | | 质检批次号 | | + | WorkOrderNumber | varchar(64) | Y | | 工单(退化维度) | | + | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | +| SupplierId | bigint | Y | | 外键 → DimSupplier | | +| QcReasonId | bigint | Y | | 外键 → DimQcReason(不通过原因) | | + | InspectorId | bigint | Y | | 质检员 (外键 → DimPerson) | | +| QcType | varchar(30) | Y | | 质检类型 | IN_PROCESS,FINAL,INCOMING | + | PassQty | decimal(18,4) | Y | | 通过数 | | + | FailQty | decimal(18,4) | Y | | 不通过数 | | +| SalesOrderNumber | varchar(64) | Y | | 销售订单(退化维度) | | +| CustomerName | varchar(255) | Y | | 客户(退化维度) | | +| ShipQty | decimal(18,4) | Y | | 发货数 | | + | EventTimeUTC | datetime2 | N | | 质检事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactSalesOrder(销售订单事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| SalesOrderId | bigint | N | autoincr | 主键 | | + | SalesOrderNumber | varchar(64) | N | | 订单编号(退化维度) | 来源:销售订单.订单编号 | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | SalesPersonId | bigint | Y | | 关联销售员 (外键 → DimPerson) | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | + | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | + | OrderDateUTC | datetime2 | Y | | 订单单据日期(UTC) | | +| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | + | DealAmount | decimal(18,4) | Y | | 成交金额 | | +| PaymentStatus | varchar(30) | Y | | 收款状态/付款状态 | | +| TransportMode | varchar(50) | Y | | 运输方式 | | +| Packaging | varchar(100) | Y | | 包材 | | +| CustomsNumber | varchar(64) | Y | | 报关单号 | | +| ProductionDocNumber | varchar(64) | Y | | 生产单据编号 | | +| Stage | varchar(50) | Y | | 当前阶段 | | + | Result | varchar(50) | Y | | 结果 | | + | EventTimeUTC | datetime2 | N | | 订单事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactSalesShipment(销售出库事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| SalesShipmentId | bigint | N | autoincr | 主键 | | + | ShipmentNumber | varchar(64) | N | | 出库单编号(退化维度) | 来源:销售出库单.出库单编号 | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | SalesPersonId | bigint | Y | | 关联销售员 (外键 → DimPerson) | | +| OperatorId | bigint | Y | | 外键 → DimPerson(发货员) | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | + | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | + | DocDateUTC | datetime2 | Y | | 出库单单据日期(UTC) | | +| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | +| TransportMode | varchar(50) | Y | | 运输方式 | | +| CustomsNumber | varchar(64) | Y | | 报关单号 | | + | Amount | decimal(18,4) | Y | | 成交金额/单据金额 | | + | EventTimeUTC | datetime2 | N | | 出库事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactSalesReturn(销售退货事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| SalesReturnId | bigint | N | autoincr | 主键 | | + | SalesReturnNumber | varchar(64) | N | | 销售退货单编号(退化维度) | | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | SalesPersonId | bigint | Y | | 销售员 (外键 → DimPerson) | | +| OperatorId | bigint | Y | | 外键 → DimPerson(处理人) | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | +| ReturnReason | varchar(255) | Y | | 退货原因 | | +| IsReship | bit | Y | | 是否退货重发 | 0,1 | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | +| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | + | Amount | decimal(18,4) | Y | | 单据金额 | | + | EventTimeUTC | datetime2 | N | | 退货事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactPurchaseRequest(采购申请事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| PurchaseRequestId | bigint | N | autoincr | 主键 | | + | PurchaseRequestNumber | varchar(64) | N | | 采购申请单编号(退化维度) | | + | SupplierId | bigint | Y | | 建议供应商 (外键 → DimSupplier) | | + | ApplicantId | bigint | Y | | 申请人 (外键 → DimPerson) | | + | CurrencyCode | varchar(16) | Y | | 币别 | | + | FxRate | decimal(18,6) | Y | | 汇率 | | + | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | + | DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | + | TotalAmount | decimal(18,4) | Y | | 采购总价 | | + | Status | varchar(30) | Y | | 采购状态 | | + | RelatedOrderNumber | varchar(64) | Y | | 订单编号(退化) | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | + | EventTimeUTC | datetime2 | N | | 采购申请事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactPurchaseOrder(采购订单事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| PurchaseOrderId | bigint | N | autoincr | 主键 | | + | PurchaseOrderNumber | varchar(64) | N | | 采购订单编号(退化维度) | | + | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | + | BuyerId | bigint | Y | | 关联采购员 (外键 → DimPerson) | | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | + | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | +| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | + | ContractId | bigint | Y | | 关联合同 (外键 → DimContract) | | +| DeliveryNoteNumber | varchar(64) | Y | | 送货单号 | | +| SettlementDateUTC | datetime2 | Y | | 结算日期(UTC) | | +| SettlementTerm | varchar(50) | Y | | 结算期限 | | + | EventTimeUTC | datetime2 | N | | 采购订单事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactPurchaseReceipt(采购入库事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| PurchaseReceiptId | bigint | N | autoincr | 主键 | | + | PurchaseReceiptNumber | varchar(64) | N | | 采购入库单编号(退化维度) | | + | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | +| BuyerId | bigint | Y | | 外键 → DimPerson(采购员) | | +| OperatorId | bigint | Y | | 外键 → DimPerson(收货员) | | + | PurchaseOrderNumber | varchar(64) | Y | | 采购订单(退化维度) | | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | +| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | +| DeliveryDateUTC | datetime2 | Y | | 交货日期(UTC) | | +| ContractNumber | varchar(64) | Y | | 合同编号(退化) | | + | Amount | decimal(18,4) | Y | | 成交金额 | | +| EstimatedCost | decimal(18,4) | Y | | 预计采购费用 | | +| ReceiptQtyTotal | decimal(18,4) | Y | | 入库总数 | | +| PaidAmount | decimal(18,4) | Y | | 已付款金额 | | +| UnpaidAmount | decimal(18,4) | Y | | 未付款金额 | | +| PaymentStatus | varchar(30) | Y | | 付款状态 | | +| ReturnedQty | decimal(18,4) | Y | | 已退货数 | | +| NotReturnedQty | decimal(18,4) | Y | | 未退货数 | | +| ReturnStatus | varchar(30) | Y | | 退货状态 | | + | EventTimeUTC | datetime2 | N | | 入库事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactPurchaseReturn(采购退货事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| PurchaseReturnId | bigint | N | autoincr | 主键 | | + | PurchaseReturnNumber | varchar(64) | N | | 采购退货单编号(退化维度) | | + | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | + | BuyerId | bigint | Y | | 采购员 (外键 → DimPerson) | | + | OperatorId | bigint | Y | | 处理人 (外键 → DimPerson) | | + | CurrencyCode | varchar(16) | Y | | 币别 | | + | FxRate | decimal(18,6) | Y | | 汇率 | | + | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | + | ReturnReason | varchar(255) | Y | | 退货原因 | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化) | | + | RelatedOrderNumber | varchar(64) | Y | | 订单号(退化) | | + | EventTimeUTC | datetime2 | N | | 退货事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactInventoryTransfer(库存调拨事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| InventoryTransferId | bigint | N | autoincr | 主键 | | + | TransferNumber | varchar(64) | N | | 调拨单编号(退化维度) | | + | OperatorId | bigint | Y | | 操作人 (外键 → DimPerson) | | + | TransferName | varchar(255) | Y | | 调拨名称 | | + | Tag | varchar(100) | Y | | 单据标签 | | + | TransferTimeUTC | datetime2 | Y | | 调拨时间(UTC) | | + | PlateNumber | varchar(32) | Y | | 车牌号 | | + | DeliveryMode | varchar(50) | Y | | 交货方式 | | + | EventTimeUTC | datetime2 | N | | 调拨事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactInventoryCount(库存盘点事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| InventoryCountId | bigint | N | autoincr | 主键 | | + | CountNumber | varchar(64) | N | | 盘点编号(退化维度) | | + | OperatorId | bigint | Y | | 盘点人 (外键 → DimPerson) | | + | WarehouseName | varchar(255) | Y | | 盘点仓库(退化) | | + | CountName | varchar(255) | Y | | 盘点名称 | | + | CountTimeUTC | datetime2 | Y | | 盘点时间(UTC) | | + | EventTimeUTC | datetime2 | N | | 盘点事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactScrap(报废事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| ScrapId | bigint | N | autoincr | 主键 | | + | ScrapNumber | varchar(64) | N | | 报废编号(退化维度) | | + | OperatorId | bigint | Y | | 操作人 (外键 → DimPerson) | | + | WarehouseName | varchar(255) | Y | | 仓库(退化) | | + | ScrapTimeUTC | datetime2 | Y | | 报废日期(UTC) | | + | EventTimeUTC | datetime2 | N | | 报废事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactArReceipt(应收收款事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| ArReceiptId | bigint | N | autoincr | 主键 | | + | DocNumber | varchar(64) | N | | 单据编号(退化维度) | 来源:收款单/回款/预收/其他收入等 | + | DocType | varchar(30) | N | | 单据类型 | RECEIPT,REFUND,ADVANCE,ADVANCE_REFUND,OTHER_INCOME | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | +| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | + | Amount | decimal(18,4) | Y | | 收款总金额/回款金额/应收金额 | | +| AmountBase | decimal(18,4) | Y | | 本位币金额 | 来源:本位币字段 | +| FeeAmount | decimal(18,4) | Y | | 手续费 | | +| DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | +| WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | +| WrittenOffAmount | decimal(18,4) | Y | | 已核销金额 | | +| UnwrittenOffAmount | decimal(18,4) | Y | | 未核销金额 | | +| RelatedDocNumber | varchar(64) | Y | | 关联单编号/销售订单编号 | | + | EventTimeUTC | datetime2 | N | | 收款事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactApPayment(应付付款事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| ApPaymentId | bigint | N | autoincr | 主键 | | + | DocNumber | varchar(64) | N | | 单据编号(退化维度) | 来源:付款单/预付款/其他支出等 | + | DocType | varchar(30) | N | | 单据类型 | PAYMENT,REFUND,ADVANCE,ADVANCE_REFUND,OTHER_EXPENSE | + | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | + | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | +| DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | +| CurrencyCode | varchar(16) | Y | | 币别 | | +| FxRate | decimal(18,6) | Y | | 汇率 | | + | Amount | decimal(18,4) | Y | | 付款总金额/应付金额 | | +| AmountBase | decimal(18,4) | Y | | 本位币金额 | | +| FeeAmount | decimal(18,4) | Y | | 手续费 | | +| DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | +| WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | +| WrittenOffAmount | decimal(18,4) | Y | | 已核销金额 | | +| UnwrittenOffAmount | decimal(18,4) | Y | | 未核销金额 | | +| RelatedDocNumber | varchar(64) | Y | | 关联单编号/采购订单编号 | | + | EventTimeUTC | datetime2 | N | | 付款事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactInvoice(开票申请事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| InvoiceId | bigint | N | autoincr | 主键 | | + | InvoiceRequestNumber | varchar(64) | N | | 发票申请编号(退化维度) | 来源:发票申请编号 | + | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | + | ContractNumber | varchar(64) | Y | | 合同编号(退化维度) | | +| HandlerId | bigint | Y | | 外键 → DimPerson(经办人) | | +| CustomerId | bigint | Y | | 外键 → DimCustomer | | + | InvoiceAmount | decimal(18,4) | Y | | 开票金额 | | +| ContractAmount | decimal(18,4) | Y | | 合同金额 | | + | InvoiceDateUTC | datetime2 | Y | | 开票日期(UTC) | | + | ActualInvoiceDateUTC | datetime2 | Y | | 开票日期(UTC) | | +| InvoiceType | varchar(50) | Y | | 开票类型 | | +| Stage | varchar(50) | Y | | 当前阶段 | | +| Result | varchar(50) | Y | | 结果 | | + | EventTimeUTC | datetime2 | N | | 开票事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactWriteOff(核销事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| WriteOffId | bigint | N | autoincr | 主键 | | + | WriteOffNumber | varchar(64) | N | | 核销单编号(退化维度) | 来源:核销单编号 | + | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | + | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | + | HandlerId | bigint | Y | | 经办人 (外键 → DimPerson) | | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | SupplierId | bigint | Y | | 关联DimSupplier (外键 → DimSupplier) | | + | WriteOffType | varchar(30) | Y | | 核销类型 | AR,AP | + | CurrencyCode | varchar(16) | Y | | 币别 | | + | FxRate | decimal(18,6) | Y | | 汇率 | | + | DiscountAmount | decimal(18,4) | Y | | 优惠金额 | | + | WriteOffAmount | decimal(18,4) | Y | | 核销总金额 | | + | EventTimeUTC | datetime2 | N | | 核销事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactCashFlow(资金流水事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| CashFlowId | bigint | N | autoincr | 主键 | | + | DocNumber | varchar(64) | Y | | 单据编号(退化维度) | 来源:资金流向.单据编号/转账单编号 | + | DocType | varchar(30) | N | | 单据类型 | CASHFLOW,TRANSFER | + | AccountName | varchar(255) | Y | | 账户名称 | | + | DocDateUTC | datetime2 | Y | | 单据日期(UTC) | | + | BusinessType | varchar(100) | Y | | 业务类型 | | + | IncomeAmount | decimal(18,4) | Y | | 收入 | | + | ExpenseAmount | decimal(18,4) | Y | | 支出 | | + | BalanceAmount | decimal(18,4) | Y | | 余额 | | + | Counterparty | varchar(255) | Y | | 往来单位 | | + | Remark | varchar(500) | Y | | 单据备注 | | + | EventTimeUTC | datetime2 | N | | 资金事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactCommission(分佣事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| CommissionId | bigint | N | autoincr | 主键 | | + | CommissionPerson | varchar(100) | N | | 分佣人 | | + | Department | varchar(100) | Y | | 部门 | | + | CommissionRatePct | decimal(9,4) | Y | | 提成比例(%) | | + | Coefficient | decimal(18,6) | Y | | 系数 | | + | ReceivableCommission | decimal(18,4) | Y | | 应收佣金 | | + | ReceivedCommission | decimal(18,4) | Y | | 实收佣金 | | + | ContractId | bigint | Y | | 关联DimContract (外键 → DimContract) | | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | ProductName | varchar(255) | Y | | 产品名称(退化) | | + | OpportunityName | varchar(255) | Y | | 商机名称(退化) | | + | ContractAmount | decimal(18,4) | Y | | 合同金额 | | + | DealTimeUTC | datetime2 | Y | | 成交时间(UTC) | | + | IsInvoiced | bit | Y | | 是否开票 | 0,1 | + | EventTimeUTC | datetime2 | N | | 分佣事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactLead(线索事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| LeadId | bigint | N | autoincr | 主键 | | + | LeadName | varchar(255) | N | | 线索名称 | | + | OwnerId | bigint | Y | | 负责人 (外键 → DimPerson) | | + | LeadSource | varchar(100) | Y | | 线索来源 | | +| Phone | varchar(32) | Y | | 手机 | | +| Telephone | varchar(32) | Y | | 电话 | | +| Email | varchar(100) | Y | | 邮箱 | | +| Address | varchar(500) | Y | | 地址 | | +| Industry | varchar(100) | Y | | 客户行业 | | +| CustomerLevel | varchar(50) | Y | | 客户级别 | | +| NextContactTimeUTC | datetime2 | Y | | 下次联系时间(UTC) | | +| FollowUpStatus | varchar(50) | Y | | 跟进状态 | | +| IsConverted | bit | Y | | 是否已转化 | 0,1 | +| Stage | varchar(50) | Y | | 当前阶段 | | +| Result | varchar(50) | Y | | 结果 | | + | EventTimeUTC | datetime2 | N | | 线索事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactOpportunity(商机事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| OpportunityId | bigint | N | autoincr | 主键 | | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | OpportunityAmount | decimal(18,4) | Y | | 商机金额 | | + | ExpectedDealDateUTC | datetime2 | Y | | 预计成交日期(UTC) | | +| NextContactTimeUTC | datetime2 | Y | | 下次联系时间(UTC) | | +| StatusGroup | varchar(50) | Y | | 商机状态组 | | +| Stage | varchar(50) | Y | | 当前阶段 | | + | Result | varchar(50) | Y | | 结果 | | + | EventTimeUTC | datetime2 | N | | 商机事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +### FactProductionPlan(生产计划_装配工单汇总事实) +| 字段 | 类型 | 空 | 缺省 | 描述 | 枚举/单位 | +|---|---|---|---|---|---| +| ProductionPlanId | bigint | N | autoincr | 主键 | | + | AssemblyWorkOrderNumber | varchar(64) | N | | 装配工单编号(退化维度) | 来源:装配工单编号 | + | WorkOrderCount | int | Y | | 工单数 | | + | FinishedCount | int | Y | | 已结束数 | | + | PlannedQty | decimal(18,4) | Y | | 计划数 | | + | CompletedQty | decimal(18,4) | Y | | 完工数 | | + | PlanStatus | varchar(50) | Y | | 生产计划状态 | | + | ProgressText | varchar(100) | Y | | 工单生成/用料生成/单据进度(原文) | | + | CustomerName | varchar(255) | Y | | 客户(退化) | | + | CustomerId | bigint | Y | | 关联DimCustomer (外键 → DimCustomer) | | + | ProductId | bigint | Y | | 关联DimProduct (外键 → DimProduct) | | + | SalesOrderId | bigint | Y | | 关联FactSalesOrder (外键 → FactSalesOrder) | | + | EventTimeUTC | datetime2 | N | | 计划事件时间(UTC) | | +| SourceSystem | varchar(50) | Y | | 数据来源 | | +| LastUpdatedUTC | datetime2 | N | sysutcdatetime | 行级更新时间 | | + +## KPI 映射(示例) + +| KPI | 口径 | 表/字段 | +|---|---|---| +| 报工良率 | `sum(GoodQty) / nullif(sum(ReportQty),0)` | `FactLaborReport.GoodQty/ReportQty` | +| 质检良率 | `sum(PassQty) / nullif(sum(PassQty+FailQty),0)` | `FactQualityInspection.PassQty/FailQty` | +| 采购未付款 | `sum(UnpaidAmount)` | `FactPurchaseReceipt.UnpaidAmount` | +| AR 未核销 | `sum(UnwrittenOffAmount)` | `FactArReceipt.UnwrittenOffAmount` | +| AP 未核销 | `sum(UnwrittenOffAmount)` | `FactApPayment.UnwrittenOffAmount` | + + +## 数据字典 + +以下枚举值仅供参考,实际实施时应根据业务系统配置进行调整。 + +### 1. 通用类 (Common) + +| 字典代码 | 字典名称 | 枚举值 (Code: Name) | 适用字段 | +|---|---|---|---| +| **CurrencyCode** | 币种 | CNY: 人民币
USD: 美元
EUR: 欧元
JPY: 日元
HKD: 港币 | `FactSalesOrder.CurrencyCode`
`FactPurchaseOrder.CurrencyCode`
`FactArReceipt.CurrencyCode`
... | +| **UomCode** | 计量单位 | pcs: 个/件
kg: 千克
m: 米
L: 升
box: 箱
set: 套 | `DimProduct.UomCode`
`DimBom.UomCode`
`FactInventoryMovement.UomCode` | +| **Boolean** | 布尔标识 | 0: 否 (No/False)
1: 是 (Yes/True) | `IsCurrent`, `IsLocked`, `IsPrimary`, `IsReship`, `IsConverted`, `IsInvoiced` | + +### 2. 人员与组织 (Person & Org) + +| 字典代码 | 字典名称 | 枚举值 | 适用字段 | +|---|---|---|---| +| **PersonStatus** | 人员状态 | ACTIVE: 在职
INACTIVE: 离职
LEAVE: 休假 | `DimPerson.Status` | +| **Department** | 部门 | SALES: 销售部
MFG: 生产部
PUR: 采购部
WH: 仓储部
FIN: 财务部
QC: 质检部 | `DimPerson.Department` | +| **SupplierLevel** | 供应商等级 | STRATEGIC: 战略供应商
PREFERRED: 优选供应商
APPROVED: 合格供应商
PROBATION: 考察供应商
EXIT: 淘汰供应商 | `DimSupplier.SupplierLevel` | +| **CustomerLevel** | 客户等级 | KA: 重点客户
VIP: 重要客户
NORMAL: 普通客户
POTENTIAL: 潜在客户 | `DimCustomer.CustomerLevel`
`FactLead.CustomerLevel` | +| **Industry** | 行业 | MFG: 制造
RETAIL: 零售
IT: 互联网
FIN: 金融
EDU: 教育 | `DimCustomer.Industry`
`FactLead.Industry` | + +### 3. 生产与库存 (Production & Inventory) + +| 字典代码 | 字典名称 | 枚举值 | 适用字段 | +|---|---|---|---| +| **WorkOrderStatus** | 工单状态 | PLANNED: 计划中
RELEASED: 已下达
STARTED: 生产中
PAUSED: 暂停
COMPLETED: 已完工
CLOSED: 已结案
SCRAP: 报废 | `FactWorkOrder.Status` | +| **MovementType** | 出入库类型 | IN_PURCHASE: 采购入库
IN_FG: 完工入库
IN_RETURN: 销售退货入库
IN_OTHER: 其他入库
OUT_SALES: 销售出库
OUT_MATERIAL: 生产领料
OUT_RETURN: 采购退货出库
OUT_SCRAP: 报废出库
TRANSFER: 调拨 | `FactInventoryMovement.DocType` | +| **WarehouseType** | 仓库类型 | RAW: 原材料仓
FG: 成品仓
WIP: 线边仓/半成品仓
SCRAP: 废品仓
VMI: 供应商库存仓 | `DimWarehouse.WarehouseType` | +| **QcType** | 质检类型 | IQC: 来料检验
IPQC: 制程检验
FQC: 终检
OQC: 出货检验 | `FactQualityInspection.QcType` | +| **ProductType** | 产品类型 | FINISHED: 成品
SEMI: 半成品
RAW: 原材料
SERVICE: 服务 | `DimProduct.ProductType` | +| **TransportCondition** | 运输条件 | NORMAL: 常温
COLD: 冷链
FRAGILE: 易碎
HAZARDOUS: 危险品 | `DimProduct.TransportCondition` | +| **PlanStatus** | 计划状态 | DRAFT: 草稿
CONFIRMED: 已确认
EXECUTING: 执行中
COMPLETED: 已完成 | `FactProductionPlan.PlanStatus` | + +### 4. 销售与采购 (Sales & Purchase) + +| 字典代码 | 字典名称 | 枚举值 | 适用字段 | +|---|---|---|---| +| **LeadSource** | 线索来源 | WEB: 官网
REFERRAL: 推荐
EXHIBITION: 展会
COLD_CALL: 陌拜
OTHER: 其他 | `FactLead.LeadSource` | +| **SalesStage** | 销售阶段 | NEW: 新建
QUOTED: 已报价
NEGOTIATING: 谈判中
WON: 赢单
LOST: 输单 | `FactOpportunity.Stage`
`FactLead.Stage` | +| **OppStatusGroup** | 商机状态组 | OPEN: 开启
WON: 赢单
LOST: 输单 | `FactOpportunity.StatusGroup` | +| **PurchaseStatus** | 采购状态 | DRAFT: 草稿
APPROVED: 已审批
SENT: 已发送
PARTIAL_RECEIVED: 部分收货
RECEIVED: 全部收货
CLOSED: 关闭 | `FactPurchaseRequest.Status` | +| **PaymentStatus** | 支付状态 | UNPAID: 未付款
PARTIAL: 部分付款
PAID: 已付款
OVERDUE: 逾期 | `FactSalesOrder.PaymentStatus`
`FactPurchaseReceipt.PaymentStatus` | +| **TransportMode** | 运输方式 | LAND: 陆运
SEA: 海运
AIR: 空运
RAIL: 铁运
EXPRESS: 快递 | `FactSalesOrder.TransportMode`
`FactSalesShipment.TransportMode` | + +### 5. 财务 (Finance) + +| 字典代码 | 字典名称 | 枚举值 | 适用字段 | +|---|---|---|---| +| **FinDocType** | 财务单据类型 | RECEIPT: 收款单
PAYMENT: 付款单
INVOICE: 发票
REFUND: 退款
ADVANCE: 预收/预付
WRITEOFF: 核销 | `FactArReceipt.DocType`
`FactApPayment.DocType` | +| **WriteOffType** | 核销类型 | AR: 应收核销
AP: 应付核销 | `FactWriteOff.WriteOffType` | +| **InvoiceType** | 发票类型 | VAT_SPECIAL: 增值税专票
VAT_NORMAL: 增值税普票
ELECTRONIC: 电子发票 | `FactInvoice.InvoiceType` | + + +## 变更记录 + +| 版本 | 日期 | 作者 | 变更描述 | 脚本影响 | +|---|---|---|---|---| +| v1.0.0 | 2025-12-25 | GPT-5.2 | 首次输出:维度SCD2 + 事实星座 + ER图 + 字段清单 | 未生成 | +| v1.1.0 | 2025-12-28 | Trae | 新增 DimPerson(人员维度)并关联销售、采购、生产、审批等事实表 | 待生成 | +| v1.2.0 | 2025-12-29 | Trae | 新增「业务数据流转全景图」,包含商机-订单-计划-生产-交付全链路 Mermaid 流程图 | 无 | +| v1.3.0 | 2025-12-29 | Trae | 升级全景图,新增外部采购闭环(PO管理、入库验收IQC、三单匹配AP、供应商管理) | 无 | +| v1.4.0 | 2025-12-29 | Trae | 升级全景图,新增财务与资金阶段(开票Invoice、收付款AP/AR、核销WriteOff、资金CashFlow) | 无 | +| v1.5.0 | 2025-12-30 | Trae | 新增「数据字典」章节,规范化通用类、人员组织、生产库存、销售采购及财务类枚举值 | 无 | diff --git a/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml new file mode 100644 index 0000000..ecfcf7e --- /dev/null +++ b/lzwcai_mcpskills_mfg_data_agentv2/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "lzwcai-mcpskills-mfg-data-agentv2" +version = "0.1.1" +description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation" +readme = "README.md" +requires-python = ">=3.13" +license = {text = "MIT"} +authors = [ + {name = "lzwcai", email = "your-email@example.com"}, +] +keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "httpx>=0.28.1", + "mcp[cli]>=1.10.1", + "pypinyin>=0.53.0", +] + +[project.scripts] +lzwcai-mcpskills-mfg-data-agentv2 = "lzwcai_mcpskills_mfg_data_agentv2.main:main" + +[tool.hatch.build.targets.wheel] +packages = ["lzwcai_mcpskills_mfg_data_agentv2"] + +[tool.hatch.build.targets.wheel.force-include] +"lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json"