docs: 删除项目文档和配置文件

移除主 README.md 文件,包含项目概述、安装指南和配置示例

移除 lzwcai_mcp_sqlexecutor 模块的完整文档、配置文件和初始化文件

删除 businessQueries.json 配置模板和 .python-version 版本指定文件

更新 .gitignore 文件以清理 Python 缓存文件
```
This commit is contained in:
2026-06-11 18:56:15 +08:00
parent 0d48341b73
commit d71458669a
169 changed files with 0 additions and 27179 deletions

View File

@@ -1,68 +0,0 @@
# lzwcai-mcp-server-package
MCP (Model Context Protocol) 服务器工具集,为 AI 助手提供企业级业务能力扩展。
## 📦 包含模块
| 模块 | 版本 | 说明 |
|------|------|------|
| [lzwcai-mcp-iot](./lzwcai_mcp_iot) | 0.3.3 | IoT 设备控制服务器,支持设备查询、定位和控制 |
| [lzwcai-mcp-sqlexecutor](./lzwcai_mcp_sqlexecutor) | 0.1.8 | SQL 查询执行服务器,支持动态工具生成 |
| [lzwcai-mcp-api-converter](./lzwcai_mcp_api_converter) | 0.1.30 | API 转换服务器,将业务 API 转换为 MCP 工具 |
| [lzwcai-demp-tool-server-dify-to-mcp](./lzwcai_demp_tool_server_dify_to_mcp) | 0.1.4 | Dify 集成工具,将 Dify 模型部署到 MCP |
| [lzwcai-demp-tool-server-dify-to-mcp-test](./lzwcai_demp_tool_server_dify_to_mcp_test) | 0.1.0 | Dify 集成工具测试版 |
## 🚀 快速安装
```bash
# IoT 设备控制
pip install lzwcai-mcp-iot
# SQL 查询执行
pip install lzwcai-mcp-sqlexecutor
# API 转换器
pip install lzwcai-mcp-api-converter
# Dify 集成
pip install lzwcai-demp-tool-server-dify-to-mcp
```
## <20> 打包 与发布
```bash
# 进入子模块目录
cd lzwcai_mcp_iot
# 使用 uv 打包
uv build
# 上传到管理端技能广场
# 将 dist/ 目录下的 .tar.gz 文件上传至技能广场
```
## 🔧 MCP 客户端配置示例
```json
{
"mcpServers": {
"iot": {
"command": "lzwcai-mcp-iot"
},
"sql": {
"command": "lzwcai-mcp-sqlexecutor"
},
"api": {
"command": "lzwcai-mcp-api-converter"
}
}
}
```
## 📄 许可证
专有软件 - 版权所有 © LZWCAI开发团队
## 📧 联系方式
- 邮箱dev@lzwcai.com

View File

@@ -1,138 +0,0 @@
# lzwcai-mcp-sqlexecutor
一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志:详细的操作日志记录
- 🌐 中文支持:工具名称自动转换为拼音
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcp-sqlexecutor
```
### 从源码安装
```bash
git clone <repository-url>
cd lzwcai_mcp_sqlexecutor
pip install -e .
```
### 使用 uv 安装(推荐)
```bash
uv pip install lzwcai-mcp-sqlexecutor
```
## 使用方法
### 命令行启动
安装后,可以直接通过命令启动:
```bash
lzwcai-mcp-sqlexecutor
```
### 作为 Python 模块运行
```bash
python -m lzwcai_mcp_sqlexecutor.main
```
### 配置到 MCP 客户端
在你的 MCP 客户端配置文件中添加:
```json
{
"mcpServers": {
"lzwcai-sqlexecutor": {
"command": "lzwcai-mcp-sqlexecutor"
}
}
}
```
## 配置说明
### businessQueries.json
`businessQueries.json` 中定义你的业务查询:
```json
[
{
"id": "query-001",
"businessName": "用户订单查询",
"businessDescription": "根据用户ID查询订单信息",
"sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}",
"parameters": {
"type": "object",
"required": ["userId"],
"properties": {
"userId": {
"type": "integer",
"description": "用户的唯一标识符",
"examples": [10086]
}
}
}
}
]
```
## 开发
### 依赖项
- Python >= 3.13
- httpx >= 0.28.1
- mcp[cli] >= 1.10.1
- pypinyin >= 0.53.0
### 本地开发
```bash
# 克隆仓库
git clone <repository-url>
cd lzwcai_mcp_sqlexecutor
# 安装开发依赖
pip install -e .
# 运行服务器
python -m lzwcai_mcp_sqlexecutor.main
```
## 构建与发布
### 使用 build 构建
```bash
pip install build
python -m build
```
### 发布到 PyPI
```bash
pip install twine
twine upload dist/*
```
## 许可证
MIT License
## 作者
lzwcai

View File

@@ -1,10 +0,0 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -1,154 +0,0 @@
# lzwcai-mcp-sqlexecutor
一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志详细的操作日志记录仅输出到文件不干扰MCP通信
- 🌐 中文支持:工具名称自动转换为拼音
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcp-sqlexecutor
```
### 从源码安装
```bash
git clone <repository-url>
cd lzwcai_mcp_sqlexecutor
pip install -e .
```
### 使用 uv 安装(推荐)
```bash
uv pip install lzwcai-mcp-sqlexecutor
```
## 使用方法
### 命令行启动
安装后,可以直接通过命令启动:
```bash
lzwcai-mcp-sqlexecutor
```
### 作为 Python 模块运行
```bash
python -m lzwcai_mcp_sqlexecutor.main
```
### 配置到 MCP 客户端
在你的 MCP 客户端配置文件中添加:
```json
{
"mcpServers": {
"lzwcai-sqlexecutor": {
"command": "lzwcai-mcp-sqlexecutor"
}
}
}
```
## 配置说明
### businessQueries.json
`businessQueries.json` 中定义你的业务查询:
```json
[
{
"id": "query-001",
"businessName": "用户订单查询",
"businessDescription": "根据用户ID查询订单信息",
"sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}",
"parameters": {
"type": "object",
"required": ["userId"],
"properties": {
"userId": {
"type": "integer",
"description": "用户的唯一标识符",
"examples": [10086]
}
}
}
}
]
```
## 开发
### 依赖项
- Python >= 3.13
- httpx >= 0.28.1
- mcp[cli] >= 1.10.1
- pypinyin >= 0.53.0
### 本地开发
```bash
# 克隆仓库
git clone <repository-url>
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

View File

@@ -1,9 +0,0 @@
"""
lzwcai-mcp-sqlexecutor - MCP server for executing business SQL queries
"""
__version__ = "0.1.2"
__author__ = "lzwcai"
__all__ = []

View File

@@ -1,289 +0,0 @@
2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api...
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方APIskill_id: 2058819964077572098
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据:
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: <class 'dict'>
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: {
"datasourceId": "240",
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。",
"sqlTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"parameters": {},
"testParams": {}
}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 240
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:144] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:153] - 正在调用测试SQL API: http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema
2026-05-25 16:05:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 "
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - test_sql_with_schema 接口返回的数据:
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - HTTP状态码: 200
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:173] - 响应数据类型: <class 'dict'>
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应数据内容: {
"msg": "操作成功",
"code": 200,
"data": {
"resultCount": 6,
"data": [
{
"记录ID": 80001,
"任务ID": "3320260418001",
"原始数据": "{\"name\": \"张三\", \"mobile\": \"13800000101\", \"dept\": \"销售部\", \"amount\": 1200}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:19.000+08:00"
},
{
"记录ID": 80002,
"任务ID": "3320260418002",
"原始数据": "{\"name\": \"李四\", \"mobile\": \"13800000102\", \"dept\": \"财务部\", \"amount\": 980}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:34.000+08:00"
},
{
"记录ID": 80003,
"任务ID": "3320260418003",
"原始数据": "{\"name\": \"王五\", \"mobile\": \"13800000103\", \"dept\": \"技术部\", \"amount\": 1500}",
"处理结果": "导入失败",
"错误原因": "字段校验失败mobile格式不正确",
"处理状态": "FAILED",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:49.000+08:00"
},
{
"记录ID": 80004,
"任务ID": "3320260418004",
"原始数据": "{\"name\": \"赵六\", \"mobile\": \"13800000104\", \"dept\": \"人事部\", \"amount\": 0}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:04.000+08:00"
},
{
"记录ID": 80005,
"任务ID": "3320260418005",
"原始数据": "{\"name\": \"孙七\", \"mobile\": \"13800000105\", \"dept\": \"运营部\", \"amount\": 2000}",
"处理结果": "导入失败",
"错误原因": "目标表写入失败:唯一键冲突",
"处理状态": "FAILED",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:19.000+08:00"
},
{
"记录ID": 80006,
"任务ID": "3320260418006",
"原始数据": "{\"name\": \"周八\", \"mobile\": \"13800000106\", \"dept\": \"法务部\", \"amount\": 300}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:34.000+08:00"
}
],
"databaseName": "import_log_db_168",
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。",
"originalTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"convertedTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"executionStatus": "success",
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
"testParams": {},
"errorMessage": null,
"executionTime": 8,
"datasourceId": "240",
"logId": "2058821884796174336",
"executableSql": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"datasourceName": "import_log_db_168"
}
}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应code: 200
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - 响应msg: 操作成功
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:178] - 响应data: {'resultCount': 6, 'data': [{'记录ID': 80001, '任务ID': '3320260418001', '原始数据': '{"name": "张三", "mobile": "13800000101", "dept": "销售部", "amount": 1200}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:19.000+08:00'}, {'记录ID': 80002, '任务ID': '3320260418002', '原始数据': '{"name": "李四", "mobile": "13800000102", "dept": "财务部", "amount": 980}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:34.000+08:00'}, {'记录ID': 80003, '任务ID': '3320260418003', '原始数据': '{"name": "王五", "mobile": "13800000103", "dept": "技术部", "amount": 1500}', '处理结果': '导入失败', '错误原因': '字段校验失败mobile格式不正确', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:49.000+08:00'}, {'记录ID': 80004, '任务ID': '3320260418004', '原始数据': '{"name": "赵六", "mobile": "13800000104", "dept": "人事部", "amount": 0}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:04.000+08:00'}, {'记录ID': 80005, '任务ID': '3320260418005', '原始数据': '{"name": "孙七", "mobile": "13800000105", "dept": "运营部", "amount": 2000}', '处理结果': '导入失败', '错误原因': '目标表写入失败:唯一键冲突', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:19.000+08:00'}, {'记录ID': 80006, '任务ID': '3320260418006', '原始数据': '{"name": "周八", "mobile": "13800000106", "dept": "法务部", "amount": 300}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:34.000+08:00'}], 'databaseName': 'import_log_db_168', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'originalTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'convertedTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'executionStatus': 'success', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'testParams': {}, 'errorMessage': None, 'executionTime': 8, 'datasourceId': '240', 'logId': '2058821884796174336', 'executableSql': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'datasourceName': 'import_log_db_168'}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:181] - 测试SQL API调用成功
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功
2026-05-25 16:15:08 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:15:08 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:15:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id:
2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
with map_exceptions(exc_map):
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
raise to_exc(exc) from exc
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
response = self.client.get(url, headers=self._get_headers())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request(
^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
response = self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions():
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
raise Exception(error_msg)
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具

View File

@@ -1,289 +0,0 @@
2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api...
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方APIskill_id: 2058819964077572098
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据:
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: <class 'dict'>
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: {
"datasourceId": "240",
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。",
"sqlTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"parameters": {},
"testParams": {}
}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 240
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:144] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:153] - 正在调用测试SQL API: http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema
2026-05-25 16:05:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 "
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - test_sql_with_schema 接口返回的数据:
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - HTTP状态码: 200
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:173] - 响应数据类型: <class 'dict'>
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应数据内容: {
"msg": "操作成功",
"code": 200,
"data": {
"resultCount": 6,
"data": [
{
"记录ID": 80001,
"任务ID": "3320260418001",
"原始数据": "{\"name\": \"张三\", \"mobile\": \"13800000101\", \"dept\": \"销售部\", \"amount\": 1200}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:19.000+08:00"
},
{
"记录ID": 80002,
"任务ID": "3320260418002",
"原始数据": "{\"name\": \"李四\", \"mobile\": \"13800000102\", \"dept\": \"财务部\", \"amount\": 980}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:34.000+08:00"
},
{
"记录ID": 80003,
"任务ID": "3320260418003",
"原始数据": "{\"name\": \"王五\", \"mobile\": \"13800000103\", \"dept\": \"技术部\", \"amount\": 1500}",
"处理结果": "导入失败",
"错误原因": "字段校验失败mobile格式不正确",
"处理状态": "FAILED",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:49.000+08:00"
},
{
"记录ID": 80004,
"任务ID": "3320260418004",
"原始数据": "{\"name\": \"赵六\", \"mobile\": \"13800000104\", \"dept\": \"人事部\", \"amount\": 0}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:04.000+08:00"
},
{
"记录ID": 80005,
"任务ID": "3320260418005",
"原始数据": "{\"name\": \"孙七\", \"mobile\": \"13800000105\", \"dept\": \"运营部\", \"amount\": 2000}",
"处理结果": "导入失败",
"错误原因": "目标表写入失败:唯一键冲突",
"处理状态": "FAILED",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:19.000+08:00"
},
{
"记录ID": 80006,
"任务ID": "3320260418006",
"原始数据": "{\"name\": \"周八\", \"mobile\": \"13800000106\", \"dept\": \"法务部\", \"amount\": 300}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:34.000+08:00"
}
],
"databaseName": "import_log_db_168",
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。",
"originalTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"convertedTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"executionStatus": "success",
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
"testParams": {},
"errorMessage": null,
"executionTime": 8,
"datasourceId": "240",
"logId": "2058821884796174336",
"executableSql": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"datasourceName": "import_log_db_168"
}
}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应code: 200
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - 响应msg: 操作成功
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:178] - 响应data: {'resultCount': 6, 'data': [{'记录ID': 80001, '任务ID': '3320260418001', '原始数据': '{"name": "张三", "mobile": "13800000101", "dept": "销售部", "amount": 1200}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:19.000+08:00'}, {'记录ID': 80002, '任务ID': '3320260418002', '原始数据': '{"name": "李四", "mobile": "13800000102", "dept": "财务部", "amount": 980}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:34.000+08:00'}, {'记录ID': 80003, '任务ID': '3320260418003', '原始数据': '{"name": "王五", "mobile": "13800000103", "dept": "技术部", "amount": 1500}', '处理结果': '导入失败', '错误原因': '字段校验失败mobile格式不正确', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:49.000+08:00'}, {'记录ID': 80004, '任务ID': '3320260418004', '原始数据': '{"name": "赵六", "mobile": "13800000104", "dept": "人事部", "amount": 0}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:04.000+08:00'}, {'记录ID': 80005, '任务ID': '3320260418005', '原始数据': '{"name": "孙七", "mobile": "13800000105", "dept": "运营部", "amount": 2000}', '处理结果': '导入失败', '错误原因': '目标表写入失败:唯一键冲突', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:19.000+08:00'}, {'记录ID': 80006, '任务ID': '3320260418006', '原始数据': '{"name": "周八", "mobile": "13800000106", "dept": "法务部", "amount": 300}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:34.000+08:00'}], 'databaseName': 'import_log_db_168', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'originalTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'convertedTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'executionStatus': 'success', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'testParams': {}, 'errorMessage': None, 'executionTime': 8, 'datasourceId': '240', 'logId': '2058821884796174336', 'executableSql': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'datasourceName': 'import_log_db_168'}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:181] - 测试SQL API调用成功
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功
2026-05-25 16:15:08 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:15:08 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:15:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id:
2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
with map_exceptions(exc_map):
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
raise to_exc(exc) from exc
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
response = self.client.get(url, headers=self._get_headers())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request(
^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
response = self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions():
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
raise Exception(error_msg)
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具

View File

@@ -1,73 +0,0 @@
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
with map_exceptions(exc_map):
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
raise to_exc(exc) from exc
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
response = self.client.get(url, headers=self._get_headers())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request(
^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
response = self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions():
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
raise Exception(error_msg)
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed

View File

@@ -1,149 +0,0 @@
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api...
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方APIskill_id: 2058819964077572098
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id:
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
with map_exceptions(exc_map):
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
raise to_exc(exc) from exc
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
response = self.client.get(url, headers=self._get_headers())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request(
^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
response = self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions():
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
raise Exception(error_msg)
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具

View File

@@ -1,268 +0,0 @@
from pathlib import Path
from typing import Any
import asyncio
import json
import logging
try:
from .utils import load_json, generate_input_schema
from .utils import get_skill_by_id, process_skill_response, test_sql_with_schema
from .utils import get_skill_id, get_env_config
from .utils.logger_config import logger_config
except ImportError:
from utils import load_json, generate_input_schema
from utils import get_skill_by_id, process_skill_response, test_sql_with_schema
from utils import get_skill_id, get_env_config
from utils.logger_config import logger_config
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
import mcp.types as types
mcp_logger = logger_config.setup_mcp_logging()
DATA_SOURCE_API = "api"
DATA_SOURCE_LOCAL = "local"
DATA_SOURCE_BOTH = "both"
DEFAULT_DATA_SOURCE = DATA_SOURCE_API
def _text_response(payload: dict[str, Any]) -> list[types.TextContent]:
"""Build a JSON text response."""
return [
types.TextContent(
type="text",
text=json.dumps(payload, ensure_ascii=False, indent=2)
)
]
def _extract_user_id(arguments: dict[str, Any]) -> Any:
"""Allow numeric 0, reject None and blank strings."""
user_id = arguments.get("userId")
if user_id is None:
return None
if isinstance(user_id, str) and not user_id.strip():
return None
return user_id
def get_queries():
"""Read local business query config."""
try:
current_dir = Path(__file__).parent
json_path = current_dir / "businessQueries.json"
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
queries = load_json(json_path)
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
return queries
except Exception as e:
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
raise
def generate_tool_schema_from_query(query: dict) -> types.Tool:
"""Generate an MCP tool definition from one query config."""
try:
parameters = query.get("parameters", {})
input_schema = generate_input_schema(parameters)
tool_name = query["businessName"]
description = f"{query['businessName']}: {query['businessDescription']}"
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
return types.Tool(
name=tool_name,
description=description,
inputSchema=input_schema
)
except Exception as e:
mcp_logger.error(
f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}",
exc_info=True
)
raise
server = Server("lzwcai-mcp-sqlexecutor")
_queries_cache = None
async def get_queries_cache(source: str = None):
"""Get or initialize query config cache."""
global _queries_cache
if _queries_cache is None:
source = source or DEFAULT_DATA_SOURCE
mcp_logger.info(f"初始化查询配置,数据源: {source}")
if source == DATA_SOURCE_LOCAL:
_queries_cache = get_queries()
mcp_logger.info(f"本地配置: {len(_queries_cache)}")
elif source == DATA_SOURCE_API:
try:
_queries_cache = await call_third_party_api()
mcp_logger.info(f"API 配置: {len(_queries_cache)}")
mcp_logger.info(f"API 配置数组: {_queries_cache}")
except Exception as e:
mcp_logger.warning(f"API 获取失败,降级使用本地配置: {e}")
_queries_cache = get_queries()
else:
local = get_queries()
try:
api = await call_third_party_api()
except Exception as e:
mcp_logger.warning(f"API 获取失败: {e}")
api = []
_queries_cache = local + api
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条,本地 {len(local)} + API {len(api)}")
return _queries_cache
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""List all dynamically generated MCP tools."""
try:
mcp_logger.info("收到列出工具请求")
queries = await get_queries_cache()
tools = [generate_tool_schema_from_query(query) for query in queries]
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
return tools
except Exception as e:
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
raise
@server.call_tool()
async def handle_call_tool(
name: str,
arguments: dict[str, Any] | None
) -> list[types.TextContent]:
"""Handle MCP tool invocation."""
try:
mcp_logger.info(f"收到工具调用请求: {name}")
mcp_logger.debug(f"工具参数: {arguments}")
queries = await get_queries_cache()
tool_item = next((query for query in queries if query["businessName"] == name), None)
if not tool_item:
return _text_response({"error": f"未找到工具 {name} 对应的配置"})
normalized_parameters = generate_input_schema(tool_item.get("parameters"))
normalized_arguments = arguments or {}
user_id = _extract_user_id(normalized_arguments)
if user_id is None:
error_msg = f"工具 {name} 缺少必填参数 userId"
mcp_logger.warning(error_msg)
return _text_response({"error": error_msg, "required": ["userId"]})
request_data = {
"datasourceId": tool_item.get("datasourceId"),
"businessName": tool_item.get("businessName"),
"businessDescription": tool_item.get("businessDescription"),
"sqlTemplate": tool_item.get("sqlTemplate"),
"parameters": normalized_parameters,
"testParams": normalized_arguments,
"userId": user_id
}
target_database_name = normalized_arguments.get("targetDatabaseName")
if target_database_name:
request_data["targetDatabaseName"] = target_database_name
mcp_logger.debug(f"添加目标数据库名称: {target_database_name}")
try:
mcp_logger.info("正在调用测试 SQL API...")
result_payload = test_sql_with_schema(request_data)
mcp_logger.info("测试 SQL API 调用成功")
except Exception as e:
error_msg = f"调用测试 SQL API 失败: {str(e)}"
mcp_logger.error(error_msg, exc_info=True)
result_payload = {"error": error_msg}
mcp_logger.debug(f"工具调用结果: {result_payload}")
return _text_response(result_payload)
except Exception as e:
error_msg = f"工具调用失败: {name}, 错误: {e}"
mcp_logger.error(error_msg, exc_info=True)
return [types.TextContent(type="text", text=f"错误: {error_msg}")]
async def call_third_party_api(skill_id: str = None) -> list:
"""Call backend API and map the response into query configs."""
try:
if skill_id is None:
skill_id = get_skill_id()
mcp_logger.info(f"调用第三方 APIskill_id: {skill_id}")
raw_result = get_skill_by_id(skill_id)
mcp_logger.info(f"成功获取原始响应: {raw_result}")
processed_queries = process_skill_response(raw_result)
mcp_logger.info(f"成功处理 {len(processed_queries)} 条数据")
return processed_queries
except Exception as e:
mcp_logger.error(f"API 调用失败: {e}", exc_info=True)
raise
async def async_main():
"""Async entry for the MCP server."""
try:
mcp_logger.info("=" * 60)
mcp_logger.info("正在启动 MCP 服务: lzwcai-mcp-sqlexecutor")
mcp_logger.info("版本: 0.1.0")
mcp_logger.info("=" * 60)
env_config = get_env_config()
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}")
mcp_logger.info("=" * 60)
from mcp.server.stdio import stdio_server
async with stdio_server() as (read_stream, write_stream):
mcp_logger.info("MCP 服务已启动,等待客户端连接...")
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="lzwcai-mcp-sqlexecutor",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
mcp_logger.info("MCP 服务已关闭")
except Exception as e:
mcp_logger.error(f"MCP 服务运行失败: {e}", exc_info=True)
raise
def main():
"""Console entrypoint."""
try:
logger_config.setup_logging(
app_name="lzwcai_mcp_sqlexecutor",
log_level=logging.INFO,
console_output=False
)
mcp_logger.info("开始运行 MCP SQL Executor 服务")
asyncio.run(async_main())
except KeyboardInterrupt:
mcp_logger.info("收到中断信号,正在关闭服务...")
except Exception as e:
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
raise
if __name__ == "__main__":
main()

View File

@@ -1,35 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcp-sqlexecutor"
version = "0.1.6"
description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "executor", "server"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcp-sqlexecutor = "lzwcai_mcp_sqlexecutor.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcp_sqlexecutor"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcp_sqlexecutor/businessQueries.json" = "lzwcai_mcp_sqlexecutor/businessQueries.json"

View File

@@ -1,24 +0,0 @@
"""Utils package for lzwcai_mcp_sqlexecutor"""
from .json_helper import load_json
from .name_helper import generate_tool_name
from .schema_helper import generate_input_schema, validate_input_schema
from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema
from .env_config import get_database_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable
__all__ = [
'load_json',
'generate_tool_name',
'generate_input_schema',
'validate_input_schema',
'DataSourceAPIClient',
'get_skill_by_id',
'process_skill_response',
'test_sql_with_schema',
'get_database_id',
'get_skill_id',
'get_backend_base_url',
'get_env_config',
'set_env_variable'
]

View File

@@ -1,209 +0,0 @@
"""Backend API client helpers."""
import json
import logging
from typing import Any, Dict, List, Optional
import httpx
try:
from .env_config import get_backend_base_url
except ImportError:
from env_config import get_backend_base_url
logger = logging.getLogger(__name__)
class DataSourceAPIClient:
"""HTTP client for backend datasource APIs."""
def __init__(
self,
base_url: Optional[str] = None,
token: Optional[str] = None
):
if base_url is None:
base_url = get_backend_base_url()
self.base_url = base_url.rstrip("/")
self.token = token or (
"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1i"
"ZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dn"
"vqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw"
)
self.client = httpx.Client(timeout=30.0)
def _get_headers(self) -> Dict[str, str]:
return {
"Authorization": f"Bearer {self.token}",
}
def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]:
"""Fetch skill details by skill id."""
try:
url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}"
logger.info(f"正在调用 API: {url}")
logger.debug(f"请求参数 - skill_id: {skill_id}")
response = self.client.get(url, headers=self._get_headers())
response.raise_for_status()
data = response.json()
logger.info(f"API 调用成功: {url}")
logger.debug(f"响应数据: {data}")
return data
except httpx.TimeoutException:
error_msg = f"API 请求超时: {url}"
logger.error(error_msg)
raise Exception(error_msg)
except httpx.HTTPStatusError as e:
error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg)
except httpx.RequestError as e:
error_msg = f"API 请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg)
raise Exception(error_msg)
except Exception as e:
error_msg = f"处理 API 响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True)
raise Exception(error_msg)
def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
"""Call backend SQL test endpoint."""
try:
logger.info("=" * 80)
logger.info("test_sql_with_schema 接口接收到的数据:")
logger.info(f"数据类型: {type(request_data)}")
logger.info(f"数据内容: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
logger.info(f"数据源 ID: {request_data.get('datasourceId')}")
logger.info(f"业务名称: {request_data.get('businessName')}")
logger.info(f"业务描述: {request_data.get('businessDescription')}")
logger.info(f"SQL 模板: {request_data.get('sqlTemplate')}")
logger.info(f"参数定义: {request_data.get('parameters')}")
logger.info(f"测试参数: {request_data.get('testParams')}")
if "userId" in request_data:
logger.info(f"用户 ID: {request_data.get('userId')}")
logger.info("=" * 80)
url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema"
headers = self._get_headers()
headers["Content-Type"] = "application/json"
headers["Accept"] = "*/*"
logger.info(f"正在调用测试 SQL API: {url}")
logger.debug(f"请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
response = self.client.post(url, headers=headers, json=request_data)
response.raise_for_status()
data = response.json()
logger.info("=" * 80)
logger.info("test_sql_with_schema 接口返回的数据:")
logger.info(f"HTTP 状态码: {response.status_code}")
logger.info(f"响应数据类型: {type(data)}")
logger.info(f"响应数据内容: {json.dumps(data, ensure_ascii=False, indent=2)}")
if isinstance(data, dict):
logger.info(f"响应 code: {data.get('code')}")
logger.info(f"响应 msg: {data.get('msg')}")
logger.info(f"响应 data: {data.get('data')}")
logger.info("=" * 80)
logger.info("测试 SQL API 调用成功")
return data
except httpx.TimeoutException:
error_msg = f"测试 SQL API 请求超时: {url}"
logger.error(error_msg)
raise Exception(error_msg)
except httpx.HTTPStatusError as e:
error_msg = f"测试 SQL API 请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg)
except httpx.RequestError as e:
error_msg = f"测试 SQL API 请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg)
raise Exception(error_msg)
except Exception as e:
error_msg = f"处理测试 SQL API 响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True)
raise Exception(error_msg)
def close(self):
"""Close the underlying HTTP client."""
self.client.close()
default_client = DataSourceAPIClient()
def get_skill_by_id(
skill_id: str,
base_url: Optional[str] = None,
token: Optional[str] = None
) -> Dict[str, Any]:
"""Convenience wrapper for skill lookup."""
if base_url or token:
client = DataSourceAPIClient(base_url=base_url, token=token)
return client.get_skill_by_id(skill_id)
return default_client.get_skill_by_id(skill_id)
def test_sql_with_schema(
request_data: Dict[str, Any],
base_url: Optional[str] = None,
token: Optional[str] = None
) -> Dict[str, Any]:
"""Convenience wrapper for SQL test endpoint."""
if base_url or token:
client = DataSourceAPIClient(base_url=base_url, token=token)
return client.test_sql_with_schema(request_data)
return default_client.test_sql_with_schema(request_data)
def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Map backend skill response into business query configs."""
try:
data_list = response.get("data", [])
queries = []
for skill in data_list:
sql_params_raw = skill.get("sqlParams")
sql_params: Dict[str, Any] = {}
if isinstance(sql_params_raw, dict):
sql_params = sql_params_raw
elif isinstance(sql_params_raw, str) and sql_params_raw.strip():
try:
parsed_sql_params = json.loads(sql_params_raw)
if isinstance(parsed_sql_params, dict):
sql_params = parsed_sql_params
else:
logger.warning(
f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是对象,已回退为空对象"
)
except json.JSONDecodeError:
logger.warning(
f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是合法 JSON已回退为空对象"
)
query = {
"id": skill.get("id"),
"businessName": skill.get("name"),
"businessDescription": skill.get("description"),
"sqlTemplate": skill.get("sqlTemplate"),
"parameters": sql_params,
"datasourceId": skill.get("datasourceId"),
}
queries.append(query)
logger.info(f"成功处理 {len(queries)} 条技能数据")
return queries
except Exception as e:
logger.error(f"处理 API 响应数据失败: {e}", exc_info=True)
raise

View File

@@ -1,87 +0,0 @@
"""环境变量配置模块"""
import os
def get_database_id(default: str = "29") -> str:
"""
获取数据库ID环境变量
Args:
default: 默认值(默认为 "29"
Returns:
str: 数据库ID
Environment Variables:
databaseId: 数据库ID
"""
return os.environ.get("databaseId", default)
def get_skill_id(default: str = "") -> str:
"""
获取技能ID环境变量
Args:
default: 默认值(默认为 ""
Returns:
str: 技能ID
Environment Variables:
skillId: 技能ID
"""
return os.environ.get("skillId", default)
def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str:
"""
获取后端API基础URL环境变量
Args:
default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086"
Returns:
str: 后端API基础URL
Environment Variables:
backendBaseUrl: 后端API基础URL
"""
return os.environ.get("backendBaseUrl", default)
def get_env_config() -> dict:
"""
获取所有环境配置
Returns:
dict: 包含所有配置的字典
Example:
config = get_env_config()
print(config['database_id']) # 输出: "29"
print(config['skill_id']) # 输出: ""
print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086"
"""
return {
"database_id": get_database_id(),
"skill_id": get_skill_id(),
"backend_base_url": get_backend_base_url()
}
def set_env_variable(key: str, value: str) -> None:
"""
设置环境变量(仅在当前进程中有效)
Args:
key: 环境变量名
value: 环境变量值
Example:
set_env_variable("databaseId", "30")
set_env_variable("skillId", "1234567890")
"""
os.environ[key] = value

View File

@@ -1,60 +0,0 @@
"""JSON 文件读取工具"""
import json
from pathlib import Path
from typing import Any, Union
def load_json(json_path: Union[str, Path]) -> Any:
"""
读取 JSON 文件并返回其内容
Args:
json_path: JSON 文件的路径(支持字符串或 Path 对象)
Returns:
JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型)
Raises:
FileNotFoundError: 当文件不存在时
json.JSONDecodeError: 当 JSON 格式无效时
Exception: 其他读取错误
Example:
>>> data = load_json('config.json')
>>> print(data)
{'key': 'value'}
>>> data = load_json(Path('data/users.json'))
>>> print(data)
[{'id': 1, 'name': 'Alice'}]
"""
try:
# 转换为 Path 对象
path = Path(json_path)
# 检查文件是否存在
if not path.exists():
raise FileNotFoundError(f"JSON 文件不存在: {json_path}")
# 检查是否为文件
if not path.is_file():
raise ValueError(f"路径不是一个文件: {json_path}")
# 读取并解析 JSON 文件
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
except json.JSONDecodeError as e:
raise json.JSONDecodeError(
f"JSON 格式错误: {e.msg}",
e.doc,
e.pos
)
except FileNotFoundError:
raise
except Exception as e:
raise Exception(f"读取 JSON 文件时发生错误: {str(e)}")

View File

@@ -1,489 +0,0 @@
# -*- coding: utf-8 -*-
"""
统一日志配置模块
提供系统级别的日志配置和管理
"""
import os
import sys
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from datetime import datetime
from pathlib import Path
class LoggerConfig:
"""日志配置管理类"""
def __init__(self, logs_dir: str = None):
"""初始化日志配置
Args:
logs_dir: 日志目录路径默认为项目根目录下的logs文件夹
"""
# 确定日志目录
if logs_dir:
self.logs_dir = Path(logs_dir)
else:
# 获取项目根目录logger_config.py 在 utils 目录下,需要上升两层到达项目根目录)
project_root = Path(__file__).parent.parent
self.logs_dir = project_root / "logs"
# 创建日志目录(包括父目录)
self.logs_dir.mkdir(parents=True, exist_ok=True)
# 日志格式
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
self.date_format = '%Y-%m-%d %H:%M:%S'
# 从环境变量获取日志级别默认为INFO
self.log_level = self._get_log_level_from_env()
# 是否已初始化
self._initialized = False
def _get_log_level_from_env(self) -> int:
"""从环境变量获取日志级别
Returns:
int: 日志级别
"""
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
# 日志级别映射
level_mapping = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'WARN': logging.WARNING, # 兼容性别名
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
'FATAL': logging.CRITICAL # 兼容性别名
}
return level_mapping.get(log_level_str, logging.INFO)
def setup_logging(self,
app_name: str = "lzwcai_mcp_sqlexecutor",
log_level: int = logging.INFO,
max_file_size: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5,
console_output: bool = True) -> logging.Logger:
"""设置系统日志配置
Args:
app_name: 应用名称,用于日志文件命名
log_level: 日志级别
max_file_size: 单个日志文件最大大小(字节)
backup_count: 保留的备份文件数量
console_output: 是否输出到控制台
Returns:
logging.Logger: 配置好的根日志器
"""
if self._initialized:
return logging.getLogger()
# 设置根日志器
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# 清除现有的处理器
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# 创建格式化器
formatter = logging.Formatter(self.log_format, self.date_format)
# 1. 主日志文件 - 按大小滚动
main_log_file = self.logs_dir / f"{app_name}.log"
file_handler = RotatingFileHandler(
main_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setLevel(log_level)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
# 2. 错误日志文件 - 只记录ERROR及以上级别
error_log_file = self.logs_dir / f"{app_name}_error.log"
error_handler = RotatingFileHandler(
error_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
root_logger.addHandler(error_handler)
# 3. 按日期滚动的日志文件
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
daily_handler = TimedRotatingFileHandler(
daily_log_file,
when='midnight',
interval=1,
backupCount=30, # 保留30天
encoding='utf-8'
)
daily_handler.setLevel(log_level)
daily_handler.setFormatter(formatter)
daily_handler.suffix = "%Y-%m-%d"
root_logger.addHandler(daily_handler)
# 4. 控制台输出
# 重要MCP协议使用stdio时必须将日志输出到stderrstdout仅用于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

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
"""
名称生成工具模块
"""
from pypinyin import lazy_pinyin, Style
import logging
logger = logging.getLogger(__name__)
def generate_tool_name(business_name: str, tool_id: str) -> str:
"""
根据业务名称和ID生成工具名称
格式: tool_拼音_id
Args:
business_name: 业务名称(中文)
tool_id: 工具ID
Returns:
str: 格式化的工具名称
"""
try:
# 将中文转换为拼音(无音调,小写)
pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL)
# 拼接拼音
pinyin_str = ''.join(pinyin_list)
# 将 ID 中的 '-' 替换为 '_'
formatted_id = tool_id.replace('-', '_')
# 组合成最终的工具名称
tool_name = f"tool_{pinyin_str}_{formatted_id}"
return tool_name
except Exception as e:
logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True)
# 降级处理:如果拼音转换失败,使用 ID
return f"tool_{tool_id.replace('-', '_')}"

View File

@@ -1,93 +0,0 @@
# -*- coding: utf-8 -*-
"""
Schema 生成工具模块。
"""
from copy import deepcopy
from typing import Any, Dict
def _normalize_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]:
"""将任意参数定义归一化为 object schema。"""
if not isinstance(parameters, dict) or not parameters:
return {
"type": "object",
"properties": {},
"required": []
}
input_schema = deepcopy(parameters)
input_schema["type"] = "object"
if not isinstance(input_schema.get("properties"), dict):
input_schema["properties"] = {}
if not isinstance(input_schema.get("required"), list):
input_schema["required"] = []
return input_schema
def generate_input_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]:
"""
从查询配置的参数定义生成 MCP 工具的 inputSchema。
会统一补齐:
- `targetDatabaseName`:可选
- `userId`:必填
"""
input_schema = _normalize_schema(parameters)
if "targetDatabaseName" not in input_schema["properties"]:
input_schema["properties"]["targetDatabaseName"] = {
"type": "string",
"description": "目标数据库名称",
"default": ""
}
if "userId" not in input_schema["properties"]:
input_schema["properties"]["userId"] = {
"type": "string",
"description": "当前 AI 平台用户 ID"
}
if "userId" not in input_schema["required"]:
input_schema["required"].append("userId")
return input_schema
def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
"""
验证 inputSchema 是否符合基本的 JSON Schema 规范。
"""
if not isinstance(schema, dict):
return False, "Schema 必须是一个字典对象"
if schema.get("type") != "object":
return False, "Schema 的 type 字段必须是 'object'"
if "properties" not in schema:
return False, "Schema 必须包含 properties 字段"
if not isinstance(schema.get("properties"), dict):
return False, "Schema 的 properties 字段必须是一个字典对象"
if "required" in schema:
required = schema["required"]
if not isinstance(required, list):
return False, "Schema 的 required 字段必须是一个列表"
properties = schema["properties"]
for field in required:
if field not in properties:
return False, f"必填字段 '{field}' 未在 properties 中定义"
for prop_name, prop_def in schema["properties"].items():
if not isinstance(prop_def, dict):
return False, f"属性 '{prop_name}' 的定义必须是一个字典对象"
if "type" not in prop_def:
return False, f"属性 '{prop_name}' 必须包含 type 字段"
return True, "Schema 验证通过"

View File

@@ -1,497 +0,0 @@
version = 1
revision = 2
requires-python = ">=3.13"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" },
]
[[package]]
name = "anyio"
version = "4.11.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" },
]
[[package]]
name = "attrs"
version = "25.4.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" },
]
[[package]]
name = "certifi"
version = "2025.10.5"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" },
]
[[package]]
name = "click"
version = "8.3.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" },
]
[[package]]
name = "httpx-sse"
version = "0.4.3"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" },
]
[[package]]
name = "jsonschema"
version = "4.25.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "referencing" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" },
]
[[package]]
name = "lzwcai-mcp-sqlexecutor"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "httpx" },
{ name = "mcp", extra = ["cli"] },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.10.1" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" },
]
[[package]]
name = "mcp"
version = "1.10.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" },
]
[package.optional-dependencies]
cli = [
{ name = "python-dotenv" },
{ name = "typer" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" },
]
[[package]]
name = "pydantic"
version = "2.12.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" },
]
[[package]]
name = "pydantic-core"
version = "2.41.4"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" },
]
[[package]]
name = "pydantic-settings"
version = "2.11.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" },
]
[[package]]
name = "python-dotenv"
version = "1.1.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" },
]
[[package]]
name = "referencing"
version = "0.37.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" },
]
[[package]]
name = "rpds-py"
version = "0.27.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" },
]
[[package]]
name = "sse-starlette"
version = "3.0.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" },
]
[[package]]
name = "starlette"
version = "0.48.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" },
]
[[package]]
name = "typer"
version = "0.19.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" },
]
[[package]]
name = "uvicorn"
version = "0.37.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" },
]

View File

@@ -1,20 +0,0 @@
"""
Repository-local launcher for lzwcai-mcp-sqlexecutor.
"""
import os
def main():
# Keep local developer defaults without overriding explicit environment settings.
os.environ.setdefault("databaseId", "240")
os.environ.setdefault("skillId", "2058819964077572098")
os.environ.setdefault("backendBaseUrl", "http://192.168.2.236:8088")
from lzwcai_mcp_sqlexecutor.main import main as package_main
package_main()
if __name__ == "__main__":
main()

View File

@@ -1,38 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcp-sqlexecutor"
version = "0.1.13"
description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "executor", "server"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcp-sqlexecutor = "lzwcai_mcp_sqlexecutor.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcp_sqlexecutor"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcp_sqlexecutor/businessQueries.json" = "lzwcai_mcp_sqlexecutor/businessQueries.json"

View File

@@ -1,842 +0,0 @@
version = 1
revision = 2
requires-python = ">=3.10"
[[package]]
name = "annotated-doc"
version = "0.0.4"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/fbc/da96e87e9c92a/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/571/ac1dc6991c450/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" },
]
[[package]]
name = "anyio"
version = "4.13.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "idna" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/334/b70e641fd2221/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/08b/310f9e24a9594/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708" },
]
[[package]]
name = "attrs"
version = "26.1.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/d03/ceb89cb322a8f/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/c64/7aa4a12dfbad9/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309" },
]
[[package]]
name = "certifi"
version = "2026.5.20"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/69d/ea482ab64caa7/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/3c5/2e209ba0a4ad7/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897" },
]
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/44d/1b5909021139f/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/0cf/2d91ecc3fcc06/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f73/b96c41e3b2ade/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/53f/77cbe57044e88/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3e8/37e3695668847/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5ed/a85d6d1879e69/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/933/2088d75dc3241/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fc7/de24befaeae77/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/cf3/64028c016c030/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e11/e82b744887154/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8ea/985900c5c95ce/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1f7/2fb8906754ac8/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b18/a3ed7d5b3bd8d/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b4c/854ef3adc1779/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/2de/9a304e27f7596/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/baf/5215e0ab74c16/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/730/cacb21e1bdff3/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/682/4f87845e33960/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9de/40a7b0323d889/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/894/1aaadaf672462/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a05/d0c237b334909/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/946/98a9c5f91f9d1/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5fe/d36fccc0612a5/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c64/9e3a33450ec82/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/66f/011380d0e49ed/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c66/38687455baf64/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6d0/2d6655b0e54f5/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8ec/a2a813c1cb7ad/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/21d/1152871b01940/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b21/e08af67b8a103/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1e3/a615586f05fc4/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/81a/fed14892743bb/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3e1/7ed538242334b/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/392/5dd22fa2b7699/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/2c8/f814d84194c9e/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/da9/02562c3e9c550/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/da6/8248800ad6320/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/467/1d9dd5ec934cb/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/00b/df7acc5f79515/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/45d/5e886156860dc/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/07b/271772c100085/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d48/a880098c96020/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f93/fd8e5c8c0a4aa/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dd4/f05f54a52fb55/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c8d/3b5532fc71b7a/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d9b/29c1f0ae438d5/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6d5/0360be4546678/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/74a/03b9698e198d4/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/19f/705ada2530c11/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/256/f80b80ca3853f/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fc3/3c5141b55ed36/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c65/4de545946e0db/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/24b/6f81f1983e6df/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/128/73ca6cb9b0f0d/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d9b/97165e8aed927/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/afb/8db5439b81cf9/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/737/fe7d37e1a1bff/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/381/00abb9d1b1435/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/087/067fa8953339c/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/203/a48d1fb583fc7/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dbd/5c7a25a7cb98f/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9a6/7fc9e8eb39039/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7a6/6c7204d886929/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7cc/09976e8b56f8c/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/92b/68146a71df785/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b1e/74d11748e7e98/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/28a/3a209b96630bc/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/755/3fb2090d71822/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6c6/c373cfc5c83a9/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1fc/9ea04857caf66/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d68/b6cef7827e864/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0a1/527a803f0a659/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9" },
]
[[package]]
name = "click"
version = "8.4.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/918/b5633eddf6b41/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/482/be17c6991b8c1/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
]
[[package]]
name = "cryptography"
version = "48.0.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/5c3/932f4436d1ccc/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/0c5/58d2cdffd8f4b/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f53/33311663ea94f/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/799/5ef305d7165c3/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/40b/a1f85eaa69598/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/369/a6348999f94bb/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a0e/692c683f4df67/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/183/49bbc56f4743c/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7e8/eac43dfca5c4c/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9cc/dac7d40688ecb/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/bd7/2e68b06bb1e96/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/59b/aa2cb386c4f0b/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/924/9e3cd978541d6/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9c4/59db21422be75/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5b0/12212e08b8dd5/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3cb/07a3ed6431663/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8c7/378637d7d8801/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/cc9/0c0b39b2e3c65/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/763/41972e1eff8b4/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/55b/7718303bf06a5/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a64/697c641c7b1b2/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/561/215ea3879cb1c/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ad6/4688338ed4bc1/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/906/cbf0670286c6e/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ea8/990436d914540/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c18/684a7f0cc9a3c/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9be/5aafa5736574f/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c17/dfe85494deaed/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/272/41b1dc9962e05/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/58d/00498e8933e4a/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/614/d0949f4790582/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7ce/4bfae76319a53/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/2eb/992bbd4661238/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/22a/5cb272895dce1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/2b4/d59804e8408e2/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/984/a20b0f62a26f4/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5a5/ed8fde7a1d093/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8cd/666227ef7af43/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/907/1196d81abc88b/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1e2/d54c8be615285/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a5d/a777e32ffed6f/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/77a/2ccbbe917f671/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/16c/d65b9330583e4/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/84c/f79f0dc8b36ac/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fdf/ef35d751d510f/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/089/0f502ddf7d9c6/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ecd/e28a596bead48/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4de/fde8685ae324a/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/db6/3bf618e5dea46/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/8b4/12432c6055b0b/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/a7a/39a3bd276781e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" },
]
[[package]]
name = "httpx-sse"
version = "0.4.3"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" },
]
[[package]]
name = "idna"
version = "3.16"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/d7a/6da03db833450/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/cc2/46e3a3f89580c/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5" },
]
[[package]]
name = "jsonschema"
version = "4.26.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/0c2/6707e2efad8aa/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/d48/9f15263b8d200/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "referencing" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" },
]
[[package]]
name = "lzwcai-mcp-sqlexecutor"
version = "0.1.11"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
{ name = "mcp", extra = ["cli"] },
{ name = "pypinyin" },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.10.1" },
{ name = "pypinyin", specifier = ">=0.53.0" },
]
[[package]]
name = "markdown-it-py"
version = "4.2.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/04a/21681d6fbb623/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/9f7/ebbcd14fe5949/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a" },
]
[[package]]
name = "mcp"
version = "1.27.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "python-multipart" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/0f4/7e1820f8f8f94/mcp-1.27.1.tar.gz", hash = "sha256:0f47e1820f8f8f941466b39749eb1d1839a04caddca2bc60e9d46e8a99914924" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/1af/3c4203b329430/mcp-1.27.1-py3-none-any.whl", hash = "sha256:1af3c4203b329430fde7a87b4fcb6392a041f5cb851fd68fc674016ab4e7c06f" },
]
[package.optional-dependencies]
cli = [
{ name = "python-dotenv" },
{ name = "typer" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" },
]
[[package]]
name = "pycparser"
version = "3.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/600/f49d217304a59/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/b72/7414169a36b7d/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" },
]
[[package]]
name = "pydantic"
version = "2.13.4"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/c40/756b57adaa8b1/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/45a/282cde31d8082/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba" },
]
[[package]]
name = "pydantic-core"
version = "2.46.4"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/62f/875393d7f2708/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/a39/6dcc17e5a0b16/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/da4/b951fe36dc7c3/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/bb6/3e0198ca18aad/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f47/286a97f0bc9b8/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/905/a0ed8ea6f2d61/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ea7/93e075b70290d/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/395/aebd9183f9d11/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b07/8afbc25f3a143/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f74/7929cf940cddb/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/daa/27d92c36f2438/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/19e/51f073cd3df25/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c17/47f85cee84c26/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/2f8/4c03c8607173d/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/835/8a950c8909158/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0e9/6592440881c74/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e0d/65b8c354be7fb/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7bf/b192b3f4b9e8a/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/903/7063db01f09b0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fc0/10ab034c8c745/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8c5/dac79fa1614d1/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f9f/a868638bf362d/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/172/99feefe090f2c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4c6/3ebc82684aa89/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/aaa/2a54443eff195/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/18e/5ceec2ab67e6d/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a0f/62d0a58f4e7da/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/041/bde0a48fd37cf/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6f2/eeda33a839975/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/14f/4c5d6db102bd7/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/324/5406455a5d981/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/962/ccbab7b642487/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/823/3f2947cf85404/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3a2/33125ac121aa3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5b7/12b53160b79a5/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/940/1557acd873c3a/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/926/c9541b14b12b1/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/56c/b4851bcaf3d11/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c68/fcd102d71ea85/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b2f/69dec1725e79a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8d0/820e8192167f8/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fbd/b89b3e1c94a30/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9aa/768456404a8bf/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e9c/26f834c65f575/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4fc/73cb559bdb54b/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5d5/902252db0d3ce/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c94/f0688e7b8d0a6/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f02/7324c56cd5406/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e73/9fee756ba1010/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9d5/6801be94b86a9/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/241/2e734dcb48da1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/955/1187363ffc0de/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/018/6750b482eefa1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/585/5698a4856556d/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/cba/f13819775b7f7/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/633/147d34cf45504/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/82c/f530117216810/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9fa/8ae11da9e2b31/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6b3/ace8194b0e520/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/184/c081504d17f1c/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/428/e04521a40150c/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/23a/ce664830ee0bf/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ce5/c1d2a8b27468f/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/728/3d57845ecf5a1/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8da/afc69c93ee8a0/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/cd2/213145bcc2ba8/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7a5/f930472650a82/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c1b/3f518abeca3aa/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1a7/dd0b3ee80d901/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3fb/702cd90b0446a/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b84/58003118a712e/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/372/429a130e469c9/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/85b/b3611ff1802f3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/811/ff8e9c313ab42/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/bfe/c22eab3c8cc2c/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/af8/244b2bef6aaad/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5a4/330cdbc57162e/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/29c/61fc04a3d8401/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c50/f2528cf200c5e/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0cb/e8b01f948de42/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/617/d7e2ca7dcb8c5/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/702/7560ee9221164/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f99/626688942fb74/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fc3/e9034a63de20e/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/97e/7cf2be5c77b7d/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3bf/92c5d0e00fefa/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3ec/bc122d18468d0/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e84/6ae7835bf0703/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/210/8ba5c1c1eca18/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4fc/be087dbc2068a/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/14d/4edf427bdcf95/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0ce/40cd7b21210e9/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/908/84113d8b48f76/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/66c/e7632c22d837c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1d8/ba486450b14f3/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/300/9f12e4e90b7f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ad7/85e92e6dc634c/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/00c/603d540afdd6b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0c5/63b08bca408dc/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/db0/6ffe51636ffe9/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/133/878133d271ade/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9bc/519fbf2b75783/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c7a/7bd4e39e8e4c1/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d39/6ec2b979760aa/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/86e/1a4418c6cd97d/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d51/026d73fcfd936/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983" },
]
[[package]]
name = "pydantic-settings"
version = "2.14.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/e87/4d3bec7e787b0/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/6e3/c7edfd8277687/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/675/7cd03768053ff/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/81a/9e26dd42fd28a/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" },
]
[[package]]
name = "pyjwt"
version = "2.13.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/415/71c89ca91598c/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/66a/dcc2aff09b3f1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728" },
]
[package.optional-dependencies]
crypto = [
{ name = "cryptography" },
]
[[package]]
name = "pypinyin"
version = "0.55.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/b57/11b3a0c6f76e6/pypinyin-0.55.0.tar.gz", hash = "sha256:b5711b3a0c6f76e67408ec6b2e3c4987a3a806b7c528076e7c7b86fcf0eaa66b" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/d53/b1e8ad2cdb815/pypinyin-0.55.0-py2.py3-none-any.whl", hash = "sha256:d53b1e8ad2cdb815fb2cb604ed3123372f5a28c6f447571244aca36fc62a286f" },
]
[[package]]
name = "python-dotenv"
version = "1.2.2"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/2c3/71a91fbd7ba08/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/1d8/214789a24de45/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a" },
]
[[package]]
name = "python-multipart"
version = "0.0.29"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/643/e93849196645e/python_multipart-0.0.29.tar.gz", hash = "sha256:643e93849196645e2dbdd81a0f8829a23123ad7f797a84a364c6fb3563f18904" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/2dd/cc971cef26622/python_multipart-0.0.29-py3-none-any.whl", hash = "sha256:2ddcc971cef266225f54f552d8fa10bcfbb1f14446caec199060daac59ff2d69" },
]
[[package]]
name = "pywin32"
version = "311"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/d03/ff496d2a0cd4a/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/797/c277201785198/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/050/2d1facf1fed48/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/184/eb5e436dea364/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3ce/80b34b22b17cc/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a73/3f1388e1a842a/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/750/ec6e621af2b94/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b8c/095edad5c211f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e28/6f46a9a39c4a1/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f95/ba5a847cba10d/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/718/a38f7e5b058e7/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7b4/075d959648406/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b7a/2c10b93f89866/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3ac/a44c046bd2ed8/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a50/8e2d9025764a8/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42" },
]
[[package]]
name = "referencing"
version = "0.37.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" },
]
[[package]]
name = "rich"
version = "15.0.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/edd/07a4824c6b401/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/33b/d4ef74232fb73/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb" },
]
[[package]]
name = "rpds-py"
version = "0.30.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/dd8/ff7cf90014af0/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/679/ae98e00c0e8d6/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4cc/2206b76b4f576/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/389/a2d49eded1896/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/32c/8528634e1bf71/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f20/7f69853edd6f6/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/67b/02ec25ba7a9e8/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0c0/e95f6819a1996/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a45/2763cc5198f2f/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e0b/65193a413ccc9/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/858/738e9c32147f7/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/da2/79aa314f00acb/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7c6/4d38fb49b6cde/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6de/2a32a1665b932/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/172/6859cd0de969f/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a2b/ffea6a4ca9f01/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dc4/f992dfe1e2bc3/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/422/c3cb9856d80b0/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/07a/e8a593e1c3c6b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/12f/90dd7557b6bd5/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/99b/47d6ad9a6da00/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/33f/559f310450450/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/946/fe926af6e44f3/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/495/aeca4b93d465e/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d9a/0ca5da0386dee/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/8d6/d1cc13664ec13/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/389/6fa1be39912cf/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/55f/6602263220594/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a51/033ff701fca75/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/47b/0ef6231c58f50/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a16/1f20d9a430068/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6ab/c8880d9d036ec/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ca2/8829ae5f5d569/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a10/10ed9524c73b9/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f8d/1736cfb49381b/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d94/8b135c4693daf/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/47f/236970bccb223/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/2e6/ecb5a5bcacf59/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a8f/a71a2e078c527/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/73c/67f2db7bc334e/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5ba/103fb455be00f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7ce/e9c752c036458/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1ab/5b83dbcf55acc/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a09/0322ca841abd4/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/669/b1805bd639dd2/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f83/424d738204d97/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e75/36cd91353c527/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/277/1c6c15973347f/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0a5/9119fc6e3f460/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/76f/ec018282b4ead/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/692/bef75a5525db9/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/902/7da1ce107104c/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9cf/69cdda1f5968a/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a47/96a717bf12b9d/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/5d4/c2aa7c50ad472/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ba8/1a9203d078054/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/945/dccface01af02/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b40/fb160a2db369a/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/806/f36b1b605e2d6/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/d96/c2086587c7c30/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/eb0/b93f2e5c2189e/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/922/e10f31f303c7c/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/cdc/62c8286ba9bf7/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/47f/9a91efc418b54/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1f3/587eb9b17f378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/39c/02563fc592411/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/51a/1234d8febafdf/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/eb2/c4071ab598733/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/6bd/fdb946967d816/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c77/afbd5f5250bf2/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/610/46904275472a7/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4c5/f36a861bc4b7d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3d4/a69de7a3e50ff/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f14/fc5df50a716f7/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/68f/19c879420aa08/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ec7/c4490c672c1a0/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/f25/1c812357a3fed/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ac9/8b175585ecf4c/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3e6/2880792319dbe/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/4e7/fc54e0900ab35/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/47e/77dc9822d3ad6/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/b4d/c1a6ff022ff85/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/455/9c972db3a3608/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0ed/177ed9bded28f/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ad1/fa8db769b76ea/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/46e/83c697b1f1c72/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ee4/54b2a007d5736/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/95f/0802447ac2d10/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/613/aa4771c99f033/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/7e6/ecfcb62edfd63/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/a1d/0bc22a7cdc173/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/0d0/8f00679177226/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/596/5af57d5848192/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/9a4/e86e34e9ab6b6/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/e5d/3e6b26f2c785d/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/626/a7433c3456653/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/acd/7eb3f4471577b/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/fe5/fa731a1fa8a0a/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/74a/3243a41112636/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3e8/eeb0544f2eb0d/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dbd/936cde57abfee/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dc8/24125c72246d9/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/27f/4b0e92de5bfbc/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/c22/62bdba0ad4fc6/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ee6/af14263f25eed/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/3ad/bb8179ce342d2/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/250/fa00e9543ac9b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/985/4cf4f488b3d57/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/993/914b8e560023b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/58e/dca431fb9b299/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dea/5b552272a9447/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ba3/af48635eb83d0/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/dff/13836529b921e/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/1b1/51685b23929ab/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4" },
{ url = "http://192.168.2.236:3141/root/pypi/+f/ac3/7f9f516c51e57/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" },
]
[[package]]
name = "sse-starlette"
version = "3.4.4"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "starlette" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/07e/0fa0460138baf/sse_starlette-3.4.4.tar.gz", hash = "sha256:07e0fa0460138baf25cdd5fb28683472c3995dc1642225191b3832d62526bcb0" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/3f4/dd50d8aed2771/sse_starlette-3.4.4-py3-none-any.whl", hash = "sha256:3f4dd50d8aed2771a091f3a83000323fc3844541c16b4fe585ae2420cc6df973" },
]
[[package]]
name = "starlette"
version = "1.1.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/e83/c7fe0ddecd871/starlette-1.1.0.tar.gz", hash = "sha256:e83c7fe0ddecd8719c5b840080325aec0260acec86e9832899e377b91d65e90f" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/7f0/dfd38e428aad5/starlette-1.1.0-py3-none-any.whl", hash = "sha256:7f0dfd38e428aad5cb6f9f667f0ca1d2d8ca3f3385dccac8305f79ec98458382" },
]
[[package]]
name = "typer"
version = "0.25.1"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "annotated-doc" },
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/961/6eb8853a09ffe/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/75c/aa44ed46a03fb/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" },
]
[[package]]
name = "uvicorn"
version = "0.48.0"
source = { registry = "http://192.168.2.236:3141/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "h11" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "http://192.168.2.236:3141/root/pypi/+f/a55/04207195d08c2/uvicorn-0.48.0.tar.gz", hash = "sha256:a5504207195d08c2511bf9125ede5ac4a4b71725d519e758d01dcf0bc2d31c37" }
wheels = [
{ url = "http://192.168.2.236:3141/root/pypi/+f/480/97851328b87ec/uvicorn-0.48.0-py3-none-any.whl", hash = "sha256:48097851328b87ec36117d3d575234519eb58c2b22d79666e9bbc6c49a761dad" },
]

View File

@@ -1,138 +0,0 @@
# lzwcai-mcpskills-analyzeOrder
一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志:详细的操作日志记录
- 🌐 中文支持:工具名称自动转换为拼音
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcpskills-analyzeOrder
```
### 从源码安装
```bash
git clone <repository-url>
cd lzwcai_mcp_sqlexecutor
pip install -e .
```
### 使用 uv 安装(推荐)
```bash
uv pip install lzwcai-mcpskills-analyzeOrder
```
## 使用方法
### 命令行启动
安装后,可以直接通过命令启动:
```bash
lzwcai-mcpskills-analyzeOrder
```
### 作为 Python 模块运行
```bash
python -m lzwcai_mcp_sqlexecutor.main
```
### 配置到 MCP 客户端
在你的 MCP 客户端配置文件中添加:
```json
{
"mcpServers": {
"lzwcai-sqlexecutor": {
"command": "lzwcai-mcpskills-analyzeOrder"
}
}
}
```
## 配置说明
### businessQueries.json
`businessQueries.json` 中定义你的业务查询:
```json
[
{
"id": "query-001",
"businessName": "用户订单查询",
"businessDescription": "根据用户ID查询订单信息",
"sqlTemplate": "SELECT * FROM orders WHERE user_id = {{userId}}",
"parameters": {
"type": "object",
"required": ["userId"],
"properties": {
"userId": {
"type": "integer",
"description": "用户的唯一标识符",
"examples": [10086]
}
}
}
}
]
```
## 开发
### 依赖项
- Python >= 3.13
- httpx >= 0.28.1
- mcp[cli] >= 1.10.1
- pypinyin >= 0.53.0
### 本地开发
```bash
# 克隆仓库
git clone <repository-url>
cd lzwcai_mcp_sqlexecutor
# 安装开发依赖
pip install -e .
# 运行服务器
python -m lzwcai_mcp_sqlexecutor.main
```
## 构建与发布
### 使用 build 构建
```bash
pip install build
python -m build
```
### 发布到 PyPI
```bash
pip install twine
twine upload dist/*
```
## 许可证
MIT License
## 作者
lzwcai

View File

@@ -1,10 +0,0 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -1,154 +0,0 @@
# lzwcai-mcpskills-analyzeWorkOrder
一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志详细的操作日志记录仅输出到文件不干扰MCP通信
- 🌐 中文支持:工具名称自动转换为拼音
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcpskills-analyzeWorkOrder
```
### 从源码安装
```bash
git clone <repository-url>
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 <repository-url>
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

View File

@@ -1,9 +0,0 @@
"""
lzwcai-mcpskills-analyzeOrder - MCP server for executing business SQL queries
"""
__version__ = "0.1.2"
__author__ = "lzwcai"
__all__ = []

File diff suppressed because one or more lines are too long

View File

@@ -1,373 +0,0 @@
from pathlib import Path
from typing import Any
import asyncio
import logging
# 支持直接运行和模块导入两种方式
try:
from .utils import load_json, generate_tool_name, generate_input_schema
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
from .utils.logger_config import logger_config
except ImportError:
from utils import load_json, generate_tool_name, generate_input_schema
from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
from utils.logger_config import logger_config
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
import mcp.types as types
# 初始化 MCP 专用日志器
mcp_logger = logger_config.setup_mcp_logging()
# ========== 数据源配置 ==========
# 数据源类型常量
DATA_SOURCE_API = "api" # 仅使用API数据
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
# 默认数据源(可修改)
DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL
# ================================
def get_queries():
"""
获取业务查询配置
Returns:
list: 包含所有业务查询配置的列表
"""
try:
# 获取当前文件所在目录
current_dir = Path(__file__).parent
# 构建 businessQueries.json 的路径
json_path = current_dir / "businessQueries.json"
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
# 使用 load_json 方法读取 JSON 文件
queries = load_json(json_path)
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
return queries
except Exception as e:
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
raise
def generate_tool_schema_from_query(query: dict) -> types.Tool:
"""
根据查询配置生成 MCP 工具模式
Args:
query: 单个查询配置字典
Returns:
types.Tool: MCP 工具对象
"""
try:
# 获取参数定义并生成 inputSchema
parameters = query.get('parameters', {})
input_schema = generate_input_schema(parameters)
# 生成工具名称(格式: tool_拼音_id
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
# 构建工具描述,包含业务名称和业务描述
description = f"{query['businessName']}: {query['businessDescription']}"
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
return types.Tool(
name=tool_name,
description=description,
inputSchema=input_schema
)
except Exception as e:
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
raise
# 创建 MCP 服务器实例
server = Server("lzwcai-mcpskills-analyzeOrder")
# 缓存查询配置,避免重复加载
_queries_cache = None
async def get_queries_cache(source: str = None):
"""
获取或初始化查询配置缓存
Args:
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE
- "api": 仅使用API数据
- "local": 仅使用本地JSON数据
- "both": 合并本地和API数据
Returns:
查询配置列表
"""
global _queries_cache
if _queries_cache is None:
source = source or DEFAULT_DATA_SOURCE
mcp_logger.info(f"初始化查询配置(数据源: {source}...")
if source == DATA_SOURCE_LOCAL:
_queries_cache = get_queries()
mcp_logger.info(f"本地配置: {len(_queries_cache)}")
elif source == DATA_SOURCE_API:
try:
_queries_cache = await call_third_party_api()
mcp_logger.info(f"API配置: {len(_queries_cache)}")
mcp_logger.info(f"API配置数组: {_queries_cache}")
except Exception as e:
mcp_logger.warning(f"API获取失败降级使用本地配置: {e}")
_queries_cache = get_queries()
else: # DATA_SOURCE_BOTH
local = get_queries()
try:
api = await call_third_party_api()
except Exception as e:
mcp_logger.warning(f"API获取失败: {e}")
api = []
_queries_cache = local + api
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)}")
return _queries_cache
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
列出所有动态生成的 MCP 工具
Returns:
list[types.Tool]: 所有可用的工具列表
"""
try:
mcp_logger.info("收到列出工具请求")
queries = await get_queries_cache()
tools = []
for query in queries:
tool = generate_tool_schema_from_query(query)
tools.append(tool)
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
return tools
except Exception as e:
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
raise
@server.call_tool()
async def handle_call_tool(
name: str,
arguments: dict[str, Any] | None
) -> list[types.TextContent]:
"""
处理工具调用请求
Args:
name: 工具名称
arguments: 工具参数
Returns:
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
"""
try:
mcp_logger.info(f"收到工具调用请求: {name}")
mcp_logger.debug(f"工具参数: {arguments}")
# 获取查询配置缓存
queries = await get_queries_cache()
# 根据工具名称查找对应的 item接口配置
tool_item = None
for query in queries:
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
if tool_name == name:
tool_item = query
break
# 构建返回结果
import json
if tool_item:
request_data = {
"datasourceId": get_datasource_id(),
"businessName": tool_item.get("businessName"),
"businessDescription": tool_item.get("businessDescription"),
"sqlTemplate": tool_item.get("sqlTemplate"),
"parameters": tool_item.get("parameters"),
"testParams": arguments or {}
}
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
if arguments and arguments.get("targetDatabaseName"):
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
# 调用测试SQL API
try:
mcp_logger.info("正在调用测试SQL API...")
api_response = test_sql_with_schema(request_data)
mcp_logger.info("测试SQL API调用成功")
# 返回包含 data 字段的结果
result = {
"success": True,
"data": api_response
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
except Exception as e:
error_msg = f"调用测试SQL API失败: {str(e)}"
mcp_logger.error(error_msg, exc_info=True)
result = {
"success": False,
"error": error_msg,
"data": None
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
else:
error_msg = f"未找到工具 {name} 对应的配置"
result = {
"success": False,
"error": error_msg,
"data": None
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
mcp_logger.debug(f"工具调用结果: {result_text}")
return [
types.TextContent(
type="text",
text=result_text
)
]
except Exception as e:
error_msg = f"工具调用失败: {name}, 错误: {e}"
mcp_logger.error(error_msg, exc_info=True)
return [
types.TextContent(
type="text",
text=f"错误: {error_msg}"
)
]
async def call_third_party_api(skill_id: str = None) -> list:
"""
调用第三方API获取技能信息并返回处理后的数据
Args:
skill_id: 技能ID默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178
Returns:
处理后的查询配置列表businessQueries格式
Example:
queries = await call_third_party_api()
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
"""
try:
# 如果没有传入 skill_id则从环境变量读取
if skill_id is None:
skill_id = get_skill_id()
mcp_logger.info(f"调用第三方APIskill_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()

View File

@@ -1,35 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcpskills-analyzeWorkOrder"
version = "0.1.10"
description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.13"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "executor", "server"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcpskills-analyzeWorkOrder = "lzwcai_mcpskills_analyzeWorkOrder.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcpskills_analyzeWorkOrder"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json" = "lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json"

View File

@@ -1,169 +0,0 @@
-- =====================================================
-- 交付风险预测:延迟概率与红/黄/绿预警等级
-- 基于历史订单的生产周期、物流延误、设备故障等特征
-- =====================================================
WITH
-- 1. 全局生产特征(汇总所有工单)
global_production AS (
SELECT
COUNT(*) AS total_wo_count,
AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate,
SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count,
SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count
FROM fact_work_order
),
-- 2. 全局质检特征
global_quality AS (
SELECT
COUNT(*) AS total_inspection_count,
SUM(COALESCE(pass_qty, 0)) AS total_pass_qty,
SUM(COALESCE(fail_qty, 0)) AS total_fail_qty,
CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0
THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0))
ELSE 1 END AS qc_pass_rate
FROM fact_quality_inspection
),
-- 3. 全局工序不良特征
global_operation AS (
SELECT
COUNT(*) AS total_task_count,
SUM(COALESCE(good_qty, 0)) AS total_good_qty,
SUM(COALESCE(bad_qty, 0)) AS total_bad_qty,
CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0
THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0))
ELSE 0 END AS operation_defect_rate
FROM fact_operation_task
),
-- 4. 客户级别发货统计
customer_shipment AS (
SELECT
customer_id,
COUNT(*) AS shipment_count,
SUM(COALESCE(amount, 0)) AS total_shipment_amount
FROM fact_sales_shipment
GROUP BY customer_id
),
-- 5. 客户级别退货统计
customer_return AS (
SELECT
customer_id,
COUNT(*) AS return_count,
SUM(COALESCE(amount, 0)) AS total_return_amount
FROM fact_sales_return
GROUP BY customer_id
),
-- 6. 订单风险评估
order_risk AS (
SELECT
so.sales_order_id,
so.sales_order_number,
c.customer_name,
so.order_date_utc,
so.deal_amount,
so.payment_status,
-- 全局生产指标
gp.avg_completion_rate AS production_completion_rate,
gp.pending_wo_count,
gp.total_wo_count AS work_order_count,
-- 全局质检指标
gq.qc_pass_rate,
gq.total_fail_qty,
-- 全局工序指标
go.operation_defect_rate,
-- 客户级别指标
COALESCE(cs.shipment_count, 0) AS shipment_count,
COALESCE(cr.return_count, 0) AS return_count,
CASE WHEN COALESCE(cs.shipment_count, 0) > 0
THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count
ELSE 0 END AS return_rate
FROM fact_sales_order so
LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true
CROSS JOIN global_production gp
CROSS JOIN global_quality gq
CROSS JOIN global_operation go
LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id
LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id
)
-- 7. 最终输出
SELECT
sales_order_id,
sales_order_number,
customer_name,
order_date_utc,
deal_amount,
payment_status,
-- 风险特征
work_order_count,
ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate,
pending_wo_count,
ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate,
ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate,
return_count,
ROUND(return_rate::NUMERIC, 4) AS return_rate,
-- 延迟概率
ROUND((
CASE WHEN production_completion_rate < 0.3 THEN 0.30
WHEN production_completion_rate < 0.5 THEN 0.20
WHEN production_completion_rate < 0.8 THEN 0.10
ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25
WHEN qc_pass_rate < 0.9 THEN 0.15
WHEN qc_pass_rate < 0.95 THEN 0.08
ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20
WHEN operation_defect_rate > 0.05 THEN 0.12
WHEN operation_defect_rate > 0.02 THEN 0.05
ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15
WHEN return_rate > 0.05 THEN 0.08
WHEN return_rate > 0.02 THEN 0.03
ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10
WHEN payment_status = 'PARTIAL' THEN 0.05
ELSE 0 END
)::NUMERIC, 2) AS delay_probability,
-- 红/黄/绿预警
CASE
WHEN (
CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END
) >= 0.50 THEN 'RED'
WHEN (
CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END
) >= 0.25 THEN 'YELLOW'
ELSE 'GREEN'
END AS risk_level,
-- 风险原因
CONCAT_WS(' | ',
CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END,
CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END,
CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END,
CASE WHEN return_rate > 0.05 THEN '历史退货率高' END,
CASE WHEN payment_status = 'UNPAID' THEN '未付款' END
) AS risk_reasons
FROM order_risk
ORDER BY delay_probability DESC, deal_amount DESC;

View File

@@ -1,25 +0,0 @@
"""Utils package for lzwcai_mcp_sqlexecutor"""
from .json_helper import load_json
from .name_helper import generate_tool_name
from .schema_helper import generate_input_schema, validate_input_schema
from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema
from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable
__all__ = [
'load_json',
'generate_tool_name',
'generate_input_schema',
'validate_input_schema',
'DataSourceAPIClient',
'get_skill_by_id',
'process_skill_response',
'test_sql_with_schema',
'get_database_id',
'get_datasource_id',
'get_skill_id',
'get_backend_base_url',
'get_env_config',
'set_env_variable'
]

View File

@@ -1,318 +0,0 @@
"""
第三方API调用客户端
用于调用外部数据源接口
"""
import httpx
import logging
import json
from typing import Dict, Any, Optional, List
# 支持直接运行和模块导入两种方式
try:
from .env_config import get_backend_base_url
except ImportError:
from env_config import get_backend_base_url
# 获取日志记录器
logger = logging.getLogger(__name__)
class DataSourceAPIClient:
"""数据源API客户端"""
def __init__(
self,
base_url: Optional[str] = None,
token: Optional[str] = None
):
"""
初始化API客户端
Args:
base_url: API基础URL默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088
token: 认证令牌Bearer Token
"""
# 如果没有传入 base_url则从环境变量读取
if base_url is None:
base_url = get_backend_base_url()
self.base_url = base_url.rstrip('/')
self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw"
self.client = httpx.Client(timeout=30.0)
def _get_headers(self) -> Dict[str, str]:
"""
获取请求头
Returns:
请求头字典
"""
return {
'Authorization': f'Bearer {self.token}',
}
def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]:
"""
根据技能ID获取技能信息
Args:
skill_id: 技能ID
Returns:
API响应数据
Raises:
Exception: 请求失败时抛出
"""
try:
url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}"
logger.info(f"正在调用API: {url}")
logger.info(f"请求参数 - skill_id: {skill_id}")
response = self.client.get(
url,
headers=self._get_headers()
)
# 检查HTTP状态码
response.raise_for_status()
# 解析JSON响应
data = response.json()
logger.info(f"API调用成功: {url}")
logger.debug(f"响应数据: {data}")
return data
except httpx.TimeoutException:
error_msg = f"API请求超时: {url}"
logger.error(error_msg)
raise Exception(error_msg)
except httpx.HTTPStatusError as e:
error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg)
except httpx.RequestError as e:
error_msg = f"API请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg)
raise Exception(error_msg)
except Exception as e:
error_msg = f"处理API响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True)
raise Exception(error_msg)
def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
"""
测试SQL语句并返回执行结果
Args:
request_data: 请求数据,包含以下字段:
- datasourceId: 数据源ID
- businessName: 业务名称
- businessDescription: 业务描述
- sqlTemplate: SQL模板
- parameters: 参数定义
- testParams: 测试参数
Returns:
API响应数据
Raises:
Exception: 请求失败时抛出
"""
try:
url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema"
# 构建请求头包含Content-Type
headers = self._get_headers()
headers['Content-Type'] = 'application/json'
headers['Accept'] = '*/*'
logger.info(f"正在调用测试SQL API: {url}")
logger.info(f"请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
# 发送POST请求
response = self.client.post(
url,
headers=headers,
json=request_data
)
# 检查HTTP状态码
response.raise_for_status()
# 解析JSON响应
result = response.json()
logger.info(f"测试SQL API调用成功")
logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}")
# 处理返回数据结构: {code, data: {errorMessage, data}, msg}
# 检查外层 code
if result.get("code") != 200:
error_msg = result.get("msg", "接口返回错误")
logger.error(f"接口返回错误: code={result.get('code')}, msg={error_msg}")
raise Exception(error_msg)
# 检查内层 errorMessage
inner_data = result.get("data", {})
if inner_data.get("errorMessage"):
error_msg = inner_data.get("errorMessage")
logger.error(f"接口业务错误: {error_msg}")
raise Exception(error_msg)
# 返回 data.data
return inner_data.get("data")
except httpx.TimeoutException:
error_msg = f"测试SQL API请求超时: {url}"
logger.error(error_msg)
raise Exception(error_msg)
except httpx.HTTPStatusError as e:
error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg)
except httpx.RequestError as e:
error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg)
raise Exception(error_msg)
except Exception as e:
error_msg = f"处理测试SQL API响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True)
raise Exception(error_msg)
def close(self):
"""关闭HTTP客户端"""
self.client.close()
# 创建默认客户端实例
default_client = DataSourceAPIClient()
def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
"""
便捷函数根据技能ID获取技能信息
Args:
skill_id: 技能ID
base_url: API基础URL可选默认从环境变量 BACKEND_BASE_URL 读取)
token: 认证令牌(可选,使用默认值)
Returns:
API响应数据
"""
if base_url or token:
client = DataSourceAPIClient(
base_url=base_url,
token=token
)
return client.get_skill_by_id(skill_id)
else:
return default_client.get_skill_by_id(skill_id)
def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
"""
便捷函数测试SQL语句并返回执行结果
Args:
request_data: 请求数据,包含以下字段:
- datasourceId: 数据源ID
- businessName: 业务名称
- businessDescription: 业务描述
- sqlTemplate: SQL模板
- parameters: 参数定义
- testParams: 测试参数
base_url: API基础URL可选默认从环境变量 BACKEND_BASE_URL 读取)
token: 认证令牌(可选,使用默认值)
Returns:
API响应数据
"""
if base_url or token:
client = DataSourceAPIClient(
base_url=base_url,
token=token
)
return client.test_sql_with_schema(request_data)
else:
return default_client.test_sql_with_schema(request_data)
def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
处理API响应数据映射为businessQueries格式
Args:
response: API原始响应数据
Returns:
处理后的查询配置列表
"""
try:
# 提取data数组
data_list = response.get("data", [])
# 默认的员工ID参数schema
default_employee_schema = {
"type": "object",
"required": ["employeeId"],
"properties": {
"employeeId": {
"type": "number",
"description": "员工ID用于标识员工的唯一数字标识符",
"examples": [1001, 2002]
}
}
}
# 映射每个skill为businessQuery格式
queries = []
for skill in data_list:
# 解析sqlParams字符串为JSON对象
sql_params = json.loads(skill.get("sqlParams", "{}"))
# 判断sqlParams是否为空对象
is_empty_params = (
not sql_params.get("properties") or
len(sql_params.get("properties", {})) == 0
) and (
not sql_params.get("required") or
len(sql_params.get("required", [])) == 0
)
# 如果是空对象使用默认的员工ID参数
if is_empty_params:
logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空使用默认员工ID参数")
sql_params = default_employee_schema
# 映射字段
query = {
"id": skill.get("id"),
"businessName": skill.get("name"),
"businessDescription": skill.get("description"),
"sqlTemplate": skill.get("sqlTemplate"),
"parameters": sql_params,
"datasourceId": skill.get("datasourceId")
}
queries.append(query)
logger.info(f"成功处理 {len(queries)} 条技能数据")
return queries
except Exception as e:
logger.error(f"处理API响应数据失败: {e}", exc_info=True)
raise

View File

@@ -1,106 +0,0 @@
"""环境变量配置模块"""
import os
from typing import Optional
def get_database_id(default: str = "29") -> str:
"""
获取数据库ID环境变量
Args:
default: 默认值(默认为 "29"
Returns:
str: 数据库ID
Environment Variables:
databaseId: 数据库ID
"""
return os.environ.get("databaseId", default)
def get_datasource_id(default: str = "") -> str:
"""
获取数据源ID环境变量
Args:
default: 默认值(默认为 ""
Returns:
str: 数据源ID
Environment Variables:
datasourceId: 数据源ID
"""
return os.environ.get("datasourceId", default)
def get_skill_id(default: str = "") -> str:
"""
获取技能ID环境变量
Args:
default: 默认值(默认为 ""
Returns:
str: 技能ID
Environment Variables:
skillId: 技能ID
"""
return os.environ.get("skillId", default)
def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str:
"""
获取后端API基础URL环境变量
Args:
default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086"
Returns:
str: 后端API基础URL
Environment Variables:
backendBaseUrl: 后端API基础URL
"""
return os.environ.get("backendBaseUrl", default)
def get_env_config() -> dict:
"""
获取所有环境配置
Returns:
dict: 包含所有配置的字典
Example:
config = get_env_config()
print(config['database_id']) # 输出: "29"
print(config['datasource_id']) # 输出: ""
print(config['skill_id']) # 输出: ""
print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086"
"""
return {
"database_id": get_database_id(),
"datasource_id": get_datasource_id(),
"skill_id": get_skill_id(),
"backend_base_url": get_backend_base_url()
}
def set_env_variable(key: str, value: str) -> None:
"""
设置环境变量(仅在当前进程中有效)
Args:
key: 环境变量名
value: 环境变量值
Example:
set_env_variable("databaseId", "30")
set_env_variable("skillId", "1234567890")
"""
os.environ[key] = value

View File

@@ -1,60 +0,0 @@
"""JSON 文件读取工具"""
import json
from pathlib import Path
from typing import Any, Union
def load_json(json_path: Union[str, Path]) -> Any:
"""
读取 JSON 文件并返回其内容
Args:
json_path: JSON 文件的路径(支持字符串或 Path 对象)
Returns:
JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型)
Raises:
FileNotFoundError: 当文件不存在时
json.JSONDecodeError: 当 JSON 格式无效时
Exception: 其他读取错误
Example:
>>> data = load_json('config.json')
>>> print(data)
{'key': 'value'}
>>> data = load_json(Path('data/users.json'))
>>> print(data)
[{'id': 1, 'name': 'Alice'}]
"""
try:
# 转换为 Path 对象
path = Path(json_path)
# 检查文件是否存在
if not path.exists():
raise FileNotFoundError(f"JSON 文件不存在: {json_path}")
# 检查是否为文件
if not path.is_file():
raise ValueError(f"路径不是一个文件: {json_path}")
# 读取并解析 JSON 文件
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
except json.JSONDecodeError as e:
raise json.JSONDecodeError(
f"JSON 格式错误: {e.msg}",
e.doc,
e.pos
)
except FileNotFoundError:
raise
except Exception as e:
raise Exception(f"读取 JSON 文件时发生错误: {str(e)}")

View File

@@ -1,489 +0,0 @@
# -*- coding: utf-8 -*-
"""
统一日志配置模块
提供系统级别的日志配置和管理
"""
import os
import sys
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from datetime import datetime
from pathlib import Path
class LoggerConfig:
"""日志配置管理类"""
def __init__(self, logs_dir: str = None):
"""初始化日志配置
Args:
logs_dir: 日志目录路径默认为项目根目录下的logs文件夹
"""
# 确定日志目录
if logs_dir:
self.logs_dir = Path(logs_dir)
else:
# 获取项目根目录logger_config.py 在 utils 目录下,需要上升两层到达项目根目录)
project_root = Path(__file__).parent.parent
self.logs_dir = project_root / "logs"
# 创建日志目录
self.logs_dir.mkdir(exist_ok=True)
# 日志格式
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
self.date_format = '%Y-%m-%d %H:%M:%S'
# 从环境变量获取日志级别默认为INFO
self.log_level = self._get_log_level_from_env()
# 是否已初始化
self._initialized = False
def _get_log_level_from_env(self) -> int:
"""从环境变量获取日志级别
Returns:
int: 日志级别
"""
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
# 日志级别映射
level_mapping = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'WARN': logging.WARNING, # 兼容性别名
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
'FATAL': logging.CRITICAL # 兼容性别名
}
return level_mapping.get(log_level_str, logging.INFO)
def setup_logging(self,
app_name: str = "lzwcai_mcp_sqlexecutor",
log_level: int = logging.INFO,
max_file_size: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5,
console_output: bool = True) -> logging.Logger:
"""设置系统日志配置
Args:
app_name: 应用名称,用于日志文件命名
log_level: 日志级别
max_file_size: 单个日志文件最大大小(字节)
backup_count: 保留的备份文件数量
console_output: 是否输出到控制台
Returns:
logging.Logger: 配置好的根日志器
"""
if self._initialized:
return logging.getLogger()
# 设置根日志器
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# 清除现有的处理器
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# 创建格式化器
formatter = logging.Formatter(self.log_format, self.date_format)
# 1. 主日志文件 - 按大小滚动
main_log_file = self.logs_dir / f"{app_name}.log"
file_handler = RotatingFileHandler(
main_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setLevel(log_level)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
# 2. 错误日志文件 - 只记录ERROR及以上级别
error_log_file = self.logs_dir / f"{app_name}_error.log"
error_handler = RotatingFileHandler(
error_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
root_logger.addHandler(error_handler)
# 3. 按日期滚动的日志文件
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
daily_handler = TimedRotatingFileHandler(
daily_log_file,
when='midnight',
interval=1,
backupCount=30, # 保留30天
encoding='utf-8'
)
daily_handler.setLevel(log_level)
daily_handler.setFormatter(formatter)
daily_handler.suffix = "%Y-%m-%d"
root_logger.addHandler(daily_handler)
# 4. 控制台输出
# 重要MCP协议使用stdio时必须将日志输出到stderrstdout仅用于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

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
"""
名称生成工具模块
"""
from pypinyin import lazy_pinyin, Style
import logging
logger = logging.getLogger(__name__)
def generate_tool_name(business_name: str, tool_id: str) -> str:
"""
根据业务名称和ID生成工具名称
格式: tool_拼音_id
Args:
business_name: 业务名称(中文)
tool_id: 工具ID
Returns:
str: 格式化的工具名称
"""
try:
# 将中文转换为拼音(无音调,小写)
pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL)
# 拼接拼音
pinyin_str = ''.join(pinyin_list)
# 将 ID 中的 '-' 替换为 '_'
formatted_id = tool_id.replace('-', '_')
# 组合成最终的工具名称
tool_name = f"tool_{pinyin_str}_{formatted_id}"
return tool_name
except Exception as e:
logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True)
# 降级处理:如果拼音转换失败,使用 ID
return f"tool_{tool_id.replace('-', '_')}"

View File

@@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
"""
Schema 生成工具模块
"""
from typing import Any, Dict, List
def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]:
"""
从查询配置的参数定义生成 MCP 工具的 inputSchema
此函数会保留完整的 JSON Schema 信息,包括:
- type: Schema 类型(通常是 "object"
- required: 必填字段列表
- properties: 属性定义(包括每个属性的 type, description, format, examples 等)
- description: Schema 的整体描述(如果有)
- 以及其他任何 JSON Schema 标准字段
此函数还会自动添加以下字段(如果原始 parameters 中未定义):
- targetDatabaseName: 目标数据库名称(非必填,默认为空字符串)
Args:
parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象
Returns:
Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象
Example:
>>> params = {
... "type": "object",
... "required": ["userId", "startTime"],
... "properties": {
... "userId": {
... "type": "integer",
... "description": "用户的唯一标识符",
... "examples": [10086]
... },
... "startTime": {
... "type": "string",
... "format": "date-time",
... "description": "查询的起始时间",
... "examples": ["2023-01-01 00:00:00"]
... }
... }
... }
>>> schema = generate_input_schema(params)
>>> # schema 将包含所有原始信息,包括 format 和 examples
>>> # 同时会自动添加 targetDatabaseName 字段
"""
# 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用
# 但确保至少包含 type 和 properties
if not parameters:
# 如果 parameters 为空,返回一个空的 object schema
return {
"type": "object",
"properties": {},
"required": []
}
# 检查 parameters 是否已经是完整的 JSON Schema 格式
# 完整格式应该有 "type": "object" 和 "properties" 字段
if "type" in parameters and parameters.get("type") == "object" and "properties" in parameters:
# 已经是完整的 JSON Schema 格式,直接使用
input_schema = dict(parameters)
else:
# parameters 是简化格式(直接是参数定义),需要转换为 JSON Schema 格式
# 例如: {"workOrderNumber": {"type": "string", "description": "...", "required": true}}
properties = {}
required = []
for param_name, param_def in parameters.items():
if isinstance(param_def, dict):
# 提取 required 标记
if param_def.get("required", False):
required.append(param_name)
# 复制参数定义,但移除 required 字段(它应该在顶层的 required 数组中)
prop_def = {k: v for k, v in param_def.items() if k != "required"}
properties[param_name] = prop_def
input_schema = {
"type": "object",
"properties": properties,
"required": required
}
# 确保必需的字段存在
if "type" not in input_schema:
input_schema["type"] = "object"
if "properties" not in input_schema:
input_schema["properties"] = {}
if "required" not in input_schema:
input_schema["required"] = []
# 添加 targetDatabaseName 字段(如果不存在)
if "targetDatabaseName" not in input_schema["properties"]:
input_schema["properties"]["targetDatabaseName"] = {
"type": "string",
"description": "目标数据库名称",
"default": ""
}
# 保留所有其他字段,如 description, examples, format 等
# JSON Schema 标准支持的字段都会被保留:
# - additionalProperties
# - patternProperties
# - minProperties / maxProperties
# - dependencies
# - 等等
return input_schema
def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
"""
验证 inputSchema 是否符合基本的 JSON Schema 规范
Args:
schema: 要验证的 schema 对象
Returns:
tuple[bool, str]: (是否有效, 错误消息或成功消息)
Example:
>>> schema = {"type": "object", "properties": {"id": {"type": "string"}}}
>>> is_valid, msg = validate_input_schema(schema)
>>> print(is_valid, msg)
True, "Schema 验证通过"
"""
if not isinstance(schema, dict):
return False, "Schema 必须是一个字典对象"
if schema.get("type") != "object":
return False, "Schema 的 type 字段必须是 'object'"
if "properties" not in schema:
return False, "Schema 必须包含 properties 字段"
if not isinstance(schema.get("properties"), dict):
return False, "Schema 的 properties 字段必须是一个字典对象"
# 验证 required 字段(如果存在)
if "required" in schema:
required = schema["required"]
if not isinstance(required, list):
return False, "Schema 的 required 字段必须是一个列表"
# 验证所有 required 的字段都在 properties 中定义
properties = schema["properties"]
for field in required:
if field not in properties:
return False, f"必填字段 '{field}' 未在 properties 中定义"
# 验证 properties 中每个字段的定义
for prop_name, prop_def in schema["properties"].items():
if not isinstance(prop_def, dict):
return False, f"属性 '{prop_name}' 的定义必须是一个字典对象"
if "type" not in prop_def:
return False, f"属性 '{prop_name}' 必须包含 type 字段"
return True, "Schema 验证通过"

View File

@@ -1,497 +0,0 @@
version = 1
revision = 2
requires-python = ">=3.13"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" },
]
[[package]]
name = "anyio"
version = "4.11.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" },
]
[[package]]
name = "attrs"
version = "25.4.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" },
]
[[package]]
name = "certifi"
version = "2025.10.5"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" },
]
[[package]]
name = "click"
version = "8.3.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" },
]
[[package]]
name = "httpx-sse"
version = "0.4.3"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" },
]
[[package]]
name = "jsonschema"
version = "4.25.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "referencing" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" },
]
[[package]]
name = "lzwcai-mcpskills-analyzeWorkOrder"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "httpx" },
{ name = "mcp", extra = ["cli"] },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.10.1" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" },
]
[[package]]
name = "mcp"
version = "1.10.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" },
]
[package.optional-dependencies]
cli = [
{ name = "python-dotenv" },
{ name = "typer" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" },
]
[[package]]
name = "pydantic"
version = "2.12.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" },
]
[[package]]
name = "pydantic-core"
version = "2.41.4"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" },
]
[[package]]
name = "pydantic-settings"
version = "2.11.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" },
]
[[package]]
name = "python-dotenv"
version = "1.1.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" },
]
[[package]]
name = "referencing"
version = "0.37.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" },
]
[[package]]
name = "rpds-py"
version = "0.27.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" },
]
[[package]]
name = "sse-starlette"
version = "3.0.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" },
]
[[package]]
name = "starlette"
version = "0.48.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" },
]
[[package]]
name = "typer"
version = "0.19.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" },
]
[[package]]
name = "uvicorn"
version = "0.37.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" },
]

View File

@@ -1,13 +0,0 @@
"""
Entry point for lzwcai-mcpskills-analyzeOrder
Runs the MCP server for SQL query execution
"""
import os
os.environ["databaseId"] = "19"
os.environ["datasourceId"] = "19"
os.environ["backendBaseUrl"] = "http://192.168.11.24:8088"
if __name__ == "__main__":
# Import and run the actual MCP server
from lzwcai_mcpskills_analyzeOrder.main import main
main()

View File

@@ -1,38 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcpskills-analyzeWorkOrder"
version = "0.1.12"
description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "executor", "server"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcpskills-analyzeWorkOrder = "lzwcai_mcpskills_analyzeWorkOrder.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcpskills_analyzeWorkOrder"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json" = "lzwcai_mcpskills_analyzeWorkOrder/businessQueries.json"

View File

@@ -1,138 +0,0 @@
# lzwcai-mcpskills-mfg-data-agent (制造业数据智能体)
一个基于 MCP (Model Context Protocol) 的制造业数据智能体服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志:详细的操作日志记录
- 🌐 中文支持:工具名称自动转换为拼音
- 🏭 制造业场景:专注于制造业数据分析与智能决策
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcpskills-mfg-data-agent
```
### 从源码安装
```bash
git clone <repository-url>
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 <repository-url>
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

View File

@@ -1,543 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人效-产值-损耗三维模型仪表盘</title>
<script src="/LzwcaiEmbedFrameFile/LzwcaiEmbedFrameV5.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; background: #0a0f1a; color: #e2e8f0; min-height: 100vh; }
/* 动画 */
@keyframes glow { 0%, 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.3); } 50% { box-shadow: 0 0 40px rgba(99, 102, 241, 0.5); } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes gradientFlow { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
.animate-glow { animation: glow 3s ease-in-out infinite; }
.animate-float { animation: float 4s ease-in-out infinite; }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-slide { animation: slideUp 0.6s ease-out forwards; }
/* 背景 */
.page-bg {
background: linear-gradient(135deg, #0a0f1a 0%, #111827 50%, #0f172a 100%);
background-attachment: fixed;
position: relative;
}
.page-bg::before {
content: '';
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: radial-gradient(ellipse at 20% 20%, rgba(99, 102, 241, 0.08) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(236, 72, 153, 0.06) 0%, transparent 50%),
radial-gradient(ellipse at 50% 50%, rgba(6, 182, 212, 0.05) 0%, transparent 60%);
pointer-events: none;
}
/* 卡片 */
.card {
background: linear-gradient(145deg, rgba(30, 41, 59, 0.8), rgba(15, 23, 42, 0.9));
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 16px;
backdrop-filter: blur(20px);
transition: all 0.3s ease;
}
.card:hover { border-color: rgba(99, 102, 241, 0.4); transform: translateY(-2px); }
/* 渐变文字 */
.gradient-text {
background: linear-gradient(135deg, #818cf8 0%, #c084fc 50%, #f472b6 100%);
-webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
/* 滚动条 */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: rgba(30, 41, 59, 0.5); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(99, 102, 241, 0.5); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(99, 102, 241, 0.7); }
/* 表格 */
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.data-table th { padding: 12px 16px; text-align: left; font-size: 11px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; background: rgba(30, 41, 59, 0.5); border-bottom: 1px solid rgba(99, 102, 241, 0.1); }
.data-table td { padding: 14px 16px; font-size: 13px; border-bottom: 1px solid rgba(51, 65, 85, 0.3); transition: background 0.2s; }
.data-table tr:hover td { background: rgba(99, 102, 241, 0.05); }
/* 标签 */
.tag { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; }
.tag-green { background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); }
.tag-yellow { background: rgba(245, 158, 11, 0.15); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.3); }
.tag-red { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); }
.tag-blue { background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); }
/* 进度条 */
.progress-bar { height: 6px; background: rgba(51, 65, 85, 0.5); border-radius: 3px; overflow: hidden; }
.progress-fill { height: 100%; border-radius: 3px; background: linear-gradient(90deg, #6366f1, #a855f7); transition: width 0.5s ease; }
/* 分页 */
.pagination { display: flex; align-items: center; gap: 8px; }
.page-btn { padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 500; border: 1px solid rgba(99, 102, 241, 0.3); background: transparent; color: #94a3b8; cursor: pointer; transition: all 0.2s; }
.page-btn:hover:not(:disabled) { background: rgba(99, 102, 241, 0.2); color: #e2e8f0; }
.page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.page-btn.active { background: linear-gradient(135deg, #6366f1, #8b5cf6); color: white; border-color: transparent; }
</style>
</head>
<body class="page-bg">
<div id="root"></div>
<script>
window.addEventListener('DOMContentLoaded', async function() {
try {
const loader = new LibraryLoader({
enableLog: true, async: false,
libraries: [
{ name: 'react', src: '/LzwcaiEmbedFrameFile/lib/react.production.min.js', check: () => typeof window.React !== 'undefined' },
{ name: 'reactDOM', src: '/LzwcaiEmbedFrameFile/lib/react-dom.production.min.js', check: () => typeof window.ReactDOM !== 'undefined' },
{ name: 'htm', src: '/LzwcaiEmbedFrameFile/lib/htm.js', check: () => typeof window.htm !== 'undefined' },
{ name: 'g2', src: '/LzwcaiEmbedFrameFile/lib/g2@5.2.4.min.js', check: () => typeof window.G2 !== 'undefined' }
]
});
await loader.loadAll();
initApp();
} catch (e) { console.error('加载失败:', e); }
});
function initApp() {
const { useState, useEffect, useMemo, useRef } = React;
const html = htm.bind(React.createElement);
const config = { title: '人效-产值-损耗三维模型仪表盘' };
const lzwcaiComInitDate = '{{lzwcaiComInitDate}}';
const defaultData = { success: false, data: [[], [], [], [], []] };
// 配置
const warningCfg = {
GREEN: { label: '正常', class: 'tag-green', icon: '●' },
YELLOW: { label: '关注', class: 'tag-yellow', icon: '●' },
RED: { label: '预警', class: 'tag-red', icon: '●' }
};
const lossCfg = {
LOW: { label: '低', class: 'tag-green' },
MEDIUM: { label: '中', class: 'tag-yellow' },
HIGH: { label: '高', class: 'tag-red' }
};
// 空状态
const Empty = ({ icon, text }) => html`
<div style=${{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '40px 20px', color: '#64748b' }}>
<div style=${{ fontSize: '32px', marginBottom: '12px', opacity: 0.5 }}>${icon}</div>
<div style=${{ fontSize: '13px' }}>${text}</div>
</div>
`;
// 格式化
const fmt = (v, d = 2) => v == null || isNaN(v) ? '-' : Number(v).toLocaleString('zh-CN', { minimumFractionDigits: d, maximumFractionDigits: d });
const fmtPct = v => v == null || isNaN(v) ? '-' : Number(v).toFixed(1) + '%';
const fmtMoney = v => v == null || isNaN(v) ? '-' : '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
function App() {
const [data, setData] = useState(defaultData);
const [error, setError] = useState(null);
const [workerPage, setWorkerPage] = useState(1);
const [productPage, setProductPage] = useState(1);
const [deptFilter, setDeptFilter] = useState('all');
const pageSize = 8;
const chartRef1 = useRef(null);
const chartRef2 = useRef(null);
const chartRef3 = useRef(null);
const chart1 = useRef(null);
const chart2 = useRef(null);
const chart3 = useRef(null);
const validate = d => d && typeof d === 'object' && Array.isArray(d.data);
// 处理数据
const processed = useMemo(() => {
if (!data.data || data.data.length < 5) return null;
const [depts = [], workers = [], trends = [], products = [], orders = []] = data.data;
const stats = {
workers: depts.reduce((s, d) => s + parseInt(d.worker_count || 0), 0),
output: depts.reduce((s, d) => s + (d.total_output_qty || 0), 0),
hours: depts.reduce((s, d) => s + (d.total_work_hours || 0), 0),
value: depts.reduce((s, d) => s + (d.estimated_output_value || 0), 0),
efficiency: depts.length ? depts.reduce((s, d) => s + (d.efficiency_index || 0), 0) / depts.length : 0,
performance: depts.length ? depts.reduce((s, d) => s + (d.performance_score || 0), 0) / depts.length : 0,
defect: depts.length ? depts.reduce((s, d) => s + (d.defect_rate || 0), 0) / depts.length : 0,
completion: depts.length ? depts.reduce((s, d) => s + (d.plan_completion_rate || 0), 0) / depts.length : 0
};
const warnings = { GREEN: 0, YELLOW: 0, RED: 0 };
depts.forEach(d => { const l = d.warning_level || 'GREEN'; if (warnings[l] !== undefined) warnings[l]++; });
const deptList = [...new Set(depts.map(d => d.department))];
return { depts, workers, trends, products, orders, stats, warnings, deptList };
}, [data]);
// 筛选员工
const filteredWorkers = useMemo(() => {
if (!processed) return [];
return deptFilter === 'all' ? processed.workers : processed.workers.filter(w => w.department === deptFilter);
}, [processed, deptFilter]);
const workerPages = Math.ceil(filteredWorkers.length / pageSize);
const pagedWorkers = filteredWorkers.slice((workerPage - 1) * pageSize, workerPage * pageSize);
const productPages = processed ? Math.ceil(processed.products.length / pageSize) : 0;
const pagedProducts = processed ? processed.products.slice((productPage - 1) * pageSize, productPage * pageSize) : [];
// 图表渲染
useEffect(() => {
if (!processed || !window.G2) return;
[chart1, chart2, chart3].forEach(c => { if (c.current) { c.current.destroy(); c.current = null; } });
// 部门效率柱状图
if (chartRef1.current && processed.depts.length > 0) {
const d = processed.depts.map(x => ({ name: x.department.slice(0, 4), efficiency: x.efficiency_index || 0, performance: x.performance_score || 0 }));
chart1.current = new G2.Chart({ container: chartRef1.current, autoFit: true, height: 180 });
chart1.current.theme({ type: 'classicDark' });
chart1.current.interval().data(d).encode('x', 'name').encode('y', 'efficiency').encode('color', 'name')
.scale('color', { range: ['#6366f1', '#8b5cf6', '#a855f7', '#c084fc', '#d946ef', '#ec4899'] })
.style('radius', 4).axis('x', { labelFontSize: 10, labelFill: '#94a3b8' }).axis('y', { title: false, labelFill: '#64748b' })
.legend(false).tooltip({ items: [{ channel: 'y', name: '效率', valueFormatter: v => v.toFixed(1) }] });
chart1.current.render();
}
// 产值分布环形图
if (chartRef2.current && processed.depts.length > 0) {
const d = processed.depts.filter(x => x.estimated_output_value > 0).map(x => ({ name: x.department, value: x.estimated_output_value }));
chart2.current = new G2.Chart({ container: chartRef2.current, autoFit: true, height: 180 });
chart2.current.theme({ type: 'classicDark' });
chart2.current.coordinate({ type: 'theta', innerRadius: 0.6 });
chart2.current.interval().data(d).transform({ type: 'stackY' }).encode('y', 'value').encode('color', 'name')
.scale('color', { range: ['#6366f1', '#8b5cf6', '#06b6d4', '#10b981', '#f59e0b'] })
.style('stroke', '#1e293b').style('lineWidth', 2)
.label({ text: d => d.name.slice(0, 3), position: 'outside', fontSize: 10, fill: '#94a3b8' })
.legend(false).tooltip({ items: [{ channel: 'y', name: '产值', valueFormatter: v => '¥' + (v/10000).toFixed(1) + '万' }] });
chart2.current.render();
}
// 月度趋势折线图
if (chartRef3.current && processed.trends.length > 0) {
const d = processed.trends.map(x => ({ month: x.month, dept: x.department.slice(0, 3), output: x.total_output || 0 }));
chart3.current = new G2.Chart({ container: chartRef3.current, autoFit: true, height: 180 });
chart3.current.theme({ type: 'classicDark' });
chart3.current.line().data(d).encode('x', 'month').encode('y', 'output').encode('color', 'dept')
.scale('color', { range: ['#6366f1', '#8b5cf6', '#06b6d4', '#10b981'] })
.style('lineWidth', 2).axis('x', { labelFontSize: 9, labelFill: '#64748b' }).axis('y', { title: false, labelFill: '#64748b' })
.legend('color', { position: 'top', itemLabelFill: '#94a3b8', itemMarkerSize: 6 });
chart3.current.point().data(d).encode('x', 'month').encode('y', 'output').encode('color', 'dept').style('r', 3);
chart3.current.render();
}
return () => { [chart1, chart2, chart3].forEach(c => { if (c.current) { c.current.destroy(); c.current = null; } }); };
}, [processed]);
// 初始化
useEffect(() => {
const helper = new ChildPageHelper({
autoRenderStatus: true, enableLog: true,
onReady: async () => {
await helper.autoInitialize(() => {
const d = helper.parseTemplateData(lzwcaiComInitDate, defaultData);
if (d && validate(d)) { setData(d); setError(null); }
});
}
});
helper.expose({
setDataLzwcaiEmbedFrameFn: d => { if (validate(d)) { setData(d); setError(null); return { status: 'success' }; } return { status: 'error' }; },
getDataLzwcaiEmbedFrameFn: () => ({ status: 'success', data }),
captureScreenshotLzwcaiEmbedFrameFn: helper.createScreenshotMethod(),
getRenderStatusLzwcaiEmbedFrameFn: () => helper.getRenderStatus()
});
}, []);
// 分页组件
const Pager = ({ page, total, onChange }) => total <= 1 ? null : html`
<div class="pagination">
<button class="page-btn" disabled=${page === 1} onClick=${() => onChange(page - 1)}></button>
${Array.from({ length: Math.min(5, total) }, (_, i) => {
let p = i + 1;
if (total > 5) { if (page <= 3) p = i + 1; else if (page >= total - 2) p = total - 4 + i; else p = page - 2 + i; }
return html`<button key=${p} class="page-btn ${page === p ? 'active' : ''}" onClick=${() => onChange(p)}>${p}</button>`;
})}
<button class="page-btn" disabled=${page === total} onClick=${() => onChange(page + 1)}></button>
</div>
`;
if (error) return html`
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '20px' }}>
<div class="card" style=${{ padding: '40px', textAlign: 'center', maxWidth: '360px' }}>
<div style=${{ fontSize: '48px', marginBottom: '16px' }}></div>
<div style=${{ fontSize: '18px', fontWeight: 600, color: '#f87171', marginBottom: '8px' }}>数据异常</div>
<div style=${{ fontSize: '13px', color: '#64748b' }}>${error}</div>
</div>
</div>`;
if (!processed) return html`
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '20px' }}>
<div class="card animate-glow" style=${{ padding: '40px', textAlign: 'center' }}>
<div class="animate-float" style=${{ fontSize: '48px', marginBottom: '16px' }}>📊</div>
<div class="gradient-text" style=${{ fontSize: '18px', fontWeight: 600 }}>加载中...</div>
</div>
</div>`;
const { depts, stats, warnings, deptList, orders } = processed;
return html`
<div style=${{ padding: '20px', maxWidth: '1600px', margin: '0 auto' }}>
<!-- 顶部标题栏 -->
<div class="card animate-slide" style=${{ padding: '20px 28px', marginBottom: '20px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '16px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div class="animate-glow" style=${{ width: '52px', height: '52px', borderRadius: '14px', background: 'linear-gradient(135deg, #6366f1, #a855f7)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '24px' }}>📊</div>
<div>
<h1 class="gradient-text" style=${{ fontSize: '22px', fontWeight: 700, margin: 0 }}>${config.title}</h1>
<p style=${{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>人效分析 · 产值统计 · 损耗监控 · 实时数据</p>
</div>
</div>
<div style=${{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
${warnings.RED > 0 && html`<span class="tag tag-red animate-pulse"><span>●</span> ${warnings.RED} 预警</span>`}
${warnings.YELLOW > 0 && html`<span class="tag tag-yellow"><span>●</span> ${warnings.YELLOW} 关注</span>`}
<span class="tag tag-green"><span>●</span> ${warnings.GREEN} 正常</span>
</div>
</div>
<!-- 核心指标 -->
<div style=${{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: '16px', marginBottom: '20px' }}>
${[
{ v: stats.workers, l: '总人数', i: '👥', c: '#6366f1' },
{ v: stats.output.toLocaleString(), l: '总产出', i: '📦', c: '#8b5cf6' },
{ v: fmt(stats.hours, 1) + 'h', l: '总工时', i: '⏱️', c: '#06b6d4' },
{ v: '¥' + (stats.value / 10000).toFixed(1) + '万', l: '总产值', i: '💰', c: '#10b981' },
{ v: fmt(stats.efficiency, 1), l: '平均效率', i: '⚡', c: '#f59e0b' },
{ v: fmt(stats.performance, 1), l: '平均绩效', i: '🎯', c: '#ec4899' },
{ v: fmtPct(stats.completion), l: '完成率', i: '✅', c: '#14b8a6' },
{ v: fmtPct(stats.defect), l: '不良率', i: '📉', c: stats.defect > 1 ? '#ef4444' : '#64748b', alert: stats.defect > 1 }
].map((m, i) => html`
<div key=${i} class="card animate-slide" style=${{ padding: '20px', animationDelay: i * 60 + 'ms', border: m.alert ? '1px solid rgba(239, 68, 68, 0.4)' : undefined }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px' }}>
<span style=${{ fontSize: '22px' }}>${m.i}</span>
<div style=${{ width: '8px', height: '8px', borderRadius: '50%', background: m.c, boxShadow: '0 0 10px ' + m.c }}></div>
</div>
<div style=${{ fontSize: '24px', fontWeight: 700, color: m.alert ? '#f87171' : '#f1f5f9' }}>${m.v}</div>
<div style=${{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>${m.l}</div>
</div>
`)}
</div>
<!-- 图表区域 -->
<div style=${{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px', marginBottom: '20px' }}>
<div class="card" style=${{ padding: '20px' }}>
<div style=${{ fontSize: '14px', fontWeight: 600, color: '#e2e8f0', marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#6366f1' }}></span>
部门效率对比
</div>
${depts.length > 0 ? html`<div ref=${chartRef1}></div>` : html`<${Empty} icon="📊" text="暂无数据" />`}
</div>
<div class="card" style=${{ padding: '20px' }}>
<div style=${{ fontSize: '14px', fontWeight: 600, color: '#e2e8f0', marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#8b5cf6' }}></span>
产值分布
</div>
${depts.length > 0 ? html`<div ref=${chartRef2}></div>` : html`<${Empty} icon="💰" text="暂无数据" />`}
</div>
<div class="card" style=${{ padding: '20px' }}>
<div style=${{ fontSize: '14px', fontWeight: 600, color: '#e2e8f0', marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#06b6d4' }}></span>
月度趋势
</div>
${processed.trends.length > 0 ? html`<div ref=${chartRef3}></div>` : html`<${Empty} icon="📈" text="暂无数据" />`}
</div>
</div>
<!-- 部门汇总表 -->
<div class="card" style=${{ marginBottom: '20px', overflow: 'hidden' }}>
<div style=${{ padding: '16px 20px', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#6366f1' }}></span>
<span style=${{ fontSize: '14px', fontWeight: 600 }}>部门人效-产值-损耗汇总</span>
<span class="tag tag-blue" style=${{ marginLeft: 'auto' }}>${depts.length} 部门</span>
</div>
${depts.length > 0 ? html`
<div style=${{ overflowX: 'auto' }}>
<table class="data-table">
<thead>
<tr>
<th>部门</th><th style=${{ textAlign: 'center' }}>人数</th><th style=${{ textAlign: 'center' }}>工时</th>
<th style=${{ textAlign: 'center' }}>产出</th><th style=${{ textAlign: 'center' }}>人均</th><th style=${{ textAlign: 'center' }}>时均</th>
<th style=${{ textAlign: 'center' }}>效率</th><th>完成率</th><th style=${{ textAlign: 'right' }}>产值</th>
<th style=${{ textAlign: 'center' }}>不良率</th><th style=${{ textAlign: 'center' }}>绩效</th><th style=${{ textAlign: 'center' }}>状态</th>
</tr>
</thead>
<tbody>
${depts.map((d, i) => {
const w = warningCfg[d.warning_level] || warningCfg.GREEN;
return html`
<tr key=${i}>
<td style=${{ fontWeight: 600, color: '#f1f5f9' }}>${d.department}</td>
<td style=${{ textAlign: 'center' }}>${d.worker_count}</td>
<td style=${{ textAlign: 'center' }}>${fmt(d.total_work_hours, 1)}h</td>
<td style=${{ textAlign: 'center', fontWeight: 600, color: '#a5b4fc' }}>${d.total_output_qty?.toLocaleString() || 0}</td>
<td style=${{ textAlign: 'center' }}>${fmt(d.output_per_worker, 1)}</td>
<td style=${{ textAlign: 'center' }}>${fmt(d.output_per_hour, 1)}</td>
<td style=${{ textAlign: 'center' }}>
<span class="tag ${d.efficiency_index >= 80 ? 'tag-green' : d.efficiency_index >= 50 ? 'tag-yellow' : 'tag-red'}">${fmt(d.efficiency_index, 1)}</span>
</td>
<td>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div class="progress-bar" style=${{ flex: 1, maxWidth: '80px' }}>
<div class="progress-fill" style=${{ width: Math.min(d.plan_completion_rate || 0, 100) + '%' }}></div>
</div>
<span style=${{ fontSize: '12px', color: '#94a3b8' }}>${fmtPct(d.plan_completion_rate)}</span>
</div>
</td>
<td style=${{ textAlign: 'right', fontWeight: 600, color: '#34d399' }}>${fmtMoney(d.estimated_output_value)}</td>
<td style=${{ textAlign: 'center' }}>
<span class="tag ${(d.defect_rate || 0) === 0 ? 'tag-green' : d.defect_rate < 1 ? 'tag-yellow' : 'tag-red'}">${fmtPct(d.defect_rate)}</span>
</td>
<td style=${{ textAlign: 'center', fontWeight: 600, color: '#a5b4fc' }}>${fmt(d.performance_score, 1)}</td>
<td style=${{ textAlign: 'center' }}><span class="tag ${w.class}">${w.icon} ${w.label}</span></td>
</tr>
`;
})}
</tbody>
</table>
</div>
` : html`<${Empty} icon="🏢" text="暂无部门数据" />`}
</div>
<!-- 下方两栏:员工明细 + 产品质量 -->
<div style=${{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
<!-- 员工产出明细 -->
<div class="card" style=${{ overflow: 'hidden' }}>
<div style=${{ padding: '16px 20px', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#f59e0b' }}></span>
<span style=${{ fontSize: '14px', fontWeight: 600 }}>员工产出明细</span>
</div>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<select value=${deptFilter} onChange=${e => { setDeptFilter(e.target.value); setWorkerPage(1); }}
style=${{ padding: '6px 12px', fontSize: '12px', borderRadius: '8px', border: '1px solid rgba(99, 102, 241, 0.3)', background: 'rgba(30, 41, 59, 0.8)', color: '#e2e8f0', cursor: 'pointer' }}>
<option value="all">全部部门</option>
${deptList.map(d => html`<option key=${d} value=${d}>${d}</option>`)}
</select>
<span class="tag tag-blue">${filteredWorkers.length} 人</span>
</div>
</div>
${filteredWorkers.length > 0 ? html`
<div style=${{ overflowX: 'auto', maxHeight: '360px', overflowY: 'auto' }}>
<table class="data-table">
<thead><tr><th>姓名</th><th>部门</th><th style=${{ textAlign: 'center' }}>工单</th><th style=${{ textAlign: 'center' }}>工时</th><th style=${{ textAlign: 'center' }}>产出</th><th style=${{ textAlign: 'center' }}>时均</th><th style=${{ textAlign: 'center' }}>排名</th></tr></thead>
<tbody>
${pagedWorkers.map((w, i) => html`
<tr key=${i}>
<td style=${{ fontWeight: 600, color: '#f1f5f9' }}>${w.worker_name}</td>
<td><span class="tag tag-blue">${w.department}</span></td>
<td style=${{ textAlign: 'center' }}>${w.work_order_count}</td>
<td style=${{ textAlign: 'center' }}>${fmt(w.total_work_hours, 2)}h</td>
<td style=${{ textAlign: 'center', fontWeight: 600, color: '#a5b4fc' }}>${w.total_output}</td>
<td style=${{ textAlign: 'center' }}>
<span class="tag ${w.output_per_hour >= 80 ? 'tag-green' : w.output_per_hour >= 40 ? 'tag-yellow' : 'tag-red'}">${fmt(w.output_per_hour, 1)}</span>
</td>
<td style=${{ textAlign: 'center' }}>
<span style=${{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '26px', height: '26px', borderRadius: '50%', fontSize: '11px', fontWeight: 700, background: parseInt(w.dept_output_rank) <= 3 ? 'linear-gradient(135deg, #f59e0b, #f97316)' : 'rgba(51, 65, 85, 0.5)', color: parseInt(w.dept_output_rank) <= 3 ? '#fff' : '#94a3b8' }}>${w.dept_output_rank}</span>
</td>
</tr>
`)}
</tbody>
</table>
</div>
<div style=${{ padding: '12px 20px', borderTop: '1px solid rgba(99, 102, 241, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style=${{ fontSize: '12px', color: '#64748b' }}>共 ${filteredWorkers.length} 人,第 ${workerPage}/${workerPages || 1} 页</span>
<${Pager} page=${workerPage} total=${workerPages} onChange=${setWorkerPage} />
</div>
` : html`<${Empty} icon="👤" text="暂无员工数据" />`}
</div>
<!-- 产品质量与损耗 -->
<div class="card" style=${{ overflow: 'hidden' }}>
<div style=${{ padding: '16px 20px', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#10b981' }}></span>
<span style=${{ fontSize: '14px', fontWeight: 600 }}>产品质量与损耗</span>
</div>
<span class="tag tag-green">${processed.products.length} 产品</span>
</div>
${processed.products.length > 0 ? html`
<div style=${{ overflowX: 'auto', maxHeight: '360px', overflowY: 'auto' }}>
<table class="data-table">
<thead><tr><th>产品</th><th>编码</th><th>部门</th><th style=${{ textAlign: 'center' }}>合格</th><th style=${{ textAlign: 'center' }}>不良</th><th style=${{ textAlign: 'center' }}>不良率</th><th style=${{ textAlign: 'center' }}>损耗</th></tr></thead>
<tbody>
${pagedProducts.map((p, i) => {
const lc = lossCfg[p.loss_level] || lossCfg.LOW;
return html`
<tr key=${i}>
<td style=${{ fontWeight: 600, color: '#f1f5f9', maxWidth: '160px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>${p.product_name}</td>
<td style=${{ fontFamily: 'monospace', fontSize: '11px', color: '#64748b' }}>${p.product_code}</td>
<td><span class="tag tag-blue">${p.department}</span></td>
<td style=${{ textAlign: 'center', color: '#34d399', fontWeight: 600 }}>${p.pass_qty?.toLocaleString() || 0}</td>
<td style=${{ textAlign: 'center', color: (p.fail_qty || 0) > 0 ? '#f87171' : '#64748b' }}>${p.fail_qty || 0}</td>
<td style=${{ textAlign: 'center' }}>
<span class="tag ${p.defect_rate == null || p.defect_rate === 0 ? 'tag-green' : p.defect_rate < 1 ? 'tag-yellow' : 'tag-red'}">${p.defect_rate == null ? '-' : fmtPct(p.defect_rate)}</span>
</td>
<td style=${{ textAlign: 'center' }}><span class="tag ${lc.class}">${lc.label}</span></td>
</tr>
`;
})}
</tbody>
</table>
</div>
<div style=${{ padding: '12px 20px', borderTop: '1px solid rgba(99, 102, 241, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span style=${{ fontSize: '12px', color: '#64748b' }}>共 ${processed.products.length} 产品,第 ${productPage}/${productPages || 1} 页</span>
<${Pager} page=${productPage} total=${productPages} onChange=${setProductPage} />
</div>
` : html`<${Empty} icon="🔍" text="暂无产品数据" />`}
</div>
</div>
<!-- 订单状态 -->
${orders.length > 0 && html`
<div class="card" style=${{ marginTop: '16px', padding: '20px' }}>
<div style=${{ fontSize: '14px', fontWeight: 600, marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style=${{ width: '6px', height: '6px', borderRadius: '50%', background: '#a855f7' }}></span>
订单状态概览
<span class="tag tag-blue" style=${{ marginLeft: 'auto' }}>${orders.length} 条</span>
</div>
<div style=${{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '12px' }}>
${orders.map((o, i) => html`
<div key=${i} style=${{ padding: '14px 16px', borderRadius: '10px', background: 'rgba(30, 41, 59, 0.5)', border: '1px solid rgba(99, 102, 241, 0.15)' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}>
<span style=${{ fontSize: '13px', fontWeight: 600, color: '#e2e8f0' }}>${o.department}</span>
<span class="tag ${o.status === 'CLOSED' ? 'tag-green' : o.status === 'STARTED' ? 'tag-blue' : 'tag-yellow'}">
${o.status === 'CLOSED' ? '已完成' : o.status === 'STARTED' ? '进行中' : '待处理'}
</span>
</div>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: '12px', color: '#94a3b8' }}>
<span>${o.order_count} 单</span>
<span style=${{ fontWeight: 600, color: '#a5b4fc' }}>${fmtPct(o.completion_rate)}</span>
</div>
<div class="progress-bar" style=${{ marginTop: '8px' }}>
<div class="progress-fill" style=${{ width: Math.min(o.completion_rate || 0, 100) + '%' }}></div>
</div>
</div>
`)}
</div>
</div>
`}
<!-- 底部 -->
<div style=${{ textAlign: 'center', padding: '24px 0 8px', color: '#475569', fontSize: '12px' }}>
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '8px', padding: '8px 20px', borderRadius: '20px', background: 'rgba(30, 41, 59, 0.5)', border: '1px solid rgba(99, 102, 241, 0.1)' }}>
📊 人效-产值-损耗三维模型 · 实时监控
</span>
</div>
</div>
`;
}
ReactDOM.render(React.createElement(App), document.getElementById('root'));
}
</script>
</body>
</html>

View File

@@ -1,791 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>指标趋势分析与拐点预警</title>
<script src="/LzwcaiEmbedFrameFile/LzwcaiEmbedFrameV5.js"></script>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
@keyframes slideIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
.animate-slide-in { animation: slideIn 0.5s ease-out forwards; }
.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
.animate-scale-in { animation: scaleIn 0.4s ease-out forwards; }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-blink { animation: blink 1.5s ease-in-out infinite; }
.animate-bounce { animation: bounce 1s ease-in-out infinite; }
.glass { backdrop-filter: blur(16px); background: rgba(255,255,255,0.85); }
.gradient-text { background: linear-gradient(135deg, #3b82f6 0%, #6366f1 50%, #8b5cf6 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.page-bg { background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 30%, #f1f5f9 70%, #f8fafc 100%); min-height: 100vh; }
.scrollbar-thin::-webkit-scrollbar { width: 6px; height: 6px; }
.scrollbar-thin::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 3px; }
.scrollbar-thin::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
.scrollbar-thin::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.card-hover { transition: all 0.3s ease; }
.card-hover:hover { transform: translateY(-4px); box-shadow: 0 20px 40px -12px rgba(0,0,0,0.15); }
.trend-arrow { display: inline-flex; align-items: center; justify-content: center; }
.shimmer-bg { background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); background-size: 200% 100%; animation: shimmer 2s infinite; }
</style>
<script>
window.addEventListener('DOMContentLoaded', async function() {
try {
const loader = new LibraryLoader({
enableLog: true, async: false,
libraries: [
{ name: 'react', src: '/LzwcaiEmbedFrameFile/lib/react.production.min.js', check: () => typeof window.React !== 'undefined' },
{ name: 'reactDOM', src: '/LzwcaiEmbedFrameFile/lib/react-dom.production.min.js', check: () => typeof window.ReactDOM !== 'undefined' },
{ name: 'htm', src: '/LzwcaiEmbedFrameFile/lib/htm.js', check: () => typeof window.htm !== 'undefined' },
{ name: 'tailwindcss', src: '/LzwcaiEmbedFrameFile/lib/tailwindcss-3.4.17.js', check: () => typeof window.tailwind !== 'undefined' },
{ name: 'g2', src: '/LzwcaiEmbedFrameFile/lib/g2@5.2.4.min.js', check: () => typeof window.G2 !== 'undefined' }
]
});
await loader.loadAll();
initReactApp();
} catch (error) { console.error('[MetricTrend] 库加载失败:', error); }
});
function initReactApp() {
const { useState, useEffect, useMemo, useRef, useCallback } = React;
const html = htm.bind(React.createElement);
const config = { enableLog: true, title: '指标趋势分析与拐点预警' };
const lzwcaiComInitDate = '{{lzwcaiComInitDate}}';
const defaultData = { success: false, data: [[], [], [], []] };
// 趋势配置
const trendConfig = {
'RISING': { label: '上升', icon: '📈', arrow: '↑', color: 'text-emerald-600', bg: 'bg-emerald-50', border: 'border-emerald-200', gradient: 'from-emerald-400 to-teal-500' },
'FALLING': { label: '下降', icon: '📉', arrow: '↓', color: 'text-red-600', bg: 'bg-red-50', border: 'border-red-200', gradient: 'from-red-400 to-rose-500' },
'STABLE': { label: '平稳', icon: '➡️', arrow: '→', color: 'text-blue-600', bg: 'bg-blue-50', border: 'border-blue-200', gradient: 'from-blue-400 to-indigo-500' },
'NONE': { label: '无数据', icon: '⚪', arrow: '-', color: 'text-slate-500', bg: 'bg-slate-50', border: 'border-slate-200', gradient: 'from-slate-400 to-slate-500' }
};
// 状态配置
const statusConfig = {
'NORMAL': { label: '正常', icon: '✅', color: 'text-emerald-600', bg: 'bg-emerald-50', border: 'border-emerald-200' },
'ANOMALY': { label: '异常', icon: '⚠️', color: 'text-red-600', bg: 'bg-red-50', border: 'border-red-200' }
};
// 拐点配置
const turningPointConfig = {
'TURNING_POINT': { label: '拐点', icon: '🔄', color: 'text-amber-600', bg: 'bg-amber-50', border: 'border-amber-200' },
'UPWARD': { label: '向上拐点', icon: '⬆️', color: 'text-emerald-600', bg: 'bg-emerald-50', border: 'border-emerald-200' },
'DOWNWARD': { label: '向下拐点', icon: '⬇️', color: 'text-red-600', bg: 'bg-red-50', border: 'border-red-200' },
'NONE': { label: '无拐点', icon: '', color: 'text-slate-500', bg: 'bg-slate-50', border: 'border-slate-200' }
};
// 建议配置
const recommendationConfig = {
'MAINTAIN_CURRENT_MEASURES': { label: '维持当前措施', icon: '✅', color: 'text-emerald-600' },
'INCREASE_MONITORING': { label: '加强监控', icon: '👁️', color: 'text-amber-600' },
'TAKE_ACTION': { label: '采取行动', icon: '🚨', color: 'text-red-600' }
};
// 空状态组件
const EmptyState = ({ icon, title, desc }) => html`
<div class="flex flex-col items-center justify-center py-10 text-center">
<div class="w-14 h-14 bg-gradient-to-br from-slate-100 to-slate-200 rounded-2xl flex items-center justify-center text-2xl mb-3">${icon}</div>
<p class="text-sm font-medium text-slate-500">${title}</p>
<p class="text-xs text-slate-400 mt-1">${desc}</p>
</div>
`;
// 趋势箭头组件
const TrendArrow = ({ trend, size = 'md' }) => {
const cfg = trendConfig[trend] || trendConfig['NONE'];
const sizeClass = size === 'lg' ? 'text-2xl' : size === 'sm' ? 'text-sm' : 'text-lg';
return html`<span class="trend-arrow ${cfg.color} ${sizeClass} font-bold">${cfg.arrow}</span>`;
};
// 指标卡片组件(可交互)
const MetricCard = ({ title, value, unit, trend, status, icon, gradient, onClick, isActive, subValue, subLabel }) => {
const trendCfg = trendConfig[trend] || trendConfig['NONE'];
const statusCfg = statusConfig[status] || statusConfig['NORMAL'];
const isAnomaly = status === 'ANOMALY';
return html`
<div
onClick=${onClick}
class="bg-white rounded-2xl p-4 shadow-md border border-slate-100/80 card-hover cursor-pointer relative overflow-hidden
${isActive ? 'ring-2 ring-indigo-400 ring-offset-2' : ''}
${isAnomaly ? 'ring-2 ring-red-200' : ''}"
>
${isAnomaly && html`<div class="absolute top-0 right-0 w-20 h-20 bg-red-500/10 rounded-bl-full"></div>`}
<div class="flex items-start justify-between mb-3">
<span class="w-10 h-10 bg-gradient-to-br ${gradient} rounded-xl flex items-center justify-center text-lg shadow-md ${isAnomaly ? 'animate-pulse' : ''}">${icon}</span>
<div class="flex items-center gap-1">
<${TrendArrow} trend=${trend} size="sm" />
<span class="text-xs font-medium ${trendCfg.color}">${trendCfg.label}</span>
</div>
</div>
<p class="text-2xl font-black text-slate-800">${value}<span class="text-sm font-normal text-slate-400 ml-1">${unit}</span></p>
<p class="text-xs text-slate-500 mt-1">${title}</p>
${subValue !== undefined && html`
<div class="mt-2 pt-2 border-t border-slate-100 flex items-center justify-between">
<span class="text-xs text-slate-400">${subLabel}</span>
<span class="text-xs font-medium text-slate-600">${subValue}</span>
</div>
`}
${isAnomaly && html`
<div class="mt-2 flex items-center gap-1">
<span class="w-2 h-2 bg-red-500 rounded-full animate-blink"></span>
<span class="text-xs font-medium text-red-600">需要关注</span>
</div>
`}
</div>
`;
};
function ChartApp() {
const [rawData, setRawData] = useState(defaultData);
const [pageHelper, setPageHelper] = useState(null);
const [error, setError] = useState(null);
const [activeMetric, setActiveMetric] = useState('efficiency'); // efficiency, output, defect
const [selectedDate, setSelectedDate] = useState(null);
const [showDetail, setShowDetail] = useState(false);
const chartRef1 = useRef(null);
const chartRef2 = useRef(null);
const chartRef3 = useRef(null);
const chartInstance1 = useRef(null);
const chartInstance2 = useRef(null);
const chartInstance3 = useRef(null);
const validateData = (data) => {
if (!data || typeof data !== 'object') return { valid: false, error: '数据格式错误' };
if (!Array.isArray(data.data)) return { valid: false, error: 'data 字段应为数组' };
return { valid: true };
};
// 处理数据
const processedData = useMemo(() => {
if (!rawData.data || rawData.data.length < 4) return null;
const [dailyMetrics = [], weeklyMetrics = [], summaryMetrics = [], turningPoints = []] = rawData.data;
// 日指标数据处理(按日期排序)
const sortedDaily = [...dailyMetrics].sort((a, b) => new Date(a.metric_date) - new Date(b.metric_date));
// 周指标数据处理
const sortedWeekly = [...weeklyMetrics].sort((a, b) => new Date(a.week_start) - new Date(b.week_start));
// 汇总指标
const summaryMap = {};
summaryMetrics.forEach(m => { summaryMap[m.metric_name] = m; });
// 统计异常和拐点数量
let anomalyCount = 0;
let turningPointCount = 0;
sortedDaily.forEach(d => {
if (d.efficiency_status === 'ANOMALY') anomalyCount++;
if (d.output_status === 'ANOMALY') anomalyCount++;
if (d.defect_status === 'ANOMALY') anomalyCount++;
if (d.efficiency_turning_point === 'TURNING_POINT') turningPointCount++;
if (d.output_turning_point === 'TURNING_POINT') turningPointCount++;
if (d.defect_turning_point === 'TURNING_POINT') turningPointCount++;
});
// 获取最新数据
const latestDaily = sortedDaily.length > 0 ? sortedDaily[sortedDaily.length - 1] : null;
const latestWeekly = sortedWeekly.length > 0 ? sortedWeekly[sortedWeekly.length - 1] : null;
return {
dailyMetrics: sortedDaily,
weeklyMetrics: sortedWeekly,
summaryMetrics: summaryMap,
turningPoints,
anomalyCount,
turningPointCount,
latestDaily,
latestWeekly
};
}, [rawData]);
// 渲染趋势图表
useEffect(() => {
if (!processedData || !window.G2) return;
// 清理旧图表
[chartInstance1, chartInstance2, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
const { dailyMetrics, weeklyMetrics } = processedData;
// 人效趋势图(折线图 + 移动平均线)
if (chartRef1.current && dailyMetrics.length > 0) {
const efficiencyData = [];
dailyMetrics.forEach(d => {
efficiencyData.push({ date: d.metric_date, value: d.hourly_output, type: '实际人效' });
efficiencyData.push({ date: d.metric_date, value: d.efficiency_ma7, type: '7日均线' });
});
chartInstance1.current = new G2.Chart({ container: chartRef1.current, autoFit: true, height: 220 });
chartInstance1.current.line()
.data(efficiencyData)
.encode('x', 'date').encode('y', 'value').encode('color', 'type')
.scale('color', { range: ['#3b82f6', '#f59e0b'] })
.style('lineWidth', 2)
.axis('x', { labelAutoRotate: true, labelFontSize: 10, title: false })
.axis('y', { title: false, labelFormatter: v => v.toFixed(1) })
.legend('color', { position: 'top', layout: { justifyContent: 'center' } })
.tooltip({ shared: true });
// 标记拐点
const turningPointData = dailyMetrics.filter(d => d.efficiency_turning_point === 'TURNING_POINT');
if (turningPointData.length > 0) {
chartInstance1.current.point()
.data(turningPointData.map(d => ({ date: d.metric_date, value: d.hourly_output })))
.encode('x', 'date').encode('y', 'value')
.style('fill', '#ef4444').style('r', 6)
.tooltip({ items: [{ channel: 'y', name: '拐点', valueFormatter: v => v.toFixed(2) }] });
}
chartInstance1.current.render();
}
// 产量趋势图
if (chartRef2.current && dailyMetrics.length > 0) {
const outputData = [];
dailyMetrics.forEach(d => {
outputData.push({ date: d.metric_date, value: d.daily_output, type: '日产量' });
outputData.push({ date: d.metric_date, value: d.output_ma7, type: '7日均线' });
});
chartInstance2.current = new G2.Chart({ container: chartRef2.current, autoFit: true, height: 220 });
chartInstance2.current.line()
.data(outputData)
.encode('x', 'date').encode('y', 'value').encode('color', 'type')
.scale('color', { range: ['#10b981', '#8b5cf6'] })
.style('lineWidth', 2)
.axis('x', { labelAutoRotate: true, labelFontSize: 10, title: false })
.axis('y', { title: false })
.legend('color', { position: 'top', layout: { justifyContent: 'center' } })
.tooltip({ shared: true });
// 标记拐点
const turningPointData = dailyMetrics.filter(d => d.output_turning_point === 'TURNING_POINT');
if (turningPointData.length > 0) {
chartInstance2.current.point()
.data(turningPointData.map(d => ({ date: d.metric_date, value: d.daily_output })))
.encode('x', 'date').encode('y', 'value')
.style('fill', '#ef4444').style('r', 6);
}
chartInstance2.current.render();
}
// 周环比趋势图(柱状图)
if (chartRef3.current && weeklyMetrics.length > 0) {
const weeklyData = weeklyMetrics.map(w => ({
week: w.week_start,
人效: w.hourly_output,
环比: w.efficiency_wow_pct || 0
}));
chartInstance3.current = new G2.Chart({ container: chartRef3.current, autoFit: true, height: 220 });
chartInstance3.current.interval()
.data(weeklyData)
.encode('x', 'week').encode('y', '人效')
.encode('color', d => d['环比'] >= 0 ? '上升' : '下降')
.scale('color', { range: ['#10b981', '#ef4444'] })
.style('radius', 6)
.axis('x', { labelAutoRotate: true, labelFontSize: 10, title: false })
.axis('y', { title: false })
.legend('color', { position: 'top', layout: { justifyContent: 'center' } })
.tooltip({ items: [
{ channel: 'y', name: '人效', valueFormatter: v => v.toFixed(2) },
{ field: '环比', name: '环比', valueFormatter: v => (v >= 0 ? '+' : '') + v.toFixed(1) + '%' }
] });
chartInstance3.current.render();
}
return () => {
[chartInstance1, chartInstance2, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
};
}, [processedData]);
useEffect(() => {
const helper = new ChildPageHelper({
autoRenderStatus: true, enableLog: config.enableLog,
onReady: async () => {
await helper.autoInitialize(() => {
const finalInitData = helper.parseTemplateData(lzwcaiComInitDate, defaultData);
if (finalInitData) {
const v = validateData(finalInitData);
v.valid ? (setRawData(finalInitData), setError(null)) : setError(v.error);
}
});
}
});
setPageHelper(helper);
helper.expose({
setDataLzwcaiEmbedFrameFn: (data) => {
const v = validateData(data);
if (v.valid) { setRawData(data); setError(null); return { status: 'success' }; }
setError(v.error); return { status: 'error', message: v.error };
},
getDataLzwcaiEmbedFrameFn: () => ({ status: 'success', data: rawData }),
captureScreenshotLzwcaiEmbedFrameFn: helper.createScreenshotMethod(),
getRenderStatusLzwcaiEmbedFrameFn: () => helper.getRenderStatus()
});
}, []);
const formatPercent = v => v !== null && v !== undefined ? (v >= 0 ? '+' : '') + v.toFixed(1) + '%' : '-';
const formatNumber = v => v !== null && v !== undefined ? v.toFixed(2) : '-';
const getTrend = trend => trendConfig[trend] || trendConfig['NONE'];
const getStatus = status => statusConfig[status] || statusConfig['NORMAL'];
const getTurningPoint = tp => turningPointConfig[tp] || turningPointConfig['NONE'];
if (error) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-2xl p-8 max-w-sm text-center border border-red-100">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-red-400 to-rose-500 rounded-2xl flex items-center justify-center text-3xl shadow-lg">⚠️</div>
<h3 class="text-lg font-bold text-red-700 mb-2">数据异常</h3>
<p class="text-sm text-red-500">${error}</p>
</div>
</div>`;
if (!processedData) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-xl p-10 max-w-sm text-center border border-blue-100">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-blue-400 to-indigo-500 rounded-2xl flex items-center justify-center text-3xl animate-pulse">📊</div>
<h3 class="text-lg font-bold text-blue-600 mb-2">加载中</h3>
<p class="text-sm text-slate-400">等待指标数据...</p>
</div>
</div>`;
const { dailyMetrics, weeklyMetrics, summaryMetrics, turningPoints, anomalyCount, turningPointCount, latestDaily, latestWeekly } = processedData;
return html`
<div class="page-bg p-4 sm:p-5">
<div class="max-w-7xl mx-auto space-y-4">
<!-- 头部 -->
<div class="glass rounded-2xl p-5 shadow-lg border border-white/60">
<div class="flex items-center justify-between flex-wrap gap-4">
<div class="flex items-center gap-4">
<div class="w-14 h-14 bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 rounded-2xl flex items-center justify-center shadow-lg shadow-indigo-500/30">
<span class="text-white text-2xl">📊</span>
</div>
<div>
<h1 class="text-2xl font-bold gradient-text">${config.title}</h1>
<p class="text-xs text-slate-500 mt-0.5">移动平均 · 线性回归 · 拐点预警</p>
</div>
</div>
<div class="flex items-center gap-3 flex-wrap">
${anomalyCount > 0 && html`
<span class="px-4 py-2 bg-red-50 border border-red-200 rounded-xl text-sm font-bold text-red-600 flex items-center gap-2 animate-pulse">
<span class="w-2.5 h-2.5 bg-red-500 rounded-full animate-blink"></span>
${anomalyCount} 个异常
</span>
`}
${turningPointCount > 0 && html`
<span class="px-4 py-2 bg-amber-50 border border-amber-200 rounded-xl text-sm font-bold text-amber-600 flex items-center gap-2">
<span class="w-2.5 h-2.5 bg-amber-500 rounded-full"></span>
${turningPointCount} 个拐点
</span>
`}
</div>
</div>
</div>
<!-- 核心指标卡片(可点击切换) -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
${latestDaily && html`
<${MetricCard}
title="人效(件/小时)"
value=${formatNumber(latestDaily.hourly_output)}
unit=""
trend=${latestDaily.efficiency_trend}
status=${latestDaily.efficiency_status}
icon="⚡"
gradient="from-blue-400 to-indigo-500"
onClick=${() => setActiveMetric('efficiency')}
isActive=${activeMetric === 'efficiency'}
subValue=${formatNumber(latestDaily.efficiency_ma7)}
subLabel="7日均线"
/>
<${MetricCard}
title="日产量"
value=${latestDaily.daily_output}
unit="件"
trend=${latestDaily.output_trend}
status=${latestDaily.output_status}
icon="📦"
gradient="from-emerald-400 to-teal-500"
onClick=${() => setActiveMetric('output')}
isActive=${activeMetric === 'output'}
subValue=${formatNumber(latestDaily.output_ma7)}
subLabel="7日均线"
/>
<${MetricCard}
title="废品率"
value=${(latestDaily.defect_rate * 100).toFixed(2)}
unit="%"
trend=${latestDaily.defect_trend}
status=${latestDaily.defect_status}
icon="🔍"
gradient="from-purple-400 to-pink-500"
onClick=${() => setActiveMetric('defect')}
isActive=${activeMetric === 'defect'}
subValue=${(latestDaily.defect_rate_ma7 * 100).toFixed(2) + '%'}
subLabel="7日均线"
/>
`}
${latestWeekly && html`
<${MetricCard}
title="周环比变化"
value=${formatPercent(latestWeekly.efficiency_wow_pct)}
unit=""
trend=${latestWeekly.efficiency_trend}
status=${latestWeekly.efficiency_wow_pct < -20 ? 'ANOMALY' : 'NORMAL'}
icon="📈"
gradient="from-amber-400 to-orange-500"
subValue=${latestWeekly.worker_count + ' 人'}
subLabel="本周人数"
/>
`}
</div>
<!-- 趋势图表区域 -->
<div class="grid lg:grid-cols-2 gap-4">
<!-- 人效趋势图 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4 card-hover">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-blue-400 to-indigo-500 rounded-lg flex items-center justify-center text-white text-xs">⚡</span>
人效趋势分析
</h3>
<div class="flex items-center gap-2">
<span class="w-3 h-3 bg-blue-500 rounded-full"></span>
<span class="text-xs text-slate-500">实际值</span>
<span class="w-3 h-3 bg-amber-500 rounded-full ml-2"></span>
<span class="text-xs text-slate-500">均线</span>
<span class="w-3 h-3 bg-red-500 rounded-full ml-2"></span>
<span class="text-xs text-slate-500">拐点</span>
</div>
</div>
${dailyMetrics.length > 0
? html`<div ref=${chartRef1} class="w-full"></div>`
: html`<${EmptyState} icon="📈" title="暂无人效数据" desc="等待数据加载" />`
}
</div>
<!-- 产量趋势图 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4 card-hover">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-lg flex items-center justify-center text-white text-xs">📦</span>
产量趋势分析
</h3>
<div class="flex items-center gap-2">
<span class="w-3 h-3 bg-emerald-500 rounded-full"></span>
<span class="text-xs text-slate-500">日产量</span>
<span class="w-3 h-3 bg-purple-500 rounded-full ml-2"></span>
<span class="text-xs text-slate-500">均线</span>
</div>
</div>
${dailyMetrics.length > 0
? html`<div ref=${chartRef2} class="w-full"></div>`
: html`<${EmptyState} icon="📦" title="暂无产量数据" desc="等待数据加载" />`
}
</div>
</div>
<!-- 周数据分析 + 拐点预警 -->
<div class="grid lg:grid-cols-3 gap-4">
<!-- 周环比趋势 -->
<div class="lg:col-span-2 bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-amber-400 to-orange-500 rounded-lg flex items-center justify-center text-white text-xs">📊</span>
周人效环比分析
</h3>
<span class="px-2 py-1 bg-amber-50 text-amber-600 rounded-full text-xs font-medium">${weeklyMetrics.length} 周</span>
</div>
${weeklyMetrics.length > 0
? html`<div ref=${chartRef3} class="w-full"></div>`
: html`<${EmptyState} icon="📊" title="暂无周数据" desc="等待数据加载" />`
}
</div>
<!-- 拐点预警列表 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-red-400 to-rose-500 rounded-lg flex items-center justify-center text-white text-xs animate-pulse">🔄</span>
拐点预警
</h3>
<span class="px-2 py-1 bg-red-50 text-red-600 rounded-full text-xs font-medium">${turningPoints.length} 项</span>
</div>
${turningPoints.length > 0 ? html`
<div class="space-y-2 max-h-[280px] overflow-y-auto scrollbar-thin pr-1">
${turningPoints.map((tp, i) => {
const tpCfg = getTurningPoint(tp.turning_type);
const recCfg = recommendationConfig[tp.recommendation] || { label: tp.recommendation, icon: '📋', color: 'text-slate-600' };
return html`
<div key=${i} class="bg-gradient-to-r from-amber-50 to-orange-50 rounded-xl p-3 border border-amber-100 hover:shadow-md transition-all card-hover animate-slide-in" style=${{ animationDelay: i * 60 + 'ms' }}>
<div class="flex items-start justify-between gap-2">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="text-lg">${tpCfg.icon}</span>
<span class="font-bold text-slate-800 text-sm">${tp.metric_date}</span>
</div>
<div class="flex flex-wrap gap-1 mb-2">
<span class="px-2 py-0.5 ${tpCfg.bg} ${tpCfg.border} border rounded text-xs ${tpCfg.color} font-medium">${tpCfg.label}</span>
</div>
<div class="flex items-center gap-2 text-xs text-slate-500">
<span>人效: ${formatNumber(tp.daily_efficiency)}</span>
<span>均线: ${formatNumber(tp.ma7_line)}</span>
</div>
</div>
<div class="text-right shrink-0">
<span class="inline-flex items-center gap-1 px-2 py-1 bg-white/80 rounded-lg text-xs font-medium ${recCfg.color}">
${recCfg.icon} ${recCfg.label}
</span>
</div>
</div>
</div>
`;
})}
</div>
` : html`<${EmptyState} icon="✨" title="暂无拐点" desc="趋势运行平稳" />`}
</div>
</div>
<!-- 汇总指标对比 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-indigo-400 to-purple-500 rounded-lg flex items-center justify-center text-white text-xs">📋</span>
周期对比分析
</h3>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
${[
{ key: 'efficiency_per_hour', label: '人效(件/小时)', icon: '⚡', gradient: 'from-blue-400 to-indigo-500' },
{ key: 'daily_output', label: '日产量', icon: '📦', gradient: 'from-emerald-400 to-teal-500' },
{ key: 'defect_rate_pct', label: '废品率', icon: '🔍', gradient: 'from-purple-400 to-pink-500' }
].map((item, i) => {
const m = summaryMetrics[item.key] || {};
const trendCfg = getTrend(m.trend);
const isWarning = m.warning === 'ANOMALY';
return html`
<div key=${i} class="bg-gradient-to-br from-slate-50 to-white rounded-xl p-4 border border-slate-100 hover:shadow-md transition-all card-hover ${isWarning ? 'ring-2 ring-red-200' : ''}">
<div class="flex items-center gap-2 mb-3">
<span class="w-8 h-8 bg-gradient-to-br ${item.gradient} rounded-lg flex items-center justify-center text-sm shadow-md">${item.icon}</span>
<span class="text-sm font-medium text-slate-700">${item.label}</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<p class="text-xs text-slate-400 mb-1">近7日均值</p>
<p class="text-lg font-bold text-slate-800">${m.last_7d_avg !== null ? formatNumber(m.last_7d_avg) : '-'}</p>
</div>
<div>
<p class="text-xs text-slate-400 mb-1">前7日均值</p>
<p class="text-lg font-bold text-slate-600">${m.prev_7d_avg !== null ? formatNumber(m.prev_7d_avg) : '-'}</p>
</div>
</div>
<div class="mt-3 pt-3 border-t border-slate-100 flex items-center justify-between">
<div class="flex items-center gap-2">
<${TrendArrow} trend=${m.trend} size="sm" />
<span class="text-xs font-medium ${trendCfg.color}">${trendCfg.label}</span>
</div>
<span class="text-sm font-bold ${m.change_rate_pct >= 0 ? 'text-emerald-600' : 'text-red-600'}">
${m.change_rate_pct !== null ? formatPercent(m.change_rate_pct) : '-'}
</span>
</div>
${isWarning && html`
<div class="mt-2 flex items-center gap-1">
<span class="w-2 h-2 bg-red-500 rounded-full animate-blink"></span>
<span class="text-xs font-medium text-red-600">需要关注</span>
</div>
`}
</div>
`;
})}
</div>
</div>
<!-- 日指标明细表 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 overflow-hidden">
<div class="px-4 py-3 border-b border-slate-100 bg-gradient-to-r from-slate-50 to-white">
<div class="flex flex-wrap items-center justify-between gap-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center text-white text-xs">📅</span>
日指标明细
</h3>
<span class="px-2 py-1 bg-cyan-50 text-cyan-600 rounded-full text-xs font-medium">${dailyMetrics.length} 条</span>
</div>
</div>
${dailyMetrics.length > 0 ? html`
<div class="overflow-x-auto max-h-[400px] overflow-y-auto scrollbar-thin">
<table class="w-full text-sm">
<thead class="bg-slate-50 sticky top-0">
<tr>
<th class="px-3 py-2.5 text-left text-xs font-semibold text-slate-500">日期</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">人效</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">人效趋势</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">日产量</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">产量趋势</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">废品率</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">拐点</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">状态</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
${dailyMetrics.slice().reverse().map((d, i) => {
const effTrend = getTrend(d.efficiency_trend);
const outTrend = getTrend(d.output_trend);
const effStatus = getStatus(d.efficiency_status);
const outStatus = getStatus(d.output_status);
const hasTurningPoint = d.efficiency_turning_point === 'TURNING_POINT' || d.output_turning_point === 'TURNING_POINT';
const hasAnomaly = d.efficiency_status === 'ANOMALY' || d.output_status === 'ANOMALY';
return html`
<tr key=${i} class="hover:bg-slate-50/50 transition-colors ${hasAnomaly ? 'bg-red-50/30' : ''} ${hasTurningPoint ? 'bg-amber-50/30' : ''}">
<td class="px-3 py-2.5">
<span class="font-semibold text-slate-800 text-sm">${d.metric_date}</span>
</td>
<td class="px-3 py-2.5 text-center">
<div>
<span class="font-bold text-slate-800">${formatNumber(d.hourly_output)}</span>
<p class="text-xs text-slate-400">均线: ${formatNumber(d.efficiency_ma7)}</p>
</div>
</td>
<td class="px-3 py-2.5 text-center">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${effTrend.bg} ${effTrend.color}">
<${TrendArrow} trend=${d.efficiency_trend} size="sm" />
${effTrend.label}
</span>
</td>
<td class="px-3 py-2.5 text-center">
<div>
<span class="font-bold text-slate-800">${d.daily_output}</span>
<p class="text-xs text-slate-400">均线: ${formatNumber(d.output_ma7)}</p>
</div>
</td>
<td class="px-3 py-2.5 text-center">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${outTrend.bg} ${outTrend.color}">
<${TrendArrow} trend=${d.output_trend} size="sm" />
${outTrend.label}
</span>
</td>
<td class="px-3 py-2.5 text-center">
<span class="text-sm text-slate-700">${(d.defect_rate * 100).toFixed(2)}%</span>
</td>
<td class="px-3 py-2.5 text-center">
${hasTurningPoint ? html`
<span class="inline-flex items-center gap-1 px-2 py-1 bg-amber-50 border border-amber-200 rounded-lg text-xs font-medium text-amber-600 animate-pulse">
🔄 拐点
</span>
` : html`<span class="text-slate-400 text-xs">-</span>`}
</td>
<td class="px-3 py-2.5 text-center">
${hasAnomaly ? html`
<span class="inline-flex items-center gap-1 px-2 py-1 bg-red-50 border border-red-200 rounded-lg text-xs font-medium text-red-600">
⚠️ 异常
</span>
` : html`
<span class="inline-flex items-center gap-1 px-2 py-1 bg-emerald-50 border border-emerald-200 rounded-lg text-xs font-medium text-emerald-600">
✅ 正常
</span>
`}
</td>
</tr>
`;
})}
</tbody>
</table>
</div>
` : html`<${EmptyState} icon="📅" title="暂无日指标数据" desc="等待数据加载" />`}
</div>
<!-- 周指标明细 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 overflow-hidden">
<div class="px-4 py-3 border-b border-slate-100 bg-gradient-to-r from-slate-50 to-white">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-violet-400 to-purple-500 rounded-lg flex items-center justify-center text-white text-xs">📆</span>
周指标汇总
</h3>
</div>
${weeklyMetrics.length > 0 ? html`
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 p-4">
${weeklyMetrics.slice().reverse().map((w, i) => {
const effTrend = getTrend(w.efficiency_trend);
const outTrend = getTrend(w.output_trend);
const isPositive = w.efficiency_wow_pct >= 0;
return html`
<div key=${i} class="bg-gradient-to-br from-slate-50 to-white rounded-xl p-4 border border-slate-100 hover:shadow-lg transition-all card-hover animate-slide-in" style=${{ animationDelay: i * 80 + 'ms' }}>
<div class="flex items-center justify-between mb-3">
<span class="text-sm font-bold text-slate-800">${w.week_start}</span>
<span class="px-2 py-1 ${isPositive ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600'} rounded-lg text-xs font-bold">
${formatPercent(w.efficiency_wow_pct)}
</span>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">人效</span>
<div class="flex items-center gap-1">
<span class="text-sm font-bold text-slate-700">${formatNumber(w.hourly_output)}</span>
<${TrendArrow} trend=${w.efficiency_trend} size="sm" />
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">总产量</span>
<span class="text-sm font-medium text-slate-600">${w.total_output}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">总工时</span>
<span class="text-sm font-medium text-slate-600">${w.total_hours}h</span>
</div>
<div class="flex items-center justify-between">
<span class="text-xs text-slate-500">人数</span>
<span class="text-sm font-medium text-slate-600">${w.worker_count} 人</span>
</div>
</div>
</div>
`;
})}
</div>
` : html`<${EmptyState} icon="📆" title="暂无周数据" desc="等待数据加载" />`}
</div>
<!-- 底部 -->
<div class="text-center pt-2 pb-3">
<p class="text-xs text-slate-400">
<span class="inline-flex items-center gap-1.5 px-4 py-2 glass rounded-full border border-white/50 shadow-sm">
📊 指标趋势分析与拐点预警 · 智能监控
</span>
</p>
</div>
</div>
</div>
`;
}
ReactDOM.render(React.createElement(ChartApp), document.getElementById('root'));
}
</script>
</head>
<body class="m-0 p-0">
<div id="root"></div>
</body>
</html>

View File

@@ -1,567 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>一页式决策简报</title>
<script src="/LzwcaiEmbedFrameFile/LzwcaiEmbedFrameV5.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif; min-height: 100vh; }
/* 动画 */
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
@keyframes glow { 0%, 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.4); } 50% { box-shadow: 0 0 40px rgba(99, 102, 241, 0.6); } }
@keyframes slideUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
@keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.anim-slide { animation: slideUp 0.5s ease-out forwards; opacity: 0; }
.anim-float { animation: float 6s ease-in-out infinite; }
.anim-glow { animation: glow 3s ease-in-out infinite; }
.anim-pulse { animation: pulse 2s ease-in-out infinite; }
/* 现代渐变背景 */
.page-bg {
background: linear-gradient(135deg, #0f0f23 0%, #1a1a3e 25%, #0d1b2a 50%, #1b263b 75%, #0f0f23 100%);
background-size: 400% 400%;
animation: gradientShift 20s ease infinite;
min-height: 100vh;
position: relative;
overflow-x: hidden;
overflow-y: auto;
}
@keyframes gradientShift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
.page-bg::before {
content: '';
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background:
radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
radial-gradient(circle at 80% 80%, rgba(236, 72, 153, 0.1) 0%, transparent 40%),
radial-gradient(circle at 50% 50%, rgba(6, 182, 212, 0.08) 0%, transparent 50%);
pointer-events: none;
}
/* 玻璃卡片 */
.glass {
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 20px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.glass:hover {
border-color: rgba(255,255,255,0.25);
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
}
/* 响应式布局 */
.main-grid { display: grid; grid-template-columns: 1fr 1.5fr 1fr; gap: 20px; }
.metric-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 16px; }
.footer-content { display: flex; align-items: center; gap: 32px; flex-wrap: wrap; }
@media (max-width: 1200px) {
.main-grid { grid-template-columns: 1fr 1fr; }
.metric-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 900px) {
.main-grid { grid-template-columns: 1fr; }
.metric-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
.metric-grid { grid-template-columns: 1fr; }
.footer-content { flex-direction: column; align-items: flex-start; gap: 16px; }
.footer-content .divider-v { display: none; }
}
/* 滚动条美化 */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); }
</style>
</head>
<body class="page-bg">
<div id="root"></div>
<script>
window.addEventListener('DOMContentLoaded', async function() {
try {
const loader = new LibraryLoader({
enableLog: true, async: false,
libraries: [
{ name: 'react', src: '/LzwcaiEmbedFrameFile/lib/react.production.min.js', check: () => typeof window.React !== 'undefined' },
{ name: 'reactDOM', src: '/LzwcaiEmbedFrameFile/lib/react-dom.production.min.js', check: () => typeof window.ReactDOM !== 'undefined' },
{ name: 'htm', src: '/LzwcaiEmbedFrameFile/lib/htm.js', check: () => typeof window.htm !== 'undefined' },
{ name: 'g2', src: '/LzwcaiEmbedFrameFile/lib/g2@5.2.4.min.js', check: () => typeof window.G2 !== 'undefined' }
]
});
await loader.loadAll();
initApp();
} catch (e) { console.error('加载失败:', e); }
});
function initApp() {
const { useState, useEffect, useMemo, useRef } = React;
const html = htm.bind(React.createElement);
const config = { title: '一页式决策简报' };
const lzwcaiComInitDate = '{{lzwcaiComInitDate}}';
const defaultData = { success: false, data: [[], [], [], [], [], []] };
// 健康状态配置
const healthCfg = {
HEALTHY: { label: '健康', gradient: 'linear-gradient(135deg, #10b981, #059669)', icon: '✓', glow: 'rgba(16, 185, 129, 0.4)' },
WARNING: { label: '关注', gradient: 'linear-gradient(135deg, #f59e0b, #d97706)', icon: '!', glow: 'rgba(245, 158, 11, 0.4)' },
CRITICAL: { label: '预警', gradient: 'linear-gradient(135deg, #ef4444, #dc2626)', icon: '✗', glow: 'rgba(239, 68, 68, 0.4)' }
};
// 指标状态配置
const metricCfg = {
NORMAL: { color: '#10b981', bg: 'rgba(16, 185, 129, 0.15)', border: 'rgba(16, 185, 129, 0.3)' },
EXCEEDED: { color: '#ef4444', bg: 'rgba(239, 68, 68, 0.15)', border: 'rgba(239, 68, 68, 0.3)' }
};
// 格式化函数
const fmt = (v, d = 0) => v == null || isNaN(v) ? '-' : Number(v).toLocaleString('zh-CN', { minimumFractionDigits: d, maximumFractionDigits: d });
const fmtMoney = v => v == null || isNaN(v) ? '-' : '¥' + (Math.abs(v) >= 10000 ? (v / 10000).toFixed(1) + '万' : fmt(v));
const fmtPct = v => v == null || isNaN(v) ? '-' : Number(v).toFixed(1) + '%';
// 空状态组件
const Empty = ({ icon, text }) => html`
<div style=${{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '40px 20px', color: 'rgba(255,255,255,0.4)' }}>
<div style=${{ fontSize: '32px', marginBottom: '12px', opacity: 0.5 }}>${icon}</div>
<div style=${{ fontSize: '13px' }}>${text}</div>
</div>
`;
// 指标卡片组件
const MetricCard = ({ icon, value, label, sub, gradient, delay = 0, alert = false, highlight = false }) => html`
<div class="glass anim-slide" style=${{
padding: '20px',
animationDelay: delay + 'ms',
background: alert ? 'linear-gradient(135deg, rgba(239,68,68,0.2), rgba(239,68,68,0.1))' :
highlight ? 'linear-gradient(135deg, rgba(16,185,129,0.2), rgba(16,185,129,0.1))' : undefined,
borderColor: alert ? 'rgba(239,68,68,0.3)' : highlight ? 'rgba(16,185,129,0.3)' : undefined
}}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div style=${{
width: '48px', height: '48px', borderRadius: '14px',
background: gradient || 'linear-gradient(135deg, #6366f1, #8b5cf6)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '22px', boxShadow: '0 8px 20px rgba(99, 102, 241, 0.3)'
}}>${icon}</div>
${alert && html`<span class="anim-pulse" style=${{ width: '10px', height: '10px', borderRadius: '50%', background: '#ef4444', boxShadow: '0 0 12px rgba(239,68,68,0.6)' }}></span>`}
</div>
<div style=${{ fontSize: '28px', fontWeight: 700, color: alert ? '#f87171' : highlight ? '#34d399' : '#fff', marginBottom: '4px' }}>${value}</div>
<div style=${{ fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>${label}</div>
${sub && html`<div style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.4)', marginTop: '4px' }}>${sub}</div>`}
</div>
`;
function App() {
const [data, setData] = useState(defaultData);
const [error, setError] = useState(null);
const chartRef1 = useRef(null);
const chartRef2 = useRef(null);
const chart1 = useRef(null);
const chart2 = useRef(null);
const validate = d => d && typeof d === 'object' && Array.isArray(d.data);
// 处理数据
const processed = useMemo(() => {
if (!data.data || data.data.length < 6) return null;
const [summary = [], comparison = [], customers = [], production = [], warnings = [], trends = []] = data.data;
const s = summary[0] || {};
return { summary: s, comparison, customers, production, warnings, trends };
}, [data]);
// 图表渲染
useEffect(() => {
if (!processed || !window.G2) return;
[chart1, chart2].forEach(c => { if (c.current) { c.current.destroy(); c.current = null; } });
// 客户贡献环形图
if (chartRef1.current && processed.customers.length > 0) {
const d = processed.customers.slice(0, 5).map(x => ({
name: x.customer_name.length > 5 ? x.customer_name.slice(0, 5) + '..' : x.customer_name,
value: x.total_order_amount || 0
}));
chart1.current = new G2.Chart({ container: chartRef1.current, autoFit: true, height: 200 });
chart1.current.theme({ type: 'classicDark' });
chart1.current.coordinate({ type: 'theta', innerRadius: 0.65 });
chart1.current.interval().data(d).transform({ type: 'stackY' }).encode('y', 'value').encode('color', 'name')
.scale('color', { range: ['#818cf8', '#a78bfa', '#c084fc', '#e879f9', '#f472b6'] })
.style('stroke', 'rgba(15,15,35,0.8)').style('lineWidth', 3)
.label({ text: d => d.name, position: 'outside', fontSize: 10, fill: 'rgba(255,255,255,0.7)', connector: true })
.legend(false).tooltip({ items: [{ channel: 'y', name: '金额', valueFormatter: v => '¥' + (v/10000).toFixed(1) + '万' }] });
chart1.current.render();
}
// 月度趋势面积图
if (chartRef2.current && processed.trends.length > 0) {
const d = processed.trends.slice().reverse().map(x => ({ month: x.month?.slice(5) || '', amount: (x.sales_amount || 0) / 10000 }));
chart2.current = new G2.Chart({ container: chartRef2.current, autoFit: true, height: 180 });
chart2.current.theme({ type: 'classicDark' });
chart2.current.area().data(d).encode('x', 'month').encode('y', 'amount')
.style('fill', 'linear-gradient(90deg, rgba(99,102,241,0.6), rgba(168,85,247,0.6))')
.style('fillOpacity', 0.4)
.axis('x', { labelFontSize: 10, labelFill: 'rgba(255,255,255,0.5)', line: null, tick: null })
.axis('y', { title: false, labelFill: 'rgba(255,255,255,0.4)', labelFormatter: v => v + '万', grid: { stroke: 'rgba(255,255,255,0.05)' } });
chart2.current.line().data(d).encode('x', 'month').encode('y', 'amount')
.style('stroke', 'url(#lineGradient)').style('lineWidth', 3);
chart2.current.point().data(d).encode('x', 'month').encode('y', 'amount')
.style('fill', '#a78bfa').style('r', 5).style('stroke', '#fff').style('lineWidth', 2);
chart2.current.render();
}
return () => { [chart1, chart2].forEach(c => { if (c.current) { c.current.destroy(); c.current = null; } }); };
}, [processed]);
// 初始化
useEffect(() => {
const helper = new ChildPageHelper({
autoRenderStatus: true, enableLog: true,
onReady: async () => {
await helper.autoInitialize(() => {
const d = helper.parseTemplateData(lzwcaiComInitDate, defaultData);
if (d && validate(d)) { setData(d); setError(null); }
});
}
});
helper.expose({
setDataLzwcaiEmbedFrameFn: d => { if (validate(d)) { setData(d); setError(null); return { status: 'success' }; } return { status: 'error' }; },
getDataLzwcaiEmbedFrameFn: () => ({ status: 'success', data }),
captureScreenshotLzwcaiEmbedFrameFn: helper.createScreenshotMethod(),
getRenderStatusLzwcaiEmbedFrameFn: () => helper.getRenderStatus()
});
}, []);
// 错误状态
if (error) return html`
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '20px' }}>
<div class="glass" style=${{ padding: '48px', textAlign: 'center', maxWidth: '400px' }}>
<div class="anim-float" style=${{ fontSize: '56px', marginBottom: '20px' }}>⚠️</div>
<div style=${{ fontSize: '20px', fontWeight: 700, color: '#f87171', marginBottom: '12px' }}>数据加载异常</div>
<div style=${{ fontSize: '14px', color: 'rgba(255,255,255,0.5)' }}>${error}</div>
</div>
</div>`;
// 加载状态
if (!processed) return html`
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', padding: '20px' }}>
<div class="glass anim-glow" style=${{ padding: '56px', textAlign: 'center' }}>
<div class="anim-float" style=${{ fontSize: '56px', marginBottom: '20px' }}>📊</div>
<div style=${{ fontSize: '20px', fontWeight: 700, background: 'linear-gradient(135deg, #818cf8, #c084fc)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>加载决策数据...</div>
</div>
</div>`;
const { summary: s, comparison, customers, production, warnings, trends } = processed;
const health = healthCfg[s.health_status] || healthCfg.WARNING;
return html`
<div style=${{ padding: '24px', maxWidth: '1500px', margin: '0 auto', position: 'relative', zIndex: 1 }}>
<!-- 顶部标题栏 -->
<header class="glass anim-slide" style=${{ padding: '20px 28px', marginBottom: '24px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '16px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div class="anim-glow" style=${{
width: '56px', height: '56px', borderRadius: '16px',
background: 'linear-gradient(135deg, #6366f1, #a855f7, #ec4899)',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '26px'
}}>📊</div>
<div>
<h1 style=${{ fontSize: '24px', fontWeight: 700, color: '#fff', margin: 0, background: 'linear-gradient(135deg, #fff, rgba(255,255,255,0.8))', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>${config.title}</h1>
<p style=${{ fontSize: '12px', color: 'rgba(255,255,255,0.5)', marginTop: '4px' }}>📅 ${s.report_date || '-'} · 订单 · 生产 · 财务 · 售后</p>
</div>
</div>
<div style=${{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style=${{
padding: '10px 20px', borderRadius: '12px',
background: health.gradient,
boxShadow: '0 4px 20px ' + health.glow,
display: 'flex', alignItems: 'center', gap: '8px'
}}>
<span style=${{ fontSize: '16px', fontWeight: 700 }}>${health.icon}</span>
<span style=${{ fontSize: '14px', fontWeight: 600, color: '#fff' }}>经营${health.label}</span>
</div>
</div>
</header>
<!-- 核心指标网格 -->
<div class="metric-grid" style=${{ marginBottom: '24px' }}>
<${MetricCard} icon="📋" value=${s.sales_order_count || 0} label="销售订单" sub=${fmtMoney(s.total_sales_amount)} gradient="linear-gradient(135deg, #6366f1, #8b5cf6)" delay=${0} />
<${MetricCard} icon="🏭" value=${s.work_order_count || 0} label="生产工单" sub=${'完成 ' + (s.completed_work_orders || 0) + ' / 进行 ' + (s.in_progress_work_orders || 0)} gradient="linear-gradient(135deg, #3b82f6, #06b6d4)" delay=${50} />
<${MetricCard} icon="📊" value=${fmtPct(s.production_completion_rate)} label="生产完成率" sub=${(s.completed_qty || 0) + ' / ' + (s.planned_qty || 0)} gradient="linear-gradient(135deg, #10b981, #14b8a6)" delay=${100} highlight=${s.production_completion_rate >= 90} />
<${MetricCard} icon="✅" value=${fmtPct(s.pass_rate)} label="质检合格率" sub=${(s.pass_qty || 0) + ' 件合格'} gradient="linear-gradient(135deg, #22c55e, #10b981)" delay=${150} highlight=${s.pass_rate >= 98} />
<${MetricCard} icon="💰" value=${fmtMoney(s.net_cash_flow)} label="净现金流" sub=${'收入 ' + fmtMoney(s.total_ar_amount)} gradient="linear-gradient(135deg, #f59e0b, #f97316)" delay=${200} highlight=${s.net_cash_flow > 0} />
<${MetricCard} icon="↩️" value=${fmtPct(s.return_rate)} label="退货率" sub=${fmtMoney(s.total_return_amount)} gradient="linear-gradient(135deg, #ef4444, #f97316)" delay=${250} alert=${s.return_rate > 5} />
</div>
<!-- 主内容区 -->
<div class="main-grid" style=${{ marginBottom: '24px' }}>
<!-- 左栏 -->
<div style=${{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<!-- 订单概况 -->
<div class="glass anim-slide" style=${{ padding: '20px', animationDelay: '300ms' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '16px' }}>
<div style=${{ width: '32px', height: '32px', borderRadius: '10px', background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px' }}>📋</div>
<span style=${{ fontSize: '15px', fontWeight: 600, color: '#fff' }}>订单概况</span>
</div>
<div style=${{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
${[
{ l: '订单总数', v: s.sales_order_count || 0, c: '#818cf8' },
{ l: '订单均价', v: fmtMoney(s.avg_order_amount), c: '#a78bfa' },
{ l: '已付款', v: s.paid_order_count || 0, c: '#34d399' },
{ l: '部分付款', v: s.partial_paid_count || 0, c: '#fbbf24' },
{ l: '未付款', v: s.unpaid_order_count || 0, c: '#f87171' },
{ l: '应收款', v: fmtMoney(s.receivable_amount), c: '#fb923c' }
].map((item, i) => html`
<div key=${i} style=${{ padding: '12px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px', borderLeft: '3px solid ' + item.c }}>
<div style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.5)', marginBottom: '4px' }}>${item.l}</div>
<div style=${{ fontSize: '16px', fontWeight: 700, color: item.c }}>${item.v}</div>
</div>
`)}
</div>
</div>
<!-- 财务流水 -->
<div class="glass anim-slide" style=${{ padding: '20px', animationDelay: '350ms' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '16px' }}>
<div style=${{ width: '32px', height: '32px', borderRadius: '10px', background: 'linear-gradient(135deg, #f59e0b, #f97316)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px' }}>💰</div>
<span style=${{ fontSize: '15px', fontWeight: 600, color: '#fff' }}>财务流水</span>
</div>
<div style=${{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
${[
{ l: '应收入账', v: fmtMoney(s.total_ar_amount), n: s.ar_receipt_count, c: '#34d399', icon: '📥' },
{ l: '应付支出', v: fmtMoney(s.total_ap_amount), n: s.ap_payment_count, c: '#f87171', icon: '📤' },
{ l: '开票金额', v: fmtMoney(s.total_invoice_amount), n: s.invoice_count, c: '#60a5fa', icon: '🧾' },
{ l: '发货金额', v: fmtMoney(s.total_shipment_amount), n: s.shipment_count, c: '#a78bfa', icon: '🚚' }
].map((item, i) => html`
<div key=${i} style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 12px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span>${item.icon}</span>
<span style=${{ fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>${item.l}</span>
</div>
<div style=${{ textAlign: 'right' }}>
<span style=${{ fontSize: '14px', fontWeight: 700, color: item.c }}>${item.v}</span>
<span style=${{ fontSize: '10px', color: 'rgba(255,255,255,0.4)', marginLeft: '6px' }}>${item.n}</span>
</div>
</div>
`)}
</div>
</div>
</div>
<!-- 中栏 -->
<div style=${{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<!-- 客户贡献 -->
<div class="glass anim-slide" style=${{ padding: '20px', animationDelay: '400ms' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style=${{ width: '32px', height: '32px', borderRadius: '10px', background: 'linear-gradient(135deg, #ec4899, #f472b6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px' }}>👥</div>
<span style=${{ fontSize: '15px', fontWeight: 600, color: '#fff' }}>客户贡献 TOP5</span>
</div>
<span style=${{ padding: '4px 10px', background: 'rgba(236,72,153,0.2)', borderRadius: '20px', fontSize: '11px', color: '#f472b6' }}>${customers.length} 客户</span>
</div>
${customers.length > 0 ? html`
<div style=${{ display: 'flex', gap: '20px' }}>
<div ref=${chartRef1} style=${{ flex: '0 0 200px' }}></div>
<div style=${{ flex: 1, display: 'flex', flexDirection: 'column', gap: '8px' }}>
${customers.slice(0, 5).map((c, i) => html`
<div key=${i} style=${{
display: 'flex', alignItems: 'center', gap: '10px', padding: '10px 12px',
background: i === 0 ? 'linear-gradient(135deg, rgba(129,140,248,0.2), rgba(167,139,250,0.1))' : 'rgba(255,255,255,0.05)',
borderRadius: '10px', border: i === 0 ? '1px solid rgba(129,140,248,0.3)' : '1px solid transparent'
}}>
<span style=${{
width: '24px', height: '24px', borderRadius: '8px',
background: i === 0 ? 'linear-gradient(135deg, #818cf8, #a78bfa)' : 'rgba(255,255,255,0.1)',
color: '#fff', fontSize: '11px', fontWeight: 700,
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>${i + 1}</span>
<div style=${{ flex: 1, minWidth: 0 }}>
<div style=${{ fontSize: '13px', fontWeight: 600, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>${c.customer_name}</div>
<div style=${{ fontSize: '10px', color: 'rgba(255,255,255,0.4)' }}>${c.order_count}单 · 贡献${fmtPct(c.contribution_rate)}</div>
</div>
<span style=${{ fontSize: '14px', fontWeight: 700, color: '#34d399' }}>${fmtMoney(c.total_order_amount)}</span>
</div>
`)}
</div>
</div>
` : html`<${Empty} icon="👥" text="暂无客户数据" />`}
</div>
<!-- 销售趋势 -->
<div class="glass anim-slide" style=${{ padding: '20px', animationDelay: '450ms' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style=${{ width: '32px', height: '32px', borderRadius: '10px', background: 'linear-gradient(135deg, #06b6d4, #0ea5e9)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px' }}>📈</div>
<span style=${{ fontSize: '15px', fontWeight: 600, color: '#fff' }}>销售趋势</span>
</div>
</div>
${trends.length > 0 ? html`
<div ref=${chartRef2} style=${{ marginBottom: '16px' }}></div>
<div style=${{ display: 'flex', justifyContent: 'space-around', padding: '12px 0', borderTop: '1px solid rgba(255,255,255,0.1)' }}>
${trends.slice(0, 4).map((t, i) => html`
<div key=${i} style=${{ textAlign: 'center' }}>
<div style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.4)', marginBottom: '4px' }}>${t.month}</div>
<div style=${{ fontSize: '16px', fontWeight: 700, color: '#fff' }}>${t.order_count}单</div>
<div style=${{ fontSize: '12px', color: '#34d399' }}>${fmtMoney(t.sales_amount)}</div>
</div>
`)}
</div>
` : html`<${Empty} icon="📈" text="暂无趋势数据" />`}
</div>
</div>
<!-- 右栏 -->
<div style=${{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<!-- 生产进度 -->
<div class="glass anim-slide" style=${{ padding: '20px', animationDelay: '500ms' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style=${{ width: '32px', height: '32px', borderRadius: '10px', background: 'linear-gradient(135deg, #10b981, #14b8a6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px' }}>🏭</div>
<span style=${{ fontSize: '15px', fontWeight: 600, color: '#fff' }}>生产进度</span>
</div>
</div>
${production.filter(p => p.work_order_count > 0).length > 0 ? html`
<div style=${{ display: 'flex', flexDirection: 'column', gap: '10px', maxHeight: '220px', overflowY: 'auto' }}>
${production.filter(p => p.work_order_count > 0).map((p, i) => {
const rate = p.completion_rate || 0;
const color = rate >= 100 ? '#34d399' : rate >= 70 ? '#fbbf24' : '#f87171';
return html`
<div key=${i} style=${{ padding: '12px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style=${{ fontSize: '13px', fontWeight: 600, color: '#fff' }}>${p.product_category}</span>
<span style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.5)' }}>${p.work_order_count}单</span>
</div>
<div style=${{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style=${{ flex: 1, height: '8px', background: 'rgba(255,255,255,0.1)', borderRadius: '4px', overflow: 'hidden' }}>
<div style=${{ width: Math.min(rate, 100) + '%', height: '100%', background: 'linear-gradient(90deg, ' + color + ', ' + color + '99)', borderRadius: '4px', transition: 'width 0.6s ease' }}></div>
</div>
<span style=${{ fontSize: '13px', fontWeight: 700, color: color, minWidth: '50px', textAlign: 'right' }}>${fmtPct(rate)}</span>
</div>
<div style=${{ fontSize: '10px', color: 'rgba(255,255,255,0.4)', marginTop: '6px' }}>
完成 ${p.completed_qty || 0} / 计划 ${p.planned_qty || 0} · ${p.worker_count || 0}人 · ${p.total_work_hours || 0}h
</div>
</div>
`;
})}
</div>
` : html`<${Empty} icon="🏭" text="暂无生产数据" />`}
</div>
<!-- 预警指标 -->
<div class="glass anim-slide" style=${{ padding: '20px', animationDelay: '550ms' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div style=${{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style=${{ width: '32px', height: '32px', borderRadius: '10px', background: 'linear-gradient(135deg, #ef4444, #f97316)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px' }}>⚠️</div>
<span style=${{ fontSize: '15px', fontWeight: 600, color: '#fff' }}>预警指标</span>
</div>
${warnings.filter(w => w.status === 'EXCEEDED').length > 0 && html`
<span class="anim-pulse" style=${{ padding: '4px 10px', background: 'rgba(239,68,68,0.2)', borderRadius: '20px', fontSize: '11px', color: '#f87171' }}>
${warnings.filter(w => w.status === 'EXCEEDED').length} 项超标
</span>
`}
</div>
${warnings.length > 0 ? html`
<div style=${{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
${warnings.map((w, i) => {
const cfg = metricCfg[w.status] || metricCfg.NORMAL;
const names = {
'unpaid_order_amount': '未付款金额',
'defect_rate_pct': '不良率',
'production_completion_rate': '生产完成率',
'return_rate_pct': '退货率'
};
return html`
<div key=${i} style=${{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '12px', background: cfg.bg, borderRadius: '10px',
border: '1px solid ' + cfg.border
}}>
<div>
<div style=${{ fontSize: '13px', fontWeight: 600, color: '#fff' }}>${names[w.metric_name] || w.metric_name}</div>
<div style=${{ fontSize: '10px', color: 'rgba(255,255,255,0.5)', marginTop: '2px' }}>阈值: ${w.threshold}</div>
</div>
<div style=${{ textAlign: 'right' }}>
<div style=${{ fontSize: '18px', fontWeight: 700, color: cfg.color }}>${w.current_value}</div>
<div style=${{ fontSize: '10px', color: cfg.color, fontWeight: 600 }}>${w.status === 'EXCEEDED' ? '超标' : '正常'}</div>
</div>
</div>
`;
})}
</div>
` : html`<${Empty} icon="✅" text="暂无预警" />`}
</div>
</div>
</div>
<!-- 底部汇总栏 -->
<footer class="glass anim-slide" style=${{ padding: '20px 28px', animationDelay: '600ms', background: 'linear-gradient(135deg, rgba(99,102,241,0.15), rgba(168,85,247,0.1))' }}>
<div style=${{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '20px' }}>
<div class="footer-content">
<div style=${{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style=${{ width: '40px', height: '40px', borderRadius: '12px', background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '18px', flexShrink: 0 }}>👥</div>
<div>
<div style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.5)' }}>人效统计</div>
<div style=${{ fontSize: '14px', color: '#fff' }}>
<span style=${{ fontWeight: 700, color: '#818cf8' }}>${s.active_worker_count || 0}</span> 人 ·
<span style=${{ fontWeight: 700 }}>${fmt(s.total_work_hours, 1)}</span> 工时 ·
人均 <span style=${{ fontWeight: 700, color: '#34d399' }}>${fmt(s.output_per_worker, 1)}</span>
</div>
</div>
</div>
<div class="divider-v" style=${{ width: '1px', height: '36px', background: 'rgba(255,255,255,0.1)' }}></div>
<div style=${{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style=${{ width: '40px', height: '40px', borderRadius: '12px', background: 'linear-gradient(135deg, #10b981, #14b8a6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '18px', flexShrink: 0 }}>✅</div>
<div>
<div style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.5)' }}>质检统计</div>
<div style=${{ fontSize: '14px', color: '#fff' }}>
<span style=${{ fontWeight: 700 }}>${s.qc_batch_count || 0}</span> 批次 ·
合格 <span style=${{ fontWeight: 700, color: '#34d399' }}>${s.pass_qty || 0}</span> ·
不良 <span style=${{ fontWeight: 700, color: s.fail_qty > 0 ? '#f87171' : 'rgba(255,255,255,0.5)' }}>${s.fail_qty || 0}</span>
</div>
</div>
</div>
<div class="divider-v" style=${{ width: '1px', height: '36px', background: 'rgba(255,255,255,0.1)' }}></div>
<div style=${{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style=${{ width: '40px', height: '40px', borderRadius: '12px', background: 'linear-gradient(135deg, #f59e0b, #f97316)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '18px', flexShrink: 0 }}>📦</div>
<div>
<div style=${{ fontSize: '11px', color: 'rgba(255,255,255,0.5)' }}>采购与退货</div>
<div style=${{ fontSize: '14px', color: '#fff' }}>
采购 <span style=${{ fontWeight: 700 }}>${s.purchase_order_count || 0}</span> 单 ·
退货 <span style=${{ fontWeight: 700, color: s.return_count > 0 ? '#f87171' : 'rgba(255,255,255,0.5)' }}>${s.return_count || 0}</span> 单
</div>
</div>
</div>
</div>
<div style=${{ textAlign: 'right' }}>
<div style=${{ fontSize: '10px', color: 'rgba(255,255,255,0.4)' }}>报告生成时间</div>
<div style=${{ fontSize: '13px', color: '#a78bfa', fontWeight: 500 }}>${new Date().toLocaleString('zh-CN')}</div>
</div>
</div>
</footer>
</div>
`;
}
ReactDOM.render(html`<${App} />`, document.getElementById('root'));
}
</script>
</body>
</html>

View File

@@ -1,626 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订单延迟预警分析</title>
<script src="/LzwcaiEmbedFrameFile/LzwcaiEmbedFrameV5.js"></script>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
@keyframes slideUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.animate-slide-up { animation: slideUp 0.5s ease-out forwards; }
.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-blink { animation: blink 1.5s ease-in-out infinite; }
.glass { backdrop-filter: blur(20px); background: rgba(255,255,255,0.92); }
.gradient-text { background: linear-gradient(135deg, #f59e0b 0%, #ef4444 50%, #dc2626 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.page-bg { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 25%, #fcd34d 50%, #fbbf24 75%, #f59e0b 100%); min-height: 100vh; }
.card-shadow { box-shadow: 0 4px 24px rgba(245, 158, 11, 0.15), 0 2px 8px rgba(0,0,0,0.08); }
.warning-red { background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%); border-color: #fca5a5; }
.warning-yellow { background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); border-color: #fcd34d; }
.warning-green { background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); border-color: #86efac; }
.table-scroll { max-height: 480px; overflow-y: auto; }
.table-scroll::-webkit-scrollbar { width: 8px; }
.table-scroll::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 4px; }
.table-scroll::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
.table-scroll::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
<script>
window.addEventListener('DOMContentLoaded', async function() {
try {
const loader = new LibraryLoader({
enableLog: true, async: false,
libraries: [
{ name: 'react', src: '/LzwcaiEmbedFrameFile/lib/react.production.min.js', check: () => typeof window.React !== 'undefined' },
{ name: 'reactDOM', src: '/LzwcaiEmbedFrameFile/lib/react-dom.production.min.js', check: () => typeof window.ReactDOM !== 'undefined' },
{ name: 'htm', src: '/LzwcaiEmbedFrameFile/lib/htm.js', check: () => typeof window.htm !== 'undefined' },
{ name: 'tailwindcss', src: '/LzwcaiEmbedFrameFile/lib/tailwindcss-3.4.17.js', check: () => typeof window.tailwind !== 'undefined' },
{ name: 'g2', src: '/LzwcaiEmbedFrameFile/lib/g2@5.2.4.min.js', check: () => typeof window.G2 !== 'undefined' }
]
});
await loader.loadAll();
initReactApp();
} catch (error) { console.error('[OrderDelayWarning] 库加载失败:', error); }
});
function initReactApp() {
const { useState, useEffect, useMemo, useRef } = React;
const html = htm.bind(React.createElement);
const config = { enableLog: true, title: '订单延迟预警分析' };
const lzwcaiComInitDate = '{{lzwcaiComInitDate}}';
const defaultData = { success: false, data: [[]] };
// 预警等级配置
const warningConfig = {
'RED': { label: '高风险', bg: 'bg-red-500', light: 'bg-red-50', text: 'text-red-600', border: 'border-red-200', icon: '🔴', gradient: 'from-red-500 to-rose-600' },
'YELLOW': { label: '中风险', bg: 'bg-amber-500', light: 'bg-amber-50', text: 'text-amber-600', border: 'border-amber-200', icon: '🟡', gradient: 'from-amber-500 to-orange-500' },
'GREEN': { label: '低风险', bg: 'bg-emerald-500', light: 'bg-emerald-50', text: 'text-emerald-600', border: 'border-emerald-200', icon: '🟢', gradient: 'from-emerald-500 to-teal-500' }
};
// 风险因素配置
const riskFactorConfig = {
'NORMAL': { label: '正常', icon: '✅' },
'PRODUCTION_DELAY': { label: '生产延迟', icon: '🏭' },
'LOGISTICS_DELAY': { label: '物流延误', icon: '🚚' },
'EQUIPMENT_FAULT': { label: '设备故障', icon: '⚙️' },
'QUALITY_ISSUE': { label: '质量问题', icon: '🔍' },
'HIGH_DEFECT': { label: '高缺陷率', icon: '⚠️' },
'HIGH_SCRAP': { label: '高报废率', icon: '🗑️' },
'WORK_ORDER_LAG': { label: '工单滞后', icon: '📋' }
};
// 空状态组件
const EmptyState = ({ icon, title, desc }) => html`
<div class="flex flex-col items-center justify-center py-12 text-center">
<div class="w-16 h-16 bg-gradient-to-br from-amber-100 to-orange-100 rounded-2xl flex items-center justify-center text-3xl mb-4 border border-amber-200">${icon}</div>
<p class="text-sm font-medium text-slate-500">${title}</p>
<p class="text-xs text-slate-400 mt-1">${desc}</p>
</div>
`;
// 分页组件
const Pagination = ({ current, total, pageSize, onChange }) => {
const totalPages = Math.ceil(total / pageSize);
if (totalPages <= 1) return null;
const pages = [];
const showPages = 5;
let start = Math.max(1, current - Math.floor(showPages / 2));
let end = Math.min(totalPages, start + showPages - 1);
if (end - start < showPages - 1) start = Math.max(1, end - showPages + 1);
for (let i = start; i <= end; i++) pages.push(i);
return html`
<div class="flex items-center justify-between px-5 py-4 border-t border-slate-100 bg-slate-50/50">
<span class="text-xs text-slate-500">共 ${total} 条记录,第 ${current}/${totalPages} 页</span>
<div class="flex items-center gap-1">
<button onClick=${() => current > 1 && onChange(current - 1)} disabled=${current <= 1}
class="px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${current <= 1 ? 'bg-slate-100 text-slate-400 cursor-not-allowed' : 'bg-white text-slate-600 hover:bg-amber-50 hover:text-amber-600 border border-slate-200'}">
上一页
</button>
${pages.map(p => html`
<button key=${p} onClick=${() => onChange(p)}
class="w-8 h-8 rounded-lg text-xs font-medium transition-all ${p === current ? 'bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-md' : 'bg-white text-slate-600 hover:bg-amber-50 border border-slate-200'}">
${p}
</button>
`)}
<button onClick=${() => current < totalPages && onChange(current + 1)} disabled=${current >= totalPages}
class="px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${current >= totalPages ? 'bg-slate-100 text-slate-400 cursor-not-allowed' : 'bg-white text-slate-600 hover:bg-amber-50 hover:text-amber-600 border border-slate-200'}">
下一页
</button>
</div>
</div>
`;
};
function ChartApp() {
const [rawData, setRawData] = useState(defaultData);
const [pageHelper, setPageHelper] = useState(null);
const [error, setError] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [sortField, setSortField] = useState('delay_probability_pct');
const [sortOrder, setSortOrder] = useState('desc');
const pageSize = 15;
const chartRef1 = useRef(null);
const chartRef2 = useRef(null);
const chartRef3 = useRef(null);
const chartInstance1 = useRef(null);
const chartInstance2 = useRef(null);
const chartInstance3 = useRef(null);
const validateData = (data) => {
if (!data || typeof data !== 'object') return { valid: false, error: '数据格式错误' };
if (!Array.isArray(data.data)) return { valid: false, error: 'data 字段应为数组' };
return { valid: true };
};
const safeArray = (arr) => Array.isArray(arr) ? arr : [];
const safeNumber = (val, def = 0) => {
const n = parseFloat(val);
return isNaN(n) ? def : n;
};
const processedData = useMemo(() => {
if (!rawData.data || rawData.data.length < 1) return null;
const orders = safeArray(rawData.data[0]).map(o => ({
...o,
order_amount: safeNumber(o.order_amount),
avg_production_days: safeNumber(o.avg_production_days),
avg_logistics_delay_days: safeNumber(o.avg_logistics_delay_days),
historical_delay_count: safeNumber(o.historical_delay_count),
defect_rate_pct: safeNumber(o.defect_rate_pct),
scrap_rate_pct: safeNumber(o.scrap_rate_pct),
active_work_order_count: safeNumber(o.active_work_order_count),
lagging_work_order_count: safeNumber(o.lagging_work_order_count),
delay_probability_pct: safeNumber(o.delay_probability_pct)
}));
// 统计各预警等级数量
const warningStats = { RED: 0, YELLOW: 0, GREEN: 0 };
orders.forEach(o => {
const level = o.warning_level || 'GREEN';
if (warningStats[level] !== undefined) warningStats[level]++;
});
// 统计风险因素分布
const riskStats = {};
orders.forEach(o => {
const factor = o.primary_risk_factor || 'NORMAL';
if (!riskStats[factor]) riskStats[factor] = 0;
riskStats[factor]++;
});
// 按客户统计
const customerStats = {};
orders.forEach(o => {
const name = o.customer_name || '未知';
if (!customerStats[name]) customerStats[name] = { count: 0, amount: 0, redCount: 0, yellowCount: 0 };
customerStats[name].count++;
customerStats[name].amount += o.order_amount;
if (o.warning_level === 'RED') customerStats[name].redCount++;
if (o.warning_level === 'YELLOW') customerStats[name].yellowCount++;
});
// 汇总指标
const totalOrders = orders.length;
const totalAmount = orders.reduce((s, o) => s + o.order_amount, 0);
const avgDelayProb = totalOrders > 0 ? orders.reduce((s, o) => s + o.delay_probability_pct, 0) / totalOrders : 0;
const highRiskCount = warningStats.RED + warningStats.YELLOW;
return {
orders, warningStats, riskStats, customerStats,
totalOrders, totalAmount, avgDelayProb, highRiskCount
};
}, [rawData]);
// 排序后的订单
const sortedOrders = useMemo(() => {
if (!processedData) return [];
const sorted = [...processedData.orders];
sorted.sort((a, b) => {
let aVal = a[sortField], bVal = b[sortField];
if (typeof aVal === 'string') aVal = aVal.toLowerCase();
if (typeof bVal === 'string') bVal = bVal.toLowerCase();
if (sortOrder === 'asc') return aVal > bVal ? 1 : -1;
return aVal < bVal ? 1 : -1;
});
return sorted;
}, [processedData, sortField, sortOrder]);
// 分页数据
const pagedOrders = sortedOrders.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// 渲染图表
useEffect(() => {
if (!processedData || !window.G2) return;
// 清理旧图表
[chartInstance1, chartInstance2, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
// 预警等级分布饼图
if (chartRef1.current && Object.values(processedData.warningStats).some(v => v > 0)) {
const pieData = [
{ name: '高风险', value: processedData.warningStats.RED, color: '#ef4444' },
{ name: '中风险', value: processedData.warningStats.YELLOW, color: '#f59e0b' },
{ name: '低风险', value: processedData.warningStats.GREEN, color: '#10b981' }
].filter(d => d.value > 0);
chartInstance1.current = new G2.Chart({ container: chartRef1.current, autoFit: true, height: 200 });
chartInstance1.current.coordinate({ type: 'theta', innerRadius: 0.6 });
chartInstance1.current.interval()
.data(pieData)
.transform({ type: 'stackY' })
.encode('y', 'value').encode('color', 'name')
.style('stroke', '#fff').style('lineWidth', 3)
.label({ text: d => d.name + '\n' + d.value + '单', position: 'outside', fontSize: 11, fill: '#64748b' })
.scale('color', { range: ['#ef4444', '#f59e0b', '#10b981'] })
.legend(false)
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v + ' 单' }] });
chartInstance1.current.render();
}
// 风险因素分布柱状图
if (chartRef2.current && Object.keys(processedData.riskStats).length > 0) {
const riskData = Object.entries(processedData.riskStats)
.map(([key, value]) => ({
factor: (riskFactorConfig[key] || { label: key }).label,
count: value
}))
.sort((a, b) => b.count - a.count);
chartInstance2.current = new G2.Chart({ container: chartRef2.current, autoFit: true, height: 200 });
chartInstance2.current.interval()
.data(riskData)
.encode('x', 'factor').encode('y', 'count').encode('color', 'factor')
.style('radius', 6)
.axis('x', { labelAutoRotate: true, labelFontSize: 10, title: false })
.axis('y', { title: false, labelFormatter: v => v + '单' })
.legend(false)
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v + ' 单' }] });
chartInstance2.current.render();
}
// 客户订单金额TOP5
if (chartRef3.current && Object.keys(processedData.customerStats).length > 0) {
const customerData = Object.entries(processedData.customerStats)
.map(([name, stats]) => ({ name: name.length > 8 ? name.slice(0, 8) + '..' : name, amount: stats.amount }))
.sort((a, b) => b.amount - a.amount)
.slice(0, 6);
chartInstance3.current = new G2.Chart({ container: chartRef3.current, autoFit: true, height: 200 });
chartInstance3.current.interval()
.data(customerData)
.encode('x', 'name').encode('y', 'amount').encode('color', 'name')
.style('radius', 6)
.axis('x', { labelAutoRotate: true, labelFontSize: 10, title: false })
.axis('y', { title: false, labelFormatter: v => (v/10000).toFixed(0) + '万' })
.legend(false)
.scale('color', { range: ['#f59e0b', '#fb923c', '#fbbf24', '#fcd34d', '#fde68a', '#fef3c7'] })
.tooltip({ items: [{ channel: 'y', valueFormatter: v => '¥' + (v/10000).toFixed(2) + '万' }] });
chartInstance3.current.render();
}
return () => {
[chartInstance1, chartInstance2, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
};
}, [processedData]);
useEffect(() => {
const helper = new ChildPageHelper({
autoRenderStatus: true, enableLog: config.enableLog,
onReady: async () => {
await helper.autoInitialize(() => {
const finalInitData = helper.parseTemplateData(lzwcaiComInitDate, defaultData);
if (finalInitData) {
const v = validateData(finalInitData);
v.valid ? (setRawData(finalInitData), setError(null)) : setError(v.error);
}
});
}
});
setPageHelper(helper);
helper.expose({
setDataLzwcaiEmbedFrameFn: (data) => {
const v = validateData(data);
if (v.valid) { setRawData(data); setError(null); return { status: 'success' }; }
setError(v.error); return { status: 'error', message: v.error };
},
getDataLzwcaiEmbedFrameFn: () => ({ status: 'success', data: rawData }),
captureScreenshotLzwcaiEmbedFrameFn: helper.createScreenshotMethod(),
getRenderStatusLzwcaiEmbedFrameFn: () => helper.getRenderStatus()
});
}, []);
const formatMoney = v => '¥' + Number(v || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2 });
const formatWan = v => ((v || 0) / 10000).toFixed(2);
const getWarning = level => warningConfig[level] || warningConfig['GREEN'];
const getRiskFactor = factor => riskFactorConfig[factor] || { label: factor, icon: '❓' };
const handleSort = (field) => {
if (sortField === field) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortOrder('desc');
}
setCurrentPage(1);
};
const SortIcon = ({ field }) => {
if (sortField !== field) return html`<span class="text-slate-300 ml-1">↕</span>`;
return html`<span class="text-amber-500 ml-1">${sortOrder === 'asc' ? '↑' : '↓'}</span>`;
};
if (error) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-2xl p-8 max-w-sm text-center border border-red-200">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-red-400 to-rose-500 rounded-2xl flex items-center justify-center text-3xl shadow-lg">⚠️</div>
<h3 class="text-lg font-bold text-red-600 mb-2">数据异常</h3>
<p class="text-sm text-red-500">${error}</p>
</div>
</div>`;
if (!processedData) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-xl p-10 max-w-sm text-center border border-amber-200">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-amber-400 to-orange-500 rounded-2xl flex items-center justify-center text-3xl animate-pulse">📦</div>
<h3 class="text-lg font-bold text-amber-600 mb-2">加载中</h3>
<p class="text-sm text-slate-400">等待订单数据...</p>
</div>
</div>`;
const { warningStats, riskStats, customerStats, totalOrders, totalAmount, avgDelayProb, highRiskCount } = processedData;
return html`
<div class="page-bg p-4 sm:p-6">
<div class="max-w-7xl mx-auto space-y-5">
<!-- 头部 -->
<div class="glass rounded-2xl p-5 card-shadow border border-white/60">
<div class="flex items-center justify-between flex-wrap gap-4">
<div class="flex items-center gap-4">
<div class="w-14 h-14 bg-gradient-to-br from-amber-500 via-orange-500 to-red-500 rounded-2xl flex items-center justify-center shadow-lg shadow-orange-500/30">
<span class="text-white text-2xl">⏰</span>
</div>
<div>
<h1 class="text-2xl font-bold gradient-text">${config.title}</h1>
<p class="text-xs text-slate-500 mt-0.5">生产周期 · 物流延误 · 设备故障 · 红/黄/绿三级预警</p>
</div>
</div>
<div class="flex items-center gap-3">
${warningStats.RED > 0 && html`
<span class="px-4 py-2 bg-red-50 border border-red-200 rounded-xl text-sm font-bold text-red-600 flex items-center gap-2 animate-pulse">
<span class="w-2.5 h-2.5 bg-red-500 rounded-full animate-blink"></span>
${warningStats.RED} 个高风险
</span>
`}
${warningStats.YELLOW > 0 && html`
<span class="px-4 py-2 bg-amber-50 border border-amber-200 rounded-xl text-sm font-bold text-amber-600 flex items-center gap-2">
<span class="w-2.5 h-2.5 bg-amber-500 rounded-full"></span>
${warningStats.YELLOW} 个中风险
</span>
`}
</div>
</div>
</div>
<!-- 核心指标卡片 -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
${[
{ icon: '📦', label: '订单总数', value: totalOrders, unit: '单', color: 'from-blue-500 to-indigo-500', shadow: 'shadow-blue-200/50' },
{ icon: '💰', label: '订单总额', value: formatWan(totalAmount), unit: '万', color: 'from-emerald-500 to-teal-500', shadow: 'shadow-emerald-200/50' },
{ icon: '📊', label: '平均延迟概率', value: avgDelayProb.toFixed(1), unit: '%', color: 'from-purple-500 to-pink-500', shadow: 'shadow-purple-200/50' },
{ icon: '⚠️', label: '风险订单', value: highRiskCount, unit: '单', color: highRiskCount > 0 ? 'from-red-500 to-rose-500' : 'from-slate-400 to-slate-500', shadow: highRiskCount > 0 ? 'shadow-red-200/50' : 'shadow-slate-200/50' }
].map((item, i) => html`
<div key=${i} class="glass rounded-2xl p-5 card-shadow border border-white/60 hover:shadow-lg transition-all animate-slide-up" style=${{ animationDelay: i * 80 + 'ms' }}>
<div class="flex items-center justify-between mb-3">
<span class="w-11 h-11 bg-gradient-to-br ${item.color} rounded-xl flex items-center justify-center text-lg shadow-md ${item.shadow}">${item.icon}</span>
</div>
<p class="text-3xl font-black text-slate-800">${item.value}<span class="text-sm font-normal text-slate-400 ml-1">${item.unit}</span></p>
<p class="text-xs text-slate-500 mt-1">${item.label}</p>
</div>
`)}
</div>
<!-- 预警等级分布卡片 -->
<div class="grid grid-cols-3 gap-4">
${[
{ level: 'RED', count: warningStats.RED },
{ level: 'YELLOW', count: warningStats.YELLOW },
{ level: 'GREEN', count: warningStats.GREEN }
].map((item, i) => {
const wc = getWarning(item.level);
const pct = totalOrders > 0 ? (item.count / totalOrders * 100).toFixed(1) : 0;
return html`
<div key=${item.level} class="glass rounded-2xl p-5 card-shadow border ${wc.border} hover:shadow-lg transition-all animate-slide-up" style=${{ animationDelay: i * 60 + 'ms' }}>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<span class="w-10 h-10 bg-gradient-to-br ${wc.gradient} rounded-xl flex items-center justify-center text-lg shadow-md">${wc.icon}</span>
<span class="font-bold ${wc.text}">${wc.label}</span>
</div>
<span class="text-2xl font-black ${wc.text}">${item.count}</span>
</div>
<div class="h-2 bg-slate-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r ${wc.gradient} rounded-full transition-all" style=${{ width: pct + '%' }}></div>
</div>
<p class="text-xs text-slate-400 mt-2 text-right">${pct}%</p>
</div>
`;
})}
</div>
<!-- 图表区域 -->
<div class="grid lg:grid-cols-3 gap-4">
<!-- 预警分布 -->
<div class="glass rounded-2xl card-shadow border border-white/60 overflow-hidden">
<div class="px-5 py-4 border-b border-slate-100 flex items-center gap-3">
<span class="w-8 h-8 bg-gradient-to-br from-amber-500 to-orange-500 rounded-lg flex items-center justify-center text-white text-sm">📊</span>
<h3 class="text-sm font-bold text-slate-700">预警等级分布</h3>
</div>
<div class="p-4">
${Object.values(warningStats).some(v => v > 0)
? html`<div ref=${chartRef1} class="w-full"></div>`
: html`<${EmptyState} icon="📊" title="暂无预警数据" desc="等待订单数据加载" />`
}
</div>
</div>
<!-- 风险因素分布 -->
<div class="glass rounded-2xl card-shadow border border-white/60 overflow-hidden">
<div class="px-5 py-4 border-b border-slate-100 flex items-center gap-3">
<span class="w-8 h-8 bg-gradient-to-br from-rose-500 to-pink-500 rounded-lg flex items-center justify-center text-white text-sm">⚠️</span>
<h3 class="text-sm font-bold text-slate-700">风险因素分布</h3>
</div>
<div class="p-4">
${Object.keys(riskStats).length > 0
? html`<div ref=${chartRef2} class="w-full"></div>`
: html`<${EmptyState} icon="⚠️" title="暂无风险数据" desc="等待风险因素数据加载" />`
}
</div>
</div>
<!-- 客户订单金额TOP -->
<div class="glass rounded-2xl card-shadow border border-white/60 overflow-hidden">
<div class="px-5 py-4 border-b border-slate-100 flex items-center gap-3">
<span class="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-500 rounded-lg flex items-center justify-center text-white text-sm">👥</span>
<h3 class="text-sm font-bold text-slate-700">客户订单金额TOP</h3>
</div>
<div class="p-4">
${Object.keys(customerStats).length > 0
? html`<div ref=${chartRef3} class="w-full"></div>`
: html`<${EmptyState} icon="👥" title="暂无客户数据" desc="等待客户数据加载" />`
}
</div>
</div>
</div>
<!-- 订单明细表格 -->
<div class="glass rounded-2xl card-shadow border border-white/60 overflow-hidden">
<div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="w-8 h-8 bg-gradient-to-br from-amber-500 to-orange-500 rounded-lg flex items-center justify-center text-white text-sm">📋</span>
<h3 class="text-sm font-bold text-slate-700">订单延迟预警明细</h3>
</div>
<span class="px-3 py-1 bg-amber-50 text-amber-600 rounded-full text-xs font-medium border border-amber-200">${totalOrders} 条记录</span>
</div>
${sortedOrders.length > 0 ? html`
<div class="table-scroll">
<table class="w-full text-sm">
<thead class="bg-slate-50 sticky top-0 z-10">
<tr>
<th class="px-4 py-3 text-left text-xs font-semibold text-slate-500 cursor-pointer hover:text-amber-600" onClick=${() => handleSort('order_number')}>
订单号<${SortIcon} field="order_number" />
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-slate-500 cursor-pointer hover:text-amber-600" onClick=${() => handleSort('customer_name')}>
客户<${SortIcon} field="customer_name" />
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-slate-500 cursor-pointer hover:text-amber-600" onClick=${() => handleSort('order_date')}>
订单日期<${SortIcon} field="order_date" />
</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-slate-500 cursor-pointer hover:text-amber-600" onClick=${() => handleSort('order_amount')}>
金额<${SortIcon} field="order_amount" />
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-slate-500">生产/物流</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-slate-500">缺陷/报废率</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-slate-500 cursor-pointer hover:text-amber-600" onClick=${() => handleSort('delay_probability_pct')}>
延迟概率<${SortIcon} field="delay_probability_pct" />
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-slate-500 cursor-pointer hover:text-amber-600" onClick=${() => handleSort('warning_level')}>
预警等级<${SortIcon} field="warning_level" />
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-slate-500">风险因素</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
${pagedOrders.map((o, i) => {
const wc = getWarning(o.warning_level);
const rf = getRiskFactor(o.primary_risk_factor);
return html`
<tr key=${o.order_number || i} class="hover:bg-amber-50/30 transition-colors animate-fade-in" style=${{ animationDelay: i * 30 + 'ms' }}>
<td class="px-4 py-3">
<span class="font-semibold text-slate-800">${o.order_number || '-'}</span>
</td>
<td class="px-4 py-3">
<p class="font-medium text-slate-700 truncate max-w-[160px]" title=${o.customer_name}>${o.customer_name || '-'}</p>
</td>
<td class="px-4 py-3 text-center text-slate-600">${o.order_date || '-'}</td>
<td class="px-4 py-3 text-right font-bold text-slate-800">${formatMoney(o.order_amount)}</td>
<td class="px-4 py-3 text-center">
<div class="flex flex-col items-center gap-0.5">
<span class="text-xs text-slate-500">🏭 ${o.avg_production_days}天</span>
<span class="text-xs text-slate-500">🚚 ${o.avg_logistics_delay_days}天</span>
</div>
</td>
<td class="px-4 py-3 text-center">
<div class="flex flex-col items-center gap-0.5">
<span class="text-xs ${o.defect_rate_pct > 5 ? 'text-red-500 font-bold' : 'text-slate-500'}">缺陷 ${o.defect_rate_pct.toFixed(1)}%</span>
<span class="text-xs ${o.scrap_rate_pct > 10 ? 'text-red-500 font-bold' : 'text-slate-500'}">报废 ${o.scrap_rate_pct.toFixed(1)}%</span>
</div>
</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold ${o.delay_probability_pct >= 50 ? 'bg-red-100 text-red-600' : o.delay_probability_pct >= 20 ? 'bg-amber-100 text-amber-600' : 'bg-emerald-100 text-emerald-600'}">
${o.delay_probability_pct.toFixed(1)}%
</span>
</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-bold ${wc.light} ${wc.text} border ${wc.border}">
${wc.icon} ${wc.label}
</span>
</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex items-center gap-1 px-2 py-1 bg-slate-100 text-slate-600 rounded-lg text-xs" title=${rf.label}>
${rf.icon} ${rf.label}
</span>
</td>
</tr>
`;
})}
</tbody>
</table>
</div>
<${Pagination} current=${currentPage} total=${sortedOrders.length} pageSize=${pageSize} onChange=${setCurrentPage} />
` : html`<${EmptyState} icon="📋" title="暂无订单数据" desc="等待订单数据加载" />`}
</div>
<!-- 客户风险汇总 -->
${Object.keys(customerStats).length > 0 && html`
<div class="glass rounded-2xl card-shadow border border-white/60 p-5">
<div class="flex items-center gap-3 mb-4">
<span class="w-8 h-8 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-lg flex items-center justify-center text-white text-sm">👥</span>
<h3 class="text-sm font-bold text-slate-700">客户订单风险汇总</h3>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
${Object.entries(customerStats)
.sort((a, b) => b[1].amount - a[1].amount)
.slice(0, 10)
.map(([name, stats], i) => html`
<div key=${name} class="bg-white rounded-xl p-4 border border-slate-100 hover:shadow-md transition-all animate-slide-up" style=${{ animationDelay: i * 40 + 'ms' }}>
<p class="text-xs text-slate-500 mb-1 truncate" title=${name}>${name}</p>
<p class="text-lg font-black text-slate-800">¥${formatWan(stats.amount)}<span class="text-xs text-slate-400">万</span></p>
<div class="flex items-center gap-2 mt-2">
<span class="text-xs text-slate-400">${stats.count}单</span>
${stats.redCount > 0 && html`<span class="px-1.5 py-0.5 bg-red-100 text-red-600 rounded text-xs font-bold">${stats.redCount}高</span>`}
${stats.yellowCount > 0 && html`<span class="px-1.5 py-0.5 bg-amber-100 text-amber-600 rounded text-xs font-bold">${stats.yellowCount}中</span>`}
</div>
</div>
`)}
</div>
</div>
`}
<!-- 底部 -->
<div class="text-center pt-3 pb-4">
<p class="text-xs text-slate-500">
<span class="inline-flex items-center gap-1.5 px-4 py-2 glass rounded-full border border-white/60 shadow-sm">
⏰ 订单延迟预警分析 · 红/黄/绿三级智能预警
</span>
</p>
</div>
</div>
</div>
`;
}
ReactDOM.render(React.createElement(ChartApp), document.getElementById('root'));
}
</script>
</head>
<body class="m-0 p-0">
<div id="root"></div>
</body>
</html>

View File

@@ -1,680 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>供应链风险预警</title>
<script src="/LzwcaiEmbedFrameFile/LzwcaiEmbedFrameV5.js"></script>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
@keyframes slideIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } }
.animate-slide-in { animation: slideIn 0.5s ease-out forwards; }
.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
.animate-scale-in { animation: scaleIn 0.4s ease-out forwards; }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-blink { animation: blink 1.5s ease-in-out infinite; }
.animate-shake { animation: shake 0.5s ease-in-out infinite; }
.glass { backdrop-filter: blur(16px); background: rgba(255,255,255,0.85); }
.gradient-text { background: linear-gradient(135deg, #ef4444 0%, #f97316 50%, #eab308 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.page-bg { background: linear-gradient(135deg, #fef2f2 0%, #fff7ed 30%, #fefce8 70%, #f8fafc 100%); min-height: 100vh; }
.scrollbar-thin::-webkit-scrollbar { width: 6px; height: 6px; }
.scrollbar-thin::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 3px; }
.scrollbar-thin::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
.scrollbar-thin::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); z-index: 50; display: flex; align-items: center; justify-content: center; animation: fadeIn 0.2s ease-out; }
.modal-content { background: white; border-radius: 1.5rem; max-width: 90vw; max-height: 85vh; overflow: hidden; animation: scaleIn 0.3s ease-out; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); }
.clickable-row { cursor: pointer; transition: all 0.2s; }
.clickable-row:hover { background: #f8fafc !important; transform: translateX(2px); }
.clickable-card { cursor: pointer; transition: all 0.2s; }
.clickable-card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px -5px rgba(0,0,0,0.1); }
</style>
<script>
window.addEventListener('DOMContentLoaded', async function() {
try {
const loader = new LibraryLoader({
enableLog: true, async: false,
libraries: [
{ name: 'react', src: '/LzwcaiEmbedFrameFile/lib/react.production.min.js', check: () => typeof window.React !== 'undefined' },
{ name: 'reactDOM', src: '/LzwcaiEmbedFrameFile/lib/react-dom.production.min.js', check: () => typeof window.ReactDOM !== 'undefined' },
{ name: 'htm', src: '/LzwcaiEmbedFrameFile/lib/htm.js', check: () => typeof window.htm !== 'undefined' },
{ name: 'tailwindcss', src: '/LzwcaiEmbedFrameFile/lib/tailwindcss-3.4.17.js', check: () => typeof window.tailwind !== 'undefined' },
{ name: 'g2', src: '/LzwcaiEmbedFrameFile/lib/g2@5.2.4.min.js', check: () => typeof window.G2 !== 'undefined' }
]
});
await loader.loadAll();
initReactApp();
} catch (error) { console.error('[SupplyChainRiskWarning] 库加载失败:', error); }
});
function initReactApp() {
const { useState, useEffect, useMemo, useRef } = React;
const html = htm.bind(React.createElement);
const config = { enableLog: true, title: '供应链风险预警' };
const lzwcaiComInitDate = '{{lzwcaiComInitDate}}';
const defaultData = { success: false, data: [[], [], [], [], [], []] };
// 风险等级配置
const riskLevelConfig = {
'HIGH': { label: '高风险', bg: 'from-red-500 to-rose-600', light: 'bg-red-50', text: 'text-red-700', border: 'border-red-200', icon: '🔴', dot: 'bg-red-500' },
'MEDIUM': { label: '中风险', bg: 'from-amber-400 to-orange-500', light: 'bg-amber-50', text: 'text-amber-700', border: 'border-amber-200', icon: '🟡', dot: 'bg-amber-500' },
'LOW': { label: '低风险', bg: 'from-emerald-400 to-teal-500', light: 'bg-emerald-50', text: 'text-emerald-700', border: 'border-emerald-200', icon: '🟢', dot: 'bg-emerald-500' }
};
// 风险模式配置
const riskPatternConfig = {
'DELIVERY_AND_QUALITY': { label: '交期+质量双风险', icon: '⚠️', color: 'text-red-600', bg: 'bg-red-50' },
'DELIVERY_ISSUE': { label: '交期异常', icon: '⏰', color: 'text-orange-600', bg: 'bg-orange-50' },
'QUALITY_ISSUE': { label: '质量问题', icon: '🔍', color: 'text-purple-600', bg: 'bg-purple-50' },
'LOGISTICS_STALL': { label: '物流停滞', icon: '🚚', color: 'text-blue-600', bg: 'bg-blue-50' },
'NORMAL': { label: '正常', icon: '✅', color: 'text-emerald-600', bg: 'bg-emerald-50' }
};
// 行动要求配置
const actionConfig = {
'IMMEDIATE_FOLLOWUP': { label: '立即跟进', icon: '🚨', color: 'text-red-600', bg: 'bg-red-100' },
'MONITOR': { label: '持续监控', icon: '👁️', color: 'text-amber-600', bg: 'bg-amber-100' },
'NORMAL': { label: '正常', icon: '✓', color: 'text-emerald-600', bg: 'bg-emerald-100' }
};
// 风险评估配置
const assessmentConfig = {
'HIGH_RISK': { label: '高风险', color: 'text-red-600', bg: 'bg-red-50' },
'MEDIUM_RISK': { label: '中风险', color: 'text-amber-600', bg: 'bg-amber-50' },
'EXCELLENT': { label: '优秀', color: 'text-emerald-600', bg: 'bg-emerald-50' }
};
// 空状态组件
const EmptyState = ({ icon, title, desc }) => html`
<div class="flex flex-col items-center justify-center py-10 text-center">
<div class="w-14 h-14 bg-gradient-to-br from-slate-100 to-slate-200 rounded-2xl flex items-center justify-center text-2xl mb-3">${icon}</div>
<p class="text-sm font-medium text-slate-500">${title}</p>
<p class="text-xs text-slate-400 mt-1">${desc}</p>
</div>
`;
// 弹窗组件
const Modal = ({ isOpen, onClose, title, icon, children }) => {
if (!isOpen) return null;
return html`
<div class="modal-overlay" onClick=${onClose}>
<div class="modal-content w-full max-w-2xl" onClick=${e => e.stopPropagation()}>
<div class="px-6 py-4 border-b border-slate-100 bg-gradient-to-r from-slate-50 to-white flex items-center justify-between">
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
<span class="text-xl">${icon}</span>
${title}
</h3>
<button onClick=${onClose} class="w-8 h-8 rounded-full bg-slate-100 hover:bg-slate-200 flex items-center justify-center text-slate-500 hover:text-slate-700 transition-colors"></button>
</div>
<div class="p-6 max-h-[60vh] overflow-y-auto scrollbar-thin">
${children}
</div>
</div>
</div>
`;
};
function ChartApp() {
const [rawData, setRawData] = useState(defaultData);
const [pageHelper, setPageHelper] = useState(null);
const [error, setError] = useState(null);
const [supplierPage, setSupplierPage] = useState(1);
const [orderPage, setOrderPage] = useState(1);
const [filterRisk, setFilterRisk] = useState('all');
const [filterPattern, setFilterPattern] = useState('all');
// 弹窗状态
const [modalData, setModalData] = useState(null);
const [modalType, setModalType] = useState(null);
const pageSize = 10;
const chartRef1 = useRef(null);
const chartRef3 = useRef(null);
const chartInstance1 = useRef(null);
const chartInstance3 = useRef(null);
const validateData = (data) => {
if (!data || typeof data !== 'object') return { valid: false, error: '数据格式错误' };
if (!Array.isArray(data.data)) return { valid: false, error: 'data 字段应为数组' };
return { valid: true };
};
// 打开弹窗
const openModal = (type, data) => {
setModalType(type);
setModalData(data);
};
// 关闭弹窗
const closeModal = () => {
setModalType(null);
setModalData(null);
};
// 处理数据
const processedData = useMemo(() => {
if (!rawData.data || rawData.data.length < 6) return null;
const [suppliers = [], highRiskOrders = [], categoryRisk = [], monthlyTrend = [], metrics = [], riskRanking = []] = rawData.data;
const metricsMap = {};
metrics.forEach(m => { metricsMap[m.metric_name] = m; });
const riskDistribution = { HIGH: 0, MEDIUM: 0, LOW: 0 };
suppliers.forEach(s => {
if (riskDistribution[s.risk_level] !== undefined) riskDistribution[s.risk_level]++;
});
const patternStats = {};
suppliers.forEach(s => {
const pattern = s.risk_pattern || 'NORMAL';
patternStats[pattern] = (patternStats[pattern] || 0) + 1;
});
const categoryStats = categoryRisk.map(c => ({
...c,
supplier_count: parseInt(c.supplier_count || 0),
order_count: parseInt(c.order_count || 0),
return_count: parseInt(c.return_count || 0)
}));
return { suppliers, highRiskOrders, categoryRisk: categoryStats, monthlyTrend, metrics: metricsMap, riskRanking, riskDistribution, patternStats };
}, [rawData]);
const filteredSuppliers = useMemo(() => {
if (!processedData) return [];
let list = processedData.suppliers;
if (filterRisk !== 'all') list = list.filter(s => s.risk_level === filterRisk);
if (filterPattern !== 'all') list = list.filter(s => s.risk_pattern === filterPattern);
return list;
}, [processedData, filterRisk, filterPattern]);
const paginatedSuppliers = useMemo(() => {
const start = (supplierPage - 1) * pageSize;
return filteredSuppliers.slice(start, start + pageSize);
}, [filteredSuppliers, supplierPage]);
const paginatedOrders = useMemo(() => {
if (!processedData) return [];
const start = (orderPage - 1) * pageSize;
return processedData.highRiskOrders.slice(start, start + pageSize);
}, [processedData, orderPage]);
const supplierTotalPages = Math.ceil(filteredSuppliers.length / pageSize);
const orderTotalPages = Math.ceil((processedData?.highRiskOrders?.length || 0) / pageSize);
// 渲染图表
useEffect(() => {
if (!processedData || !window.G2) return;
[chartInstance1, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
if (chartRef1.current && Object.values(processedData.riskDistribution).some(v => v > 0)) {
const riskData = Object.entries(processedData.riskDistribution)
.filter(([_, v]) => v > 0)
.map(([level, count]) => ({ name: riskLevelConfig[level]?.label || level, value: count }));
chartInstance1.current = new G2.Chart({ container: chartRef1.current, autoFit: true, height: 180 });
chartInstance1.current.coordinate({ type: 'theta', innerRadius: 0.6 });
chartInstance1.current.interval()
.data(riskData)
.transform({ type: 'stackY' })
.encode('y', 'value').encode('color', 'name')
.scale('color', { range: ['#ef4444', '#f59e0b', '#10b981'] })
.style('stroke', '#fff').style('lineWidth', 2)
.label({ text: d => d.value > 0 ? `${d.name}\n${d.value}家` : '', position: 'outside', fontSize: 10 })
.legend(false)
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v + ' 家供应商' }] });
chartInstance1.current.render();
}
if (chartRef3.current && processedData.suppliers.length > 0) {
const topRiskSuppliers = [...processedData.suppliers]
.filter(s => s.total_risk_score > 0)
.sort((a, b) => b.total_risk_score - a.total_risk_score)
.slice(0, 6)
.map(s => ({ name: s.supplier_name.length > 4 ? s.supplier_name.slice(0, 4) + '..' : s.supplier_name, 风险分: s.total_risk_score, level: s.risk_level }));
if (topRiskSuppliers.length > 0) {
chartInstance3.current = new G2.Chart({ container: chartRef3.current, autoFit: true, height: 180 });
chartInstance3.current.interval()
.data(topRiskSuppliers)
.encode('x', 'name').encode('y', '风险分').encode('color', 'level')
.scale('color', { domain: ['HIGH', 'MEDIUM', 'LOW'], range: ['#ef4444', '#f59e0b', '#10b981'] })
.scale('y', { domain: [0, 100] })
.style('radius', 6)
.axis('x', { labelFontSize: 10 })
.axis('y', { title: false })
.legend(false)
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v + ' 分' }] });
chartInstance3.current.render();
}
}
return () => {
[chartInstance1, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
};
}, [processedData]);
useEffect(() => {
const helper = new ChildPageHelper({
autoRenderStatus: true, enableLog: config.enableLog,
onReady: async () => {
await helper.autoInitialize(() => {
const finalInitData = helper.parseTemplateData(lzwcaiComInitDate, defaultData);
if (finalInitData) {
const v = validateData(finalInitData);
v.valid ? (setRawData(finalInitData), setError(null)) : setError(v.error);
}
});
}
});
setPageHelper(helper);
helper.expose({
setDataLzwcaiEmbedFrameFn: (data) => {
const v = validateData(data);
if (v.valid) { setRawData(data); setError(null); return { status: 'success' }; }
setError(v.error); return { status: 'error', message: v.error };
},
getDataLzwcaiEmbedFrameFn: () => ({ status: 'success', data: rawData }),
captureScreenshotLzwcaiEmbedFrameFn: helper.createScreenshotMethod(),
getRenderStatusLzwcaiEmbedFrameFn: () => helper.getRenderStatus()
});
}, []);
const formatPercent = v => (v || 0).toFixed(1) + '%';
const getRisk = level => riskLevelConfig[level] || riskLevelConfig['LOW'];
const getPattern = pattern => riskPatternConfig[pattern] || riskPatternConfig['NORMAL'];
const getAction = action => actionConfig[action] || actionConfig['NORMAL'];
const getAssessment = assessment => assessmentConfig[assessment] || assessmentConfig['EXCELLENT'];
// 订单详情弹窗内容
const renderOrderDetail = (order) => {
const rc = getRisk(order.risk_level);
const ac = getAction(order.action_required);
const pc = getPattern(order.risk_pattern);
return html`
<div class="space-y-4">
<div class="flex items-center gap-3 p-4 rounded-xl ${order.risk_level === 'HIGH' ? 'bg-red-50' : 'bg-amber-50'}">
<span class="text-3xl">${rc.icon}</span>
<div>
<p class="text-xl font-bold text-slate-800">${order.purchase_order_number}</p>
<p class="text-sm text-slate-600">${order.supplier_name}</p>
</div>
<span class="ml-auto px-3 py-1.5 ${ac.bg} rounded-lg text-sm font-medium ${ac.color}">${ac.icon} ${ac.label}</span>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="p-3 bg-slate-50 rounded-xl">
<p class="text-xs text-slate-500 mb-1">供应商类别</p>
<p class="font-semibold text-slate-800">${order.supplier_category || '-'}</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl">
<p class="text-xs text-slate-500 mb-1">风险等级</p>
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-sm font-medium ${rc.light} ${rc.text}">${rc.icon} ${rc.label}</span>
</div>
<div class="p-3 bg-slate-50 rounded-xl">
<p class="text-xs text-slate-500 mb-1">下单日期</p>
<p class="font-semibold text-slate-800">${order.order_date || '-'}</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl">
<p class="text-xs text-slate-500 mb-1">已等待天数</p>
<p class="font-semibold ${order.days_since_order > 30 ? 'text-red-600' : 'text-slate-800'}">${order.days_since_order} 天</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl">
<p class="text-xs text-slate-500 mb-1">质量合格率</p>
<p class="font-semibold ${order.quality_rate < 80 ? 'text-red-600' : order.quality_rate < 95 ? 'text-amber-600' : 'text-emerald-600'}">${formatPercent(order.quality_rate)}</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl">
<p class="text-xs text-slate-500 mb-1">风险模式</p>
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-sm font-medium ${pc.bg} ${pc.color}">${pc.icon} ${pc.label}</span>
</div>
</div>
${order.risk_factors && html`
<div class="p-4 bg-orange-50 rounded-xl border border-orange-100">
<p class="text-sm font-semibold text-orange-700 mb-2">⚠️ 风险因素</p>
<p class="text-sm text-orange-600">${order.risk_factors}</p>
</div>
`}
</div>
`;
};
// 供应商详情弹窗内容
const renderSupplierDetail = (s) => {
const rc = getRisk(s.risk_level);
const pc = getPattern(s.risk_pattern);
return html`
<div class="space-y-4">
<div class="flex items-center gap-3 p-4 rounded-xl ${s.risk_level === 'HIGH' ? 'bg-red-50' : s.risk_level === 'MEDIUM' ? 'bg-amber-50' : 'bg-emerald-50'}">
<span class="text-3xl">${rc.icon}</span>
<div class="flex-1">
<p class="text-xl font-bold text-slate-800">${s.supplier_name}</p>
<p class="text-sm text-slate-600">${s.supplier_category}</p>
</div>
<div class="text-center">
<span class="inline-flex items-center justify-center w-14 h-14 rounded-full text-xl font-bold ${s.total_risk_score >= 80 ? 'bg-red-100 text-red-700' : s.total_risk_score >= 50 ? 'bg-amber-100 text-amber-700' : 'bg-emerald-100 text-emerald-700'}">
${s.total_risk_score}
</span>
<p class="text-xs text-slate-500 mt-1">风险评分</p>
</div>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold text-blue-600">${s.order_count}</p>
<p class="text-xs text-slate-500">订单数</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold text-emerald-600">${s.receipt_count}</p>
<p class="text-xs text-slate-500">收货数</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold text-red-600">${s.return_count || 0}</p>
<p class="text-xs text-slate-500">退货数</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold ${s.avg_delivery_days > 100 ? 'text-red-600' : 'text-slate-700'}">${s.avg_delivery_days}</p>
<p class="text-xs text-slate-500">平均交期(天)</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold ${s.quality_rate < 80 ? 'text-red-600' : s.quality_rate < 95 ? 'text-amber-600' : 'text-emerald-600'}">${formatPercent(s.quality_rate)}</p>
<p class="text-xs text-slate-500">质量合格率</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-sm font-medium ${pc.bg} ${pc.color}">${pc.icon} ${pc.label}</span>
<p class="text-xs text-slate-500 mt-1">风险模式</p>
</div>
</div>
<div class="flex items-center gap-2 p-3 rounded-xl ${rc.light} ${rc.border} border">
<span class="text-lg">${rc.icon}</span>
<span class="font-medium ${rc.text}">风险等级: ${rc.label}</span>
</div>
</div>
`;
};
// 排名详情弹窗内容
const renderRankingDetail = (r) => {
const ac = getAssessment(r.risk_assessment);
return html`
<div class="space-y-4">
<div class="flex items-center gap-3 p-4 rounded-xl bg-gradient-to-r from-amber-50 to-orange-50">
<span class="w-12 h-12 flex items-center justify-center rounded-xl text-xl font-bold ${r.risk_rank <= 3 ? 'bg-gradient-to-br from-amber-400 to-orange-500 text-white' : 'bg-slate-100 text-slate-600'}">${r.risk_rank}</span>
<div class="flex-1">
<p class="text-xl font-bold text-slate-800">${r.supplier_name}</p>
<p class="text-sm text-slate-600">${r.supplier_category}</p>
</div>
<span class="px-3 py-1.5 rounded-lg text-sm font-medium ${ac.bg} ${ac.color}">${ac.label}</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold text-blue-600">${r.order_count || 0}</p>
<p class="text-xs text-slate-500">订单数</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold text-emerald-600">${r.receipt_count || 0}</p>
<p class="text-xs text-slate-500">收货数</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold text-red-600">${r.return_count || 0}</p>
<p class="text-xs text-slate-500">退货数</p>
</div>
<div class="p-3 bg-slate-50 rounded-xl text-center">
<p class="text-2xl font-bold ${r.return_rate > 20 ? 'text-red-600' : r.return_rate > 10 ? 'text-amber-600' : 'text-emerald-600'}">${formatPercent(r.return_rate)}</p>
<p class="text-xs text-slate-500">退货率</p>
</div>
</div>
</div>
`;
};
if (error) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-2xl p-8 max-w-sm text-center border border-red-100">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-red-400 to-rose-500 rounded-2xl flex items-center justify-center text-3xl shadow-lg">⚠️</div>
<h3 class="text-lg font-bold text-red-700 mb-2">数据异常</h3>
<p class="text-sm text-red-500">${error}</p>
</div>
</div>`;
if (!processedData) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-xl p-10 max-w-sm text-center border border-orange-100">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-orange-400 to-red-500 rounded-2xl flex items-center justify-center text-3xl animate-pulse">🔗</div>
<h3 class="text-lg font-bold text-orange-600 mb-2">加载中</h3>
<p class="text-sm text-slate-400">等待供应链数据...</p>
</div>
</div>`;
const { suppliers, highRiskOrders, categoryRisk, monthlyTrend, metrics, riskRanking, riskDistribution, patternStats } = processedData;
return html`
<div class="page-bg p-4 sm:p-5">
<div class="max-w-7xl mx-auto space-y-4">
<!-- 弹窗 -->
<${Modal} isOpen=${modalType === 'order'} onClose=${closeModal} title="订单详情" icon="📦">
${modalData && renderOrderDetail(modalData)}
<//>
<${Modal} isOpen=${modalType === 'supplier'} onClose=${closeModal} title="供应商详情" icon="🏭">
${modalData && renderSupplierDetail(modalData)}
<//>
<${Modal} isOpen=${modalType === 'ranking'} onClose=${closeModal} title="排名详情" icon="🏆">
${modalData && renderRankingDetail(modalData)}
<//>
<!-- 头部 -->
<div class="glass rounded-2xl p-5 shadow-lg border border-white/60">
<div class="flex items-center justify-between flex-wrap gap-4">
<div class="flex items-center gap-4">
<div class="w-14 h-14 bg-gradient-to-br from-red-500 via-orange-500 to-amber-500 rounded-2xl flex items-center justify-center shadow-lg shadow-red-500/30">
<span class="text-white text-2xl">🔗</span>
</div>
<div>
<h1 class="text-2xl font-bold gradient-text">${config.title}</h1>
<p class="text-xs text-slate-500 mt-0.5">供应商监控 · 交期预警 · 质量追踪</p>
</div>
</div>
<div class="flex items-center gap-3 flex-wrap">
${(() => {
const highRisk = parseInt(metrics.high_risk_suppliers?.metric_value || 0);
const overdueOrders = parseInt(metrics.overdue_orders_30d?.metric_value || 0);
const recentReturns = parseInt(metrics.recent_returns_30d?.metric_value || 0);
return html`
${highRisk > 0 && html`
<span class="px-4 py-2 bg-red-50 border border-red-200 rounded-xl text-sm font-bold text-red-600 flex items-center gap-2 animate-pulse">
<span class="w-2.5 h-2.5 bg-red-500 rounded-full animate-blink"></span>
${highRisk} 高风险供应商
</span>
`}
${overdueOrders > 0 && html`
<span class="px-4 py-2 bg-orange-50 border border-orange-200 rounded-xl text-sm font-bold text-orange-600 flex items-center gap-2">
<span class="w-2.5 h-2.5 bg-orange-500 rounded-full"></span>
${overdueOrders} 超期订单
</span>
`}
${recentReturns > 0 && html`
<span class="px-4 py-2 bg-purple-50 border border-purple-200 rounded-xl text-sm font-bold text-purple-600 flex items-center gap-2">
<span class="w-2.5 h-2.5 bg-purple-500 rounded-full"></span>
${recentReturns} 近期退货
</span>
`}
`;
})()}
</div>
</div>
</div>
<!-- 核心指标卡片 -->
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
${[
{ key: 'high_risk_suppliers', icon: '🔴', label: '高风险供应商', color: 'from-red-500 to-rose-600', shadow: 'shadow-red-200/50', alert: true },
{ key: 'medium_risk_suppliers', icon: '🟡', label: '中风险供应商', color: 'from-amber-400 to-orange-500', shadow: 'shadow-amber-200/50' },
{ key: 'low_risk_suppliers', icon: '🟢', label: '低风险供应商', color: 'from-emerald-400 to-teal-500', shadow: 'shadow-emerald-200/50' },
{ key: 'pending_orders', icon: '📦', label: '待收货订单', color: 'from-blue-400 to-indigo-500', shadow: 'shadow-blue-200/50' },
{ key: 'overdue_orders_30d', icon: '⏰', label: '超期订单(30天)', color: 'from-orange-400 to-red-500', shadow: 'shadow-orange-200/50', alert: true },
{ key: 'recent_returns_30d', icon: '↩️', label: '近期退货(30天)', color: 'from-purple-400 to-pink-500', shadow: 'shadow-purple-200/50' }
].map((item, i) => {
const m = metrics[item.key] || {};
const isAlert = item.alert && parseInt(m.metric_value || 0) > 0;
const status = m.status;
return html`
<div key=${i} class="bg-white rounded-xl p-4 shadow-md border border-slate-100/80 hover:shadow-lg transition-all animate-slide-in ${isAlert ? 'ring-2 ring-red-200 bg-red-50/30' : ''}" style=${{ animationDelay: i * 50 + 'ms' }}>
<div class="flex items-center justify-between mb-2">
<span class="w-10 h-10 bg-gradient-to-br ${item.color} rounded-xl flex items-center justify-center text-base shadow-md ${item.shadow} ${isAlert ? 'animate-shake' : ''}">${item.icon}</span>
${status && status !== 'NORMAL' && html`
<span class="px-1.5 py-0.5 text-xs rounded ${status === 'ATTENTION_NEEDED' ? 'bg-red-100 text-red-600' : status === 'DELIVERY_WARNING' ? 'bg-orange-100 text-orange-600' : 'bg-slate-100 text-slate-600'}">
${status === 'ATTENTION_NEEDED' ? '需关注' : status === 'DELIVERY_WARNING' ? '交期预警' : status}
</span>
`}
</div>
<p class="text-2xl font-black ${isAlert ? 'text-red-600' : 'text-slate-800'}">${m.metric_value || '0'}</p>
<p class="text-xs text-slate-500 mt-0.5">${item.label}</p>
</div>
`;
})}
</div>
<!-- 图表区域 -->
<div class="grid lg:grid-cols-4 gap-4">
<!-- 风险分布 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-red-400 to-rose-500 rounded-lg flex items-center justify-center text-white text-xs">📊</span>
风险等级分布
</h3>
${Object.values(riskDistribution).some(v => v > 0)
? html`<div ref=${chartRef1} class="w-full"></div>`
: html`<${EmptyState} icon="📊" title="暂无风险数据" desc="等待数据加载" />`
}
</div>
<!-- 类别质量合格率 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-orange-400 to-amber-500 rounded-lg flex items-center justify-center text-white text-xs">📈</span>
类别质量合格率
</h3>
${categoryRisk.length > 0 ? html`
<div class="space-y-2 max-h-[180px] overflow-y-auto scrollbar-thin pr-1">
${[...categoryRisk].sort((a, b) => a.quality_rate - b.quality_rate).slice(0, 6).map((c, i) => {
const rc = getRisk(c.category_risk_level);
return html`
<div key=${i} class="flex items-center gap-2 p-2 rounded-lg ${c.category_risk_level === 'HIGH' ? 'bg-red-50' : 'bg-slate-50'} hover:shadow-sm transition-all">
<span class="w-2 h-2 rounded-full ${rc.dot}"></span>
<span class="flex-1 text-xs font-medium text-slate-700 truncate">${c.supplier_category}</span>
<div class="flex items-center gap-2 shrink-0">
<div class="w-16 h-1.5 bg-slate-200 rounded-full overflow-hidden">
<div class="h-full rounded-full transition-all ${c.quality_rate < 50 ? 'bg-red-500' : c.quality_rate < 80 ? 'bg-orange-500' : c.quality_rate < 95 ? 'bg-amber-500' : 'bg-emerald-500'}" style=${{ width: Math.min(c.quality_rate, 100) + '%' }}></div>
</div>
<span class="text-xs font-bold w-12 text-right ${c.quality_rate < 50 ? 'text-red-600' : c.quality_rate < 80 ? 'text-orange-600' : c.quality_rate < 95 ? 'text-amber-600' : 'text-emerald-600'}">${formatPercent(c.quality_rate)}</span>
</div>
</div>
`;
})}
</div>
` : html`<${EmptyState} icon="📈" title="暂无类别数据" desc="等待数据加载" />`}
</div>
<!-- 风险评分TOP -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-purple-400 to-pink-500 rounded-lg flex items-center justify-center text-white text-xs"></span>
风险评分TOP
</h3>
${suppliers.filter(s => s.total_risk_score > 0).length > 0
? html`<div ref=${chartRef3} class="w-full"></div>`
: html`<${EmptyState} icon="⚡" title="暂无评分数据" desc="等待数据加载" />`
}
</div>
<!-- 月度趋势 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center text-white text-xs">📉</span>
月度趋势
</h3>
${monthlyTrend.length > 0 ? html`
<div class="space-y-2">
${monthlyTrend.map((t, i) => html`
<div key=${i} class="p-3 rounded-xl bg-gradient-to-r from-slate-50 to-white border border-slate-100">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-bold text-slate-700">${t.month_start?.slice(0, 7) || '未知'}</span>
<span class="px-2 py-0.5 rounded text-xs font-medium ${t.return_rate > 20 ? 'bg-red-100 text-red-600' : t.return_rate > 10 ? 'bg-amber-100 text-amber-600' : 'bg-emerald-100 text-emerald-600'}">
退货率 ${formatPercent(t.return_rate)}
</span>
</div>
<div class="grid grid-cols-3 gap-2 text-xs">
<div class="text-center p-1.5 bg-blue-50 rounded">
<p class="text-blue-600 font-bold">${t.receipt_count}</p>
<p class="text-slate-500">收货数</p>
</div>
<div class="text-center p-1.5 bg-orange-50 rounded">
<p class="text-orange-600 font-bold">${t.avg_delivery_days?.toFixed(0) || 0}天</p>
<p class="text-slate-500">平均交期</p>
</div>
<div class="text-center p-1.5 bg-red-50 rounded">
<p class="text-red-600 font-bold">${t.return_count}</p>
<p class="text-slate-500">退货数</p>
</div>
</div>
</div>
`)}
</div>
` : html`<${EmptyState} icon="📉" title="暂无趋势数据" desc="等待数据加载" />`}
</div>
</div>
<!-- 高风险订单预警 - 简化卡片,点击查看详情 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-red-400 to-rose-500 rounded-lg flex items-center justify-center text-white text-xs animate-blink">🚨</span>
高风险订单预警
<span class="text-xs text-slate-400 font-normal ml-2">点击卡片查看详情</span>
</h3>
<span class="px-2 py-1 bg-red-50 text-red-600 rounded-full text-xs font-medium">${highRiskOrders.length} 项</span>
</div>
${highRiskOrders.length > 0 ? html`
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3 max-h-[280px] overflow-y-auto scrollbar-thin pr-1">
${paginatedOrders.map((order, i) => {
const rc = getRisk(order.risk_level);
const ac = getAction(order.action_required);
return html`
<div key=${i} onClick=${() => openModal('order', order)}
class="clickable-card bg-gradient-to-r ${order.risk_level === 'HIGH' ? 'from-red-50 to-orange-50 border-red-100' : 'from-amber-50 to-yellow-50 border-amber-100'} rounded-xl p-3 border animate-slide-in"
style=${{ animationDelay: i * 60 + 'ms' }}>
<div class="flex items-center gap-2 mb-2">
<span class="text-lg">${rc.icon}</span>
<span class="font-bold text-slate-800 text-sm truncate flex-1">${order.purchase_order_number}</span>
</div>
<p class="text-xs text-slate-600 truncate mb-1">${order.supplier_name}</p>
<div class="flex items-center justify-between">
<span class="text-xs text-orange-600">已等 ${order.days_since_order} 天</span>
<span class="text-xs text-slate-400">点击详情 →</span>
</div>
</div>
`;
})}
</div>
${orderTotalPages > 1 && html`
<div class="mt-3 flex items-center justify-center gap-2">
<button onClick=${() => setOrderPage(p => Math.max(1, p - 1))} disabled=${orderPage === 1}
class="px-3 py-1 text-xs rounded-lg border border-slate-200 hover:bg-slate-50 disabled:opacity-50 transition-colors">上一页</button>
<span class="text-xs text-slate-500">${orderPage}/${orderTotalPages}</span>
<button onClick=${() => setOrderPage(p => Math.min(orderTotalPages, p + 1))} disabled=${orderPage === orderTotalPages}
class="px-3 py-1 text-xs rounded-lg border border-slate-200 hover:bg-slate-50 disabled:opacity-50 transition-colors">下一页</button>
</div>
`}
` : html`<${EmptyState} icon="✨" title="暂无高风险订单" desc="供应链运行正常" />`}
</div>

View File

@@ -1,696 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工单执行进度与异常节点</title>
<script src="/LzwcaiEmbedFrameFile/LzwcaiEmbedFrameV5.js"></script>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
@keyframes slideIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.animate-slide-in { animation: slideIn 0.5s ease-out forwards; }
.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
.animate-scale-in { animation: scaleIn 0.4s ease-out forwards; }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-blink { animation: blink 1.5s ease-in-out infinite; }
.glass { backdrop-filter: blur(16px); background: rgba(255,255,255,0.85); }
.gradient-text { background: linear-gradient(135deg, #3b82f6 0%, #6366f1 50%, #8b5cf6 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.page-bg { background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 30%, #f1f5f9 70%, #f8fafc 100%); min-height: 100vh; }
.scrollbar-thin::-webkit-scrollbar { width: 6px; height: 6px; }
.scrollbar-thin::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 3px; }
.scrollbar-thin::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
.scrollbar-thin::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
<script>
window.addEventListener('DOMContentLoaded', async function() {
try {
const loader = new LibraryLoader({
enableLog: true, async: false,
libraries: [
{ name: 'react', src: '/LzwcaiEmbedFrameFile/lib/react.production.min.js', check: () => typeof window.React !== 'undefined' },
{ name: 'reactDOM', src: '/LzwcaiEmbedFrameFile/lib/react-dom.production.min.js', check: () => typeof window.ReactDOM !== 'undefined' },
{ name: 'htm', src: '/LzwcaiEmbedFrameFile/lib/htm.js', check: () => typeof window.htm !== 'undefined' },
{ name: 'tailwindcss', src: '/LzwcaiEmbedFrameFile/lib/tailwindcss-3.4.17.js', check: () => typeof window.tailwind !== 'undefined' },
{ name: 'g2', src: '/LzwcaiEmbedFrameFile/lib/g2@5.2.4.min.js', check: () => typeof window.G2 !== 'undefined' }
]
});
await loader.loadAll();
initReactApp();
} catch (error) { console.error('[WorkOrderProgress] 库加载失败:', error); }
});
function initReactApp() {
const { useState, useEffect, useMemo, useRef } = React;
const html = htm.bind(React.createElement);
const config = { enableLog: true, title: '工单执行进度与异常节点' };
const lzwcaiComInitDate = '{{lzwcaiComInitDate}}';
const defaultData = { success: false, data: [[], [], [], [], [], []] };
// 状态映射配置 OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED
const statusMap = {
'OPEN': 'PENDING', 'STARTED': 'IN_PROGRESS', 'CLOSED': 'COMPLETED',
'PENDING': 'PENDING', 'IN_PROGRESS': 'IN_PROGRESS', 'COMPLETED': 'COMPLETED'
};
const statusConfig = {
'PENDING': { label: '待处理', bg: 'from-amber-400 to-orange-500', light: 'bg-amber-50', text: 'text-amber-700', border: 'border-amber-200', icon: '⏳', dot: 'bg-amber-400' },
'IN_PROGRESS': { label: '进行中', bg: 'from-blue-400 to-indigo-500', light: 'bg-blue-50', text: 'text-blue-700', border: 'border-blue-200', icon: '🔄', dot: 'bg-blue-500' },
'COMPLETED': { label: '已完成', bg: 'from-emerald-400 to-teal-500', light: 'bg-emerald-50', text: 'text-emerald-700', border: 'border-emerald-200', icon: '✅', dot: 'bg-emerald-500' }
};
const riskConfig = {
'HIGH': { label: '高风险', bg: 'from-red-500 to-rose-600', light: 'bg-red-50', text: 'text-red-700', border: 'border-red-200', icon: '🔴' },
'MEDIUM': { label: '中风险', bg: 'from-amber-400 to-orange-500', light: 'bg-amber-50', text: 'text-amber-700', border: 'border-amber-200', icon: '🟡' },
'LOW': { label: '低风险', bg: 'from-blue-400 to-cyan-500', light: 'bg-blue-50', text: 'text-blue-600', border: 'border-blue-200', icon: '🔵' },
'NONE': { label: '无风险', bg: 'from-slate-300 to-slate-400', light: 'bg-slate-50', text: 'text-slate-600', border: 'border-slate-200', icon: '⚪' }
};
const anomalyConfig = {
'SCHEDULE_DELAY': { label: '进度延迟', icon: '⏰', action: '调整排期', color: 'text-red-600' },
'MATERIAL_SHORTAGE': { label: '物料短缺', icon: '📦', action: '补充物料', color: 'text-orange-600' },
'LABOR_STALLED': { label: '人力停滞', icon: '👷', action: '检查人力', color: 'text-amber-600' },
'EQUIPMENT_FAULT': { label: '设备故障', icon: '🔧', action: '维修设备', color: 'text-purple-600' },
'QUALITY_ISSUE': { label: '质量问题', icon: '🔍', action: '质量检查', color: 'text-pink-600' }
};
const actionConfig = {
'ADJUST_SCHEDULE': '调整排期', 'REPLENISH_MATERIAL': '补充物料', 'CHECK_LABOR': '检查人力',
'REPAIR_EQUIPMENT': '维修设备', 'QUALITY_CHECK': '质量检查'
};
// 空状态组件
const EmptyState = ({ icon, title, desc }) => html`
<div class="flex flex-col items-center justify-center py-10 text-center">
<div class="w-14 h-14 bg-gradient-to-br from-slate-100 to-slate-200 rounded-2xl flex items-center justify-center text-2xl mb-3">${icon}</div>
<p class="text-sm font-medium text-slate-500">${title}</p>
<p class="text-xs text-slate-400 mt-1">${desc}</p>
</div>
`;
function ChartApp() {
const [rawData, setRawData] = useState(defaultData);
const [pageHelper, setPageHelper] = useState(null);
const [error, setError] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [filterStatus, setFilterStatus] = useState('all');
const [filterRisk, setFilterRisk] = useState('all');
const pageSize = 10;
const chartRef1 = useRef(null);
const chartRef2 = useRef(null);
const chartRef3 = useRef(null);
const chartInstance1 = useRef(null);
const chartInstance2 = useRef(null);
const chartInstance3 = useRef(null);
const validateData = (data) => {
if (!data || typeof data !== 'object') return { valid: false, error: '数据格式错误' };
if (!Array.isArray(data.data)) return { valid: false, error: 'data 字段应为数组' };
return { valid: true };
};
// 状态映射函数
const mapStatus = (status) => statusMap[status] || status;
const processedData = useMemo(() => {
if (!rawData.data || rawData.data.length < 6) return null;
const [workOrders = [], statusSummary = [], anomalyNodes = [], categorySummary = [], timeline = [], metrics = []] = rawData.data;
// 映射工单状态
const mappedWorkOrders = workOrders.map(wo => ({
...wo,
status_name: mapStatus(wo.status_name)
}));
// 核心指标处理
const metricsMap = {};
metrics.forEach(m => { metricsMap[m.metric] = m; });
// 状态汇总处理
const statusStats = {};
statusSummary.forEach(s => {
const mapped = mapStatus(s.status_name);
if (!statusStats[mapped]) statusStats[mapped] = { count: 0, planned: 0, completed: 0, pct: 0 };
statusStats[mapped].count += parseInt(s.order_count || 0);
statusStats[mapped].planned += s.total_planned || 0;
statusStats[mapped].completed += s.total_completed || 0;
statusStats[mapped].pct += s.pct || 0;
});
// 异常节点处理
const anomalyList = anomalyNodes.map(a => ({
...a,
anomalyInfo: anomalyConfig[a.anomaly_type] || { label: a.anomaly_type, icon: '⚠️', action: '检查', color: 'text-slate-600' }
}));
// 按类别汇总
const categoryStats = categorySummary.map(c => ({
...c,
pending: parseInt(c.pending || 0),
in_progress: parseInt(c.in_progress || 0),
completed: parseInt(c.completed || 0)
}));
// 时间线处理
const timelineData = timeline.map(t => ({
...t,
status_name: mapStatus(t.status_name)
}));
// 风险分布统计
const riskStats = { HIGH: 0, MEDIUM: 0, LOW: 0, NONE: 0 };
mappedWorkOrders.forEach(wo => {
const risk = wo.risk_level || 'NONE';
if (riskStats[risk] !== undefined) riskStats[risk]++;
});
return {
workOrders: mappedWorkOrders,
statusSummary: statusStats,
anomalyNodes: anomalyList,
categorySummary: categoryStats,
timeline: timelineData,
metrics: metricsMap,
riskStats
};
}, [rawData]);
// 过滤后的工单列表
const filteredWorkOrders = useMemo(() => {
if (!processedData) return [];
let list = processedData.workOrders;
if (filterStatus !== 'all') list = list.filter(wo => wo.status_name === filterStatus);
if (filterRisk !== 'all') list = list.filter(wo => wo.risk_level === filterRisk);
return list;
}, [processedData, filterStatus, filterRisk]);
// 分页数据
const paginatedWorkOrders = useMemo(() => {
const start = (currentPage - 1) * pageSize;
return filteredWorkOrders.slice(start, start + pageSize);
}, [filteredWorkOrders, currentPage]);
const totalPages = Math.ceil(filteredWorkOrders.length / pageSize);
// 渲染图表
useEffect(() => {
if (!processedData || !window.G2) return;
// 清理旧图表
[chartInstance1, chartInstance2, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
// 状态分布环形图
if (chartRef1.current && Object.keys(processedData.statusSummary).length > 0) {
const statusData = Object.entries(processedData.statusSummary).map(([status, v]) => ({
name: statusConfig[status]?.label || status,
value: v.count
}));
chartInstance1.current = new G2.Chart({ container: chartRef1.current, autoFit: true, height: 200 });
chartInstance1.current.coordinate({ type: 'theta', innerRadius: 0.6 });
chartInstance1.current.interval()
.data(statusData)
.transform({ type: 'stackY' })
.encode('y', 'value').encode('color', 'name')
.scale('color', { range: ['#f59e0b', '#3b82f6', '#10b981'] })
.style('stroke', '#fff').style('lineWidth', 2)
.label({ text: d => d.value > 0 ? d.name : '', position: 'outside', fontSize: 10 })
.legend('color', { position: 'bottom', layout: { justifyContent: 'center' } })
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v + ' 单' }] });
chartInstance1.current.render();
}
// 类别完成率柱状图
if (chartRef2.current && processedData.categorySummary.length > 0) {
const categoryData = processedData.categorySummary.map(c => ({
name: c.product_category.length > 5 ? c.product_category.slice(0, 5) + '..' : c.product_category,
完成率: c.completion_rate
}));
chartInstance2.current = new G2.Chart({ container: chartRef2.current, autoFit: true, height: 200 });
chartInstance2.current.interval()
.data(categoryData)
.encode('x', 'name').encode('y', '完成率').encode('color', 'name')
.style('radius', 6)
.axis('x', { labelAutoRotate: true, labelFontSize: 10 })
.axis('y', { title: false, labelFormatter: v => v + '%' })
.legend(false)
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v.toFixed(1) + '%' }] });
chartInstance2.current.render();
}
// 风险分布饼图
if (chartRef3.current && Object.values(processedData.riskStats).some(v => v > 0)) {
const riskData = Object.entries(processedData.riskStats)
.filter(([_, v]) => v > 0)
.map(([risk, count]) => ({
name: riskConfig[risk]?.label || risk,
value: count
}));
chartInstance3.current = new G2.Chart({ container: chartRef3.current, autoFit: true, height: 200 });
chartInstance3.current.coordinate({ type: 'theta', innerRadius: 0.5 });
chartInstance3.current.interval()
.data(riskData)
.transform({ type: 'stackY' })
.encode('y', 'value').encode('color', 'name')
.scale('color', { range: ['#ef4444', '#f59e0b', '#3b82f6', '#94a3b8'] })
.style('stroke', '#fff').style('lineWidth', 2)
.label({ text: 'name', position: 'outside', fontSize: 10 })
.legend('color', { position: 'bottom', layout: { justifyContent: 'center' } })
.tooltip({ items: [{ channel: 'y', valueFormatter: v => v + ' 单' }] });
chartInstance3.current.render();
}
return () => {
[chartInstance1, chartInstance2, chartInstance3].forEach(ref => {
if (ref.current) { ref.current.destroy(); ref.current = null; }
});
};
}, [processedData]);
useEffect(() => {
const helper = new ChildPageHelper({
autoRenderStatus: true, enableLog: config.enableLog,
onReady: async () => {
await helper.autoInitialize(() => {
const finalInitData = helper.parseTemplateData(lzwcaiComInitDate, defaultData);
if (finalInitData) {
const v = validateData(finalInitData);
v.valid ? (setRawData(finalInitData), setError(null)) : setError(v.error);
}
});
}
});
setPageHelper(helper);
helper.expose({
setDataLzwcaiEmbedFrameFn: (data) => {
const v = validateData(data);
if (v.valid) { setRawData(data); setError(null); return { status: 'success' }; }
setError(v.error); return { status: 'error', message: v.error };
},
getDataLzwcaiEmbedFrameFn: () => ({ status: 'success', data: rawData }),
captureScreenshotLzwcaiEmbedFrameFn: helper.createScreenshotMethod(),
getRenderStatusLzwcaiEmbedFrameFn: () => helper.getRenderStatus()
});
}, []);
const formatPercent = v => (v || 0).toFixed(1) + '%';
const getStatus = status => statusConfig[status] || statusConfig['PENDING'];
const getRisk = risk => riskConfig[risk] || riskConfig['NONE'];
if (error) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-2xl p-8 max-w-sm text-center border border-red-100">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-red-400 to-rose-500 rounded-2xl flex items-center justify-center text-3xl shadow-lg">⚠️</div>
<h3 class="text-lg font-bold text-red-700 mb-2">数据异常</h3>
<p class="text-sm text-red-500">${error}</p>
</div>
</div>`;
if (!processedData) return html`
<div class="page-bg flex items-center justify-center p-6">
<div class="glass rounded-3xl shadow-xl p-10 max-w-sm text-center border border-blue-100">
<div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-blue-400 to-indigo-500 rounded-2xl flex items-center justify-center text-3xl animate-pulse">📋</div>
<h3 class="text-lg font-bold text-blue-600 mb-2">加载中</h3>
<p class="text-sm text-slate-400">等待工单数据...</p>
</div>
</div>`;
const { statusSummary, anomalyNodes, categorySummary, timeline, metrics, riskStats } = processedData;
return html`
<div class="page-bg p-4 sm:p-5">
<div class="max-w-7xl mx-auto space-y-4">
<!-- 头部 -->
<div class="glass rounded-2xl p-5 shadow-lg border border-white/60">
<div class="flex items-center justify-between flex-wrap gap-4">
<div class="flex items-center gap-4">
<div class="w-14 h-14 bg-gradient-to-br from-blue-500 via-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center shadow-lg shadow-indigo-500/30">
<span class="text-white text-2xl">📋</span>
</div>
<div>
<h1 class="text-2xl font-bold gradient-text">${config.title}</h1>
<p class="text-xs text-slate-500 mt-0.5">实时监控 · 状态追踪 · 异常预警</p>
</div>
</div>
<div class="flex items-center gap-3 flex-wrap">
${(() => {
const anomalyCount = parseInt(metrics.anomaly_count?.value || 0);
const pendingCount = parseInt(metrics.pending?.value || 0);
const inProgressCount = parseInt(metrics.in_progress?.value || 0);
return html`
${anomalyCount > 0 && html`
<span class="px-4 py-2 bg-red-50 border border-red-200 rounded-xl text-sm font-bold text-red-600 flex items-center gap-2 animate-pulse">
<span class="w-2.5 h-2.5 bg-red-500 rounded-full animate-blink"></span>
${anomalyCount} 个异常
</span>
`}
${pendingCount > 0 && html`
<span class="px-4 py-2 bg-amber-50 border border-amber-200 rounded-xl text-sm font-bold text-amber-600 flex items-center gap-2">
<span class="w-2.5 h-2.5 bg-amber-500 rounded-full"></span>
${pendingCount} 待处理
</span>
`}
${inProgressCount > 0 && html`
<span class="px-4 py-2 bg-blue-50 border border-blue-200 rounded-xl text-sm font-bold text-blue-600 flex items-center gap-2">
<span class="w-2.5 h-2.5 bg-blue-500 rounded-full"></span>
${inProgressCount} 进行中
</span>
`}
`;
})()}
</div>
</div>
</div>
<!-- 核心指标卡片 -->
<div class="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-7 gap-3">
${[
{ key: 'total_orders', icon: '📦', label: '总工单', color: 'from-slate-500 to-slate-600', shadow: 'shadow-slate-200/50' },
{ key: 'pending', icon: '⏳', label: '待处理', color: 'from-amber-400 to-orange-500', shadow: 'shadow-amber-200/50' },
{ key: 'in_progress', icon: '🔄', label: '进行中', color: 'from-blue-400 to-indigo-500', shadow: 'shadow-blue-200/50' },
{ key: 'completed', icon: '✅', label: '已完成', color: 'from-emerald-400 to-teal-500', shadow: 'shadow-emerald-200/50' },
{ key: 'completion_rate', icon: '📊', label: '完成率', color: 'from-purple-400 to-pink-500', shadow: 'shadow-purple-200/50' },
{ key: 'anomaly_count', icon: '⚠️', label: '异常数', color: 'from-red-400 to-rose-500', shadow: 'shadow-red-200/50', alert: true },
{ key: 'today_completed', icon: '🎯', label: '今日完成', color: 'from-cyan-400 to-blue-500', shadow: 'shadow-cyan-200/50' }
].map((item, i) => {
const m = metrics[item.key] || {};
const isAlert = item.alert && parseInt(m.value || 0) > 0;
return html`
<div key=${i} class="bg-white rounded-xl p-4 shadow-md border border-slate-100/80 hover:shadow-lg transition-all animate-slide-in ${isAlert ? 'ring-2 ring-red-200 bg-red-50/30' : ''}" style=${{ animationDelay: i * 50 + 'ms' }}>
<div class="flex items-center justify-between mb-2">
<span class="w-10 h-10 bg-gradient-to-br ${item.color} rounded-xl flex items-center justify-center text-base shadow-md ${item.shadow} ${isAlert ? 'animate-pulse' : ''}">${item.icon}</span>
</div>
<p class="text-2xl font-black ${isAlert ? 'text-red-600' : 'text-slate-800'}">${m.value || '0'}</p>
<p class="text-xs text-slate-500 mt-0.5">${item.label}</p>
</div>
`;
})}
</div>
<!-- 主体内容区 -->
<div class="grid lg:grid-cols-3 gap-4">
<!-- 左侧:状态分布 + 风险分布 -->
<div class="space-y-4">
<!-- 状态分布 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-blue-400 to-indigo-500 rounded-lg flex items-center justify-center text-white text-xs">📊</span>
状态分布
</h3>
${Object.keys(statusSummary).length > 0
? html`<div ref=${chartRef1} class="w-full"></div>`
: html`<${EmptyState} icon="📊" title="暂无状态数据" desc="等待数据加载" />`
}
</div>
<!-- 风险分布 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-red-400 to-rose-500 rounded-lg flex items-center justify-center text-white text-xs">⚡</span>
风险分布
</h3>
${Object.values(riskStats).some(v => v > 0)
? html`<div ref=${chartRef3} class="w-full"></div>`
: html`<${EmptyState} icon="⚡" title="暂无风险数据" desc="等待数据加载" />`
}
</div>
</div>
<!-- 中间:异常节点预警 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-red-400 to-rose-500 rounded-lg flex items-center justify-center text-white text-xs animate-blink">🚨</span>
异常节点预警
</h3>
<span class="px-2 py-1 bg-red-50 text-red-600 rounded-full text-xs font-medium">${anomalyNodes.length} 项</span>
</div>
${anomalyNodes.length > 0 ? html`
<div class="space-y-2 max-h-[420px] overflow-y-auto scrollbar-thin pr-1">
${anomalyNodes.map((a, i) => html`
<div key=${i} class="bg-gradient-to-r from-red-50 to-orange-50 rounded-xl p-3 border border-red-100 hover:shadow-md transition-all animate-slide-in" style=${{ animationDelay: i * 60 + 'ms' }}>
<div class="flex items-start justify-between gap-2">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="text-lg">${a.anomalyInfo.icon}</span>
<span class="font-bold text-slate-800 text-sm truncate">${a.work_order_number}</span>
</div>
<p class="text-xs text-slate-600 truncate mb-2">${a.product_name}</p>
<div class="flex flex-wrap gap-1">
<span class="px-2 py-0.5 bg-white/80 rounded text-xs ${a.anomalyInfo.color} font-medium">${a.anomalyInfo.label}</span>
<span class="px-2 py-0.5 bg-white/80 rounded text-xs text-slate-500">${formatPercent(a.completion_rate)} 完成</span>
</div>
</div>
<div class="text-right shrink-0">
<p class="text-xs text-slate-400 mb-1">${a.elapsed_hours?.toFixed(1) || 0}h</p>
<span class="inline-flex items-center px-2 py-1 bg-gradient-to-r from-orange-400 to-red-500 text-white rounded-lg text-xs font-medium shadow-sm">
${actionConfig[a.action] || a.action}
</span>
</div>
</div>
</div>
`)}
</div>
` : html`<${EmptyState} icon="✨" title="暂无异常" desc="所有工单运行正常" />`}
</div>
<!-- 右侧:类别完成率 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<h3 class="text-sm font-bold text-slate-700 mb-3 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-lg flex items-center justify-center text-white text-xs">📈</span>
类别完成率
</h3>
${categorySummary.length > 0 ? html`
<div ref=${chartRef2} class="w-full mb-3"></div>
<div class="space-y-2 max-h-[200px] overflow-y-auto scrollbar-thin pr-1">
${categorySummary.map((c, i) => html`
<div key=${i} class="flex items-center justify-between p-2 bg-slate-50 rounded-lg hover:bg-slate-100 transition-colors">
<div class="flex items-center gap-2 min-w-0">
<span class="w-2 h-2 rounded-full bg-gradient-to-r from-indigo-400 to-purple-500"></span>
<span class="text-xs font-medium text-slate-700 truncate">${c.product_category}</span>
</div>
<div class="flex items-center gap-3 shrink-0">
<div class="flex gap-1 text-xs">
<span class="text-amber-600">${c.pending}</span>
<span class="text-slate-300">/</span>
<span class="text-blue-600">${c.in_progress}</span>
<span class="text-slate-300">/</span>
<span class="text-emerald-600">${c.completed}</span>
</div>
<span class="px-2 py-0.5 bg-emerald-50 text-emerald-600 rounded text-xs font-bold">${formatPercent(c.completion_rate)}</span>
</div>
</div>
`)}
</div>
` : html`<${EmptyState} icon="📈" title="暂无类别数据" desc="等待数据加载" />`}
</div>
</div>
<!-- 工单明细表 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 overflow-hidden">
<div class="px-4 py-3 border-b border-slate-100 bg-gradient-to-r from-slate-50 to-white">
<div class="flex flex-wrap items-center justify-between gap-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-indigo-400 to-purple-500 rounded-lg flex items-center justify-center text-white text-xs">📋</span>
工单明细
</h3>
<div class="flex flex-wrap items-center gap-2">
<!-- 状态筛选 -->
<select value=${filterStatus} onChange=${e => { setFilterStatus(e.target.value); setCurrentPage(1); }}
class="px-3 py-1.5 text-xs border border-slate-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-indigo-200">
<option value="all">全部状态</option>
<option value="PENDING">待处理</option>
<option value="IN_PROGRESS">进行中</option>
<option value="COMPLETED">已完成</option>
</select>
<!-- 风险筛选 -->
<select value=${filterRisk} onChange=${e => { setFilterRisk(e.target.value); setCurrentPage(1); }}
class="px-3 py-1.5 text-xs border border-slate-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-indigo-200">
<option value="all">全部风险</option>
<option value="HIGH">高风险</option>
<option value="MEDIUM">中风险</option>
<option value="LOW">低风险</option>
<option value="NONE">无风险</option>
</select>
<span class="px-2 py-1 bg-indigo-50 text-indigo-600 rounded-full text-xs font-medium">${filteredWorkOrders.length} 条</span>
</div>
</div>
</div>
${filteredWorkOrders.length > 0 ? html`
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-slate-50">
<tr>
<th class="px-3 py-2.5 text-left text-xs font-semibold text-slate-500">工单号</th>
<th class="px-3 py-2.5 text-left text-xs font-semibold text-slate-500">产品</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">状态</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">进度</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">计划/完成</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">风险</th>
<th class="px-3 py-2.5 text-center text-xs font-semibold text-slate-500">异常</th>
<th class="px-3 py-2.5 text-right text-xs font-semibold text-slate-500">工时</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
${paginatedWorkOrders.map((wo, i) => {
const sc = getStatus(wo.status_name);
const rc = getRisk(wo.risk_level);
const hasAnomaly = wo.anomaly_flags && wo.anomaly_flags.length > 0;
const anomalyInfo = hasAnomaly ? (anomalyConfig[wo.anomaly_flags] || { label: wo.anomaly_flags, icon: '⚠️', color: 'text-slate-600' }) : null;
return html`
<tr key=${i} class="hover:bg-slate-50/50 transition-colors ${hasAnomaly ? 'bg-red-50/30' : ''}">
<td class="px-3 py-2.5">
<span class="font-semibold text-slate-800 text-sm">${wo.work_order_number}</span>
</td>
<td class="px-3 py-2.5">
<p class="text-slate-700 text-sm truncate max-w-[160px]">${wo.product_name}</p>
<p class="text-xs text-slate-400">${wo.product_category}</p>
</td>
<td class="px-3 py-2.5 text-center">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${sc.light} ${sc.text}">
${sc.icon} ${sc.label}
</span>
</td>
<td class="px-3 py-2.5">
<div class="flex items-center gap-2">
<div class="flex-1 h-2 bg-slate-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-indigo-400 to-purple-500 rounded-full transition-all" style=${{ width: Math.min(wo.completion_rate || 0, 100) + '%' }}></div>
</div>
<span class="text-xs font-bold text-slate-600 w-12 text-right">${formatPercent(wo.completion_rate)}</span>
</div>
</td>
<td class="px-3 py-2.5 text-center">
<span class="text-sm text-slate-700">${wo.completed_qty || 0}<span class="text-slate-400">/</span>${wo.planned_qty || 0}</span>
</td>
<td class="px-3 py-2.5 text-center">
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${rc.light} ${rc.text}">
${rc.icon} ${rc.label}
</span>
</td>
<td class="px-3 py-2.5 text-center">
${hasAnomaly ? html`
<span class="inline-flex items-center gap-1 px-2 py-1 bg-red-50 rounded-lg text-xs font-medium ${anomalyInfo.color}">
${anomalyInfo.icon} ${anomalyInfo.label}
</span>
` : html`<span class="text-slate-400 text-xs">-</span>`}
</td>
<td class="px-3 py-2.5 text-right">
<span class="text-sm font-medium text-slate-600">${(wo.total_work_hours || 0).toFixed(1)}h</span>
</td>
</tr>
`;
})}
</tbody>
</table>
</div>
<!-- 分页 -->
${totalPages > 1 && html`
<div class="px-4 py-3 border-t border-slate-100 flex items-center justify-between">
<p class="text-xs text-slate-500">共 ${filteredWorkOrders.length} 条,第 ${currentPage}/${totalPages} 页</p>
<div class="flex items-center gap-1">
<button onClick=${() => setCurrentPage(p => Math.max(1, p - 1))} disabled=${currentPage === 1}
class="px-3 py-1.5 text-xs rounded-lg border border-slate-200 hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">上一页</button>
${Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let page = i + 1;
if (totalPages > 5) {
if (currentPage <= 3) page = i + 1;
else if (currentPage >= totalPages - 2) page = totalPages - 4 + i;
else page = currentPage - 2 + i;
}
return html`
<button key=${page} onClick=${() => setCurrentPage(page)}
class="w-8 h-8 text-xs rounded-lg border transition-colors ${currentPage === page
? 'bg-gradient-to-r from-indigo-500 to-purple-500 text-white border-transparent shadow-md'
: 'border-slate-200 hover:bg-slate-50'}">${page}</button>
`;
})}
<button onClick=${() => setCurrentPage(p => Math.min(totalPages, p + 1))} disabled=${currentPage === totalPages}
class="px-3 py-1.5 text-xs rounded-lg border border-slate-200 hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">下一页</button>
</div>
</div>
`}
` : html`<${EmptyState} icon="📋" title="暂无工单数据" desc="调整筛选条件或等待数据加载" />`}
</div>
<!-- 执行时间线 -->
<div class="bg-white rounded-2xl shadow-md border border-slate-100/80 p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-bold text-slate-700 flex items-center gap-2">
<span class="w-6 h-6 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-lg flex items-center justify-center text-white text-xs">⏱️</span>
执行时间线
</h3>
<span class="px-2 py-1 bg-cyan-50 text-cyan-600 rounded-full text-xs font-medium">${timeline.length} 条记录</span>
</div>
${timeline.length > 0 ? html`
<div class="relative">
<div class="absolute left-4 top-0 bottom-0 w-0.5 bg-gradient-to-b from-indigo-200 via-purple-200 to-pink-200"></div>
<div class="space-y-3 max-h-[300px] overflow-y-auto scrollbar-thin pr-2">
${timeline.slice(0, 15).map((t, i) => {
const sc = getStatus(t.status_name);
return html`
<div key=${i} class="relative pl-10 animate-slide-in" style=${{ animationDelay: i * 40 + 'ms' }}>
<div class="absolute left-2.5 w-3 h-3 rounded-full ${sc.dot} ring-4 ring-white shadow-sm"></div>
<div class="bg-gradient-to-r from-slate-50 to-white rounded-xl p-3 border border-slate-100 hover:shadow-md transition-all">
<div class="flex items-start justify-between gap-2">
<div class="min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="font-bold text-slate-800 text-sm">${t.work_order_number}</span>
<span class="px-1.5 py-0.5 rounded text-xs font-medium ${sc.light} ${sc.text}">${sc.label}</span>
</div>
<p class="text-xs text-slate-600 truncate">${t.product_name}</p>
</div>
<div class="text-right shrink-0">
<p class="text-xs text-slate-400">${t.start_time}</p>
<p class="text-xs font-medium text-indigo-600">${formatPercent(t.completion_rate)}</p>
</div>
</div>
<div class="mt-2 flex items-center gap-3 text-xs text-slate-500">
<span>计划: ${t.planned_qty}</span>
<span>完成: ${t.completed_qty}</span>
<span>耗时: ${(t.duration_hours || 0).toFixed(1)}h</span>
</div>
</div>
</div>
`;
})}
</div>
</div>
` : html`<${EmptyState} icon="⏱️" title="暂无时间线数据" desc="等待数据加载" />`}
</div>
<!-- 底部 -->
<div class="text-center pt-2 pb-3">
<p class="text-xs text-slate-400">
<span class="inline-flex items-center gap-1.5 px-4 py-2 glass rounded-full border border-white/50 shadow-sm">
📋 工单执行进度与异常节点 · 实时监控
</span>
</p>
</div>
</div>
</div>
`;
}
ReactDOM.render(React.createElement(ChartApp), document.getElementById('root'));
}
</script>
</head>
<body class="m-0 p-0">
<div id="root"></div>
</body>
</html>

View File

@@ -1,10 +0,0 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -1,154 +0,0 @@
# lzwcai-mcpskills-analyzeWorkOrder
一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志详细的操作日志记录仅输出到文件不干扰MCP通信
- 🌐 中文支持:工具名称自动转换为拼音
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcpskills-analyzeWorkOrder
```
### 从源码安装
```bash
git clone <repository-url>
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 <repository-url>
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

View File

@@ -1,9 +0,0 @@
"""
lzwcai-mcpskills-analyzeOrder - MCP server for executing business SQL queries
"""
__version__ = "0.1.2"
__author__ = "lzwcai"
__all__ = []

File diff suppressed because one or more lines are too long

View File

@@ -1,295 +0,0 @@
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-08 00:15:16 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-08 00:15:17 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-08 00:15:17 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-08 00:15:17 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-08 00:15:17 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-08 00:15:17 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-08 00:15:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 00:15:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:15:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:15:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 00:15:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:15:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:15:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 00:15:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:15:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:15:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-08 00:15:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:15:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:16:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-08 00:16:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:16:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:16:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 00:16:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:16:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-08 00:30:22 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-08 00:30:26 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-08 00:30:26 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-08 00:30:26 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-08 00:30:26 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-08 00:30:26 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-08 00:30:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 00:30:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:27 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:32 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 00:30:32 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:32 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-08 00:30:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-08 00:30:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 00:30:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:39 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-08 00:30:39 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:30:44 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-08 00:30:44 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:30:44 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-08 00:38:55 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-08 00:38:57 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-08 00:38:57 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-08 00:38:57 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-08 00:38:57 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-08 00:38:57 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-08 00:38:59 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 00:38:59 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:38:59 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-08 00:52:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-08 00:52:36 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-08 00:52:36 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-08 00:52:36 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-08 00:52:36 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-08 00:52:36 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-08 00:52:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 00:52:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:52:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:53:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 00:53:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:53:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-09 22:50:49 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-09 22:50:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-09 22:50:56 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-09 22:50:56 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-09 22:50:56 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-09 22:50:56 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-09 22:50:57 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-09 22:50:57 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 22:50:57 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 22:51:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-09 22:51:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 22:51:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 22:51:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-09 22:51:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 22:51:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 22:51:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-09 22:51:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 22:51:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 22:51:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-09 22:51:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 22:51:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 22:52:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-09 22:52:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 22:52:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 23:05:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-09 23:05:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 23:05:39 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 23:48:54 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-09 23:48:54 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 23:48:54 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-10 00:10:04 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-10 00:10:04 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-10 00:10:04 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-10 00:26:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-10 00:26:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-10 00:26:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-10 00:47:45 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-10 00:47:45 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-10 00:47:46 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-16 12:39:13 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-16 12:39:14 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-16 12:39:14 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-16 12:39:14 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-16 12:39:14 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-16 12:39:14 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-16 12:39:17 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-16 12:39:17 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:39:17 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:39:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-16 12:39:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:39:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:39:26 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-16 12:39:26 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:39:26 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:39:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief
2026-01-16 12:39:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:39:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:39:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-16 12:39:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:39:33 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:39:34 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-16 12:39:34 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:39:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-29 13:30:19 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-29 13:30:21 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-29 13:30:21 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-29 13:30:21 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-29 13:30:21 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-29 13:30:21 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-29 13:30:22 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-29 13:30:22 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:30:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:30:25 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-29 13:30:25 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:30:25 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 57
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 57
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.167.30.2:8088
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-29 13:36:26 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-29 13:36:27 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-29 13:36:27 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-29 13:36:27 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-29 13:36:27 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-29 13:36:27 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-29 13:36:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis
2026-01-29 13:36:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:36:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:36:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes
2026-01-29 13:36:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:36:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:36:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning
2026-01-29 13:36:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:36:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:36:38 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard
2026-01-29 13:36:38 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:36:38 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-29 13:36:41 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning
2026-01-29 13:36:41 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-29 13:36:41 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功

View File

@@ -1,373 +0,0 @@
from pathlib import Path
from typing import Any
import asyncio
import logging
# 支持直接运行和模块导入两种方式
try:
from .utils import load_json, generate_tool_name, generate_input_schema
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
from .utils.logger_config import logger_config
except ImportError:
from utils import load_json, generate_tool_name, generate_input_schema
from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
from utils.logger_config import logger_config
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
import mcp.types as types
# 初始化 MCP 专用日志器
mcp_logger = logger_config.setup_mcp_logging()
# ========== 数据源配置 ==========
# 数据源类型常量
DATA_SOURCE_API = "api" # 仅使用API数据
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
# 默认数据源(可修改)
DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL
# ================================
def get_queries():
"""
获取业务查询配置
Returns:
list: 包含所有业务查询配置的列表
"""
try:
# 获取当前文件所在目录
current_dir = Path(__file__).parent
# 构建 businessQueries.json 的路径
json_path = current_dir / "businessQueries.json"
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
# 使用 load_json 方法读取 JSON 文件
queries = load_json(json_path)
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
return queries
except Exception as e:
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
raise
def generate_tool_schema_from_query(query: dict) -> types.Tool:
"""
根据查询配置生成 MCP 工具模式
Args:
query: 单个查询配置字典
Returns:
types.Tool: MCP 工具对象
"""
try:
# 获取参数定义并生成 inputSchema
parameters = query.get('parameters', {})
input_schema = generate_input_schema(parameters)
# 生成工具名称(格式: tool_拼音_id
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
# 构建工具描述,包含业务名称和业务描述
description = f"{query['businessName']}: {query['businessDescription']}"
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
return types.Tool(
name=tool_name,
description=description,
inputSchema=input_schema
)
except Exception as e:
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
raise
# 创建 MCP 服务器实例
server = Server("lzwcai-mcpskills-mfg-data-agent")
# 缓存查询配置,避免重复加载
_queries_cache = None
async def get_queries_cache(source: str = None):
"""
获取或初始化查询配置缓存
Args:
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE
- "api": 仅使用API数据
- "local": 仅使用本地JSON数据
- "both": 合并本地和API数据
Returns:
查询配置列表
"""
global _queries_cache
if _queries_cache is None:
source = source or DEFAULT_DATA_SOURCE
mcp_logger.info(f"初始化查询配置(数据源: {source}...")
if source == DATA_SOURCE_LOCAL:
_queries_cache = get_queries()
mcp_logger.info(f"本地配置: {len(_queries_cache)}")
elif source == DATA_SOURCE_API:
try:
_queries_cache = await call_third_party_api()
mcp_logger.info(f"API配置: {len(_queries_cache)}")
mcp_logger.info(f"API配置数组: {_queries_cache}")
except Exception as e:
mcp_logger.warning(f"API获取失败降级使用本地配置: {e}")
_queries_cache = get_queries()
else: # DATA_SOURCE_BOTH
local = get_queries()
try:
api = await call_third_party_api()
except Exception as e:
mcp_logger.warning(f"API获取失败: {e}")
api = []
_queries_cache = local + api
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)}")
return _queries_cache
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
列出所有动态生成的 MCP 工具
Returns:
list[types.Tool]: 所有可用的工具列表
"""
try:
mcp_logger.info("收到列出工具请求")
queries = await get_queries_cache()
tools = []
for query in queries:
tool = generate_tool_schema_from_query(query)
tools.append(tool)
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
return tools
except Exception as e:
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
raise
@server.call_tool()
async def handle_call_tool(
name: str,
arguments: dict[str, Any] | None
) -> list[types.TextContent]:
"""
处理工具调用请求
Args:
name: 工具名称
arguments: 工具参数
Returns:
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
"""
try:
mcp_logger.info(f"收到工具调用请求: {name}")
mcp_logger.debug(f"工具参数: {arguments}")
# 获取查询配置缓存
queries = await get_queries_cache()
# 根据工具名称查找对应的 item接口配置
tool_item = None
for query in queries:
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
if tool_name == name:
tool_item = query
break
# 构建返回结果
import json
if tool_item:
request_data = {
"datasourceId": get_datasource_id(),
"businessName": tool_item.get("businessName"),
"businessDescription": tool_item.get("businessDescription"),
"sqlTemplate": tool_item.get("sqlTemplate"),
"parameters": tool_item.get("parameters"),
"testParams": arguments or {}
}
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
if arguments and arguments.get("targetDatabaseName"):
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
# 调用测试SQL API
try:
mcp_logger.info("正在调用测试SQL API...")
api_response = test_sql_with_schema(request_data)
mcp_logger.info("测试SQL API调用成功")
# 返回包含 data 字段的结果
result = {
"success": True,
"data": api_response
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
except Exception as e:
error_msg = f"调用测试SQL API失败: {str(e)}"
mcp_logger.error(error_msg, exc_info=True)
result = {
"success": False,
"error": error_msg,
"data": None
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
else:
error_msg = f"未找到工具 {name} 对应的配置"
result = {
"success": False,
"error": error_msg,
"data": None
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
mcp_logger.debug(f"工具调用结果: {result_text}")
return [
types.TextContent(
type="text",
text=result_text
)
]
except Exception as e:
error_msg = f"工具调用失败: {name}, 错误: {e}"
mcp_logger.error(error_msg, exc_info=True)
return [
types.TextContent(
type="text",
text=f"错误: {error_msg}"
)
]
async def call_third_party_api(skill_id: str = None) -> list:
"""
调用第三方API获取技能信息并返回处理后的数据
Args:
skill_id: 技能ID默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178
Returns:
处理后的查询配置列表businessQueries格式
Example:
queries = await call_third_party_api()
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
"""
try:
# 如果没有传入 skill_id则从环境变量读取
if skill_id is None:
skill_id = get_skill_id()
mcp_logger.info(f"调用第三方APIskill_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()

View File

@@ -1,35 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcpskills-mfg-data-agent"
version = "0.1.1"
description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.13"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcpskills-mfg-data-agent = "lzwcai_mcpskills_mfg_data_agent.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcpskills_mfg_data_agent"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcpskills_mfg_data_agent/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agent/businessQueries.json"

View File

@@ -1,169 +0,0 @@
-- =====================================================
-- 交付风险预测:延迟概率与红/黄/绿预警等级
-- 基于历史订单的生产周期、物流延误、设备故障等特征
-- =====================================================
WITH
-- 1. 全局生产特征(汇总所有工单)
global_production AS (
SELECT
COUNT(*) AS total_wo_count,
AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate,
SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count,
SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count
FROM fact_work_order
),
-- 2. 全局质检特征
global_quality AS (
SELECT
COUNT(*) AS total_inspection_count,
SUM(COALESCE(pass_qty, 0)) AS total_pass_qty,
SUM(COALESCE(fail_qty, 0)) AS total_fail_qty,
CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0
THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0))
ELSE 1 END AS qc_pass_rate
FROM fact_quality_inspection
),
-- 3. 全局工序不良特征
global_operation AS (
SELECT
COUNT(*) AS total_task_count,
SUM(COALESCE(good_qty, 0)) AS total_good_qty,
SUM(COALESCE(bad_qty, 0)) AS total_bad_qty,
CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0
THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0))
ELSE 0 END AS operation_defect_rate
FROM fact_operation_task
),
-- 4. 客户级别发货统计
customer_shipment AS (
SELECT
customer_id,
COUNT(*) AS shipment_count,
SUM(COALESCE(amount, 0)) AS total_shipment_amount
FROM fact_sales_shipment
GROUP BY customer_id
),
-- 5. 客户级别退货统计
customer_return AS (
SELECT
customer_id,
COUNT(*) AS return_count,
SUM(COALESCE(amount, 0)) AS total_return_amount
FROM fact_sales_return
GROUP BY customer_id
),
-- 6. 订单风险评估
order_risk AS (
SELECT
so.sales_order_id,
so.sales_order_number,
c.customer_name,
so.order_date_utc,
so.deal_amount,
so.payment_status,
-- 全局生产指标
gp.avg_completion_rate AS production_completion_rate,
gp.pending_wo_count,
gp.total_wo_count AS work_order_count,
-- 全局质检指标
gq.qc_pass_rate,
gq.total_fail_qty,
-- 全局工序指标
go.operation_defect_rate,
-- 客户级别指标
COALESCE(cs.shipment_count, 0) AS shipment_count,
COALESCE(cr.return_count, 0) AS return_count,
CASE WHEN COALESCE(cs.shipment_count, 0) > 0
THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count
ELSE 0 END AS return_rate
FROM fact_sales_order so
LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true
CROSS JOIN global_production gp
CROSS JOIN global_quality gq
CROSS JOIN global_operation go
LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id
LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id
)
-- 7. 最终输出
SELECT
sales_order_id,
sales_order_number,
customer_name,
order_date_utc,
deal_amount,
payment_status,
-- 风险特征
work_order_count,
ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate,
pending_wo_count,
ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate,
ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate,
return_count,
ROUND(return_rate::NUMERIC, 4) AS return_rate,
-- 延迟概率
ROUND((
CASE WHEN production_completion_rate < 0.3 THEN 0.30
WHEN production_completion_rate < 0.5 THEN 0.20
WHEN production_completion_rate < 0.8 THEN 0.10
ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25
WHEN qc_pass_rate < 0.9 THEN 0.15
WHEN qc_pass_rate < 0.95 THEN 0.08
ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20
WHEN operation_defect_rate > 0.05 THEN 0.12
WHEN operation_defect_rate > 0.02 THEN 0.05
ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15
WHEN return_rate > 0.05 THEN 0.08
WHEN return_rate > 0.02 THEN 0.03
ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10
WHEN payment_status = 'PARTIAL' THEN 0.05
ELSE 0 END
)::NUMERIC, 2) AS delay_probability,
-- 红/黄/绿预警
CASE
WHEN (
CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END
) >= 0.50 THEN 'RED'
WHEN (
CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END
) >= 0.25 THEN 'YELLOW'
ELSE 'GREEN'
END AS risk_level,
-- 风险原因
CONCAT_WS(' | ',
CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END,
CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END,
CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END,
CASE WHEN return_rate > 0.05 THEN '历史退货率高' END,
CASE WHEN payment_status = 'UNPAID' THEN '未付款' END
) AS risk_reasons
FROM order_risk
ORDER BY delay_probability DESC, deal_amount DESC;

View File

@@ -1,25 +0,0 @@
"""Utils package for lzwcai_mcp_sqlexecutor"""
from .json_helper import load_json
from .name_helper import generate_tool_name
from .schema_helper import generate_input_schema, validate_input_schema
from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema
from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable
__all__ = [
'load_json',
'generate_tool_name',
'generate_input_schema',
'validate_input_schema',
'DataSourceAPIClient',
'get_skill_by_id',
'process_skill_response',
'test_sql_with_schema',
'get_database_id',
'get_datasource_id',
'get_skill_id',
'get_backend_base_url',
'get_env_config',
'set_env_variable'
]

View File

@@ -1,332 +0,0 @@
"""
第三方API调用客户端
用于调用外部数据源接口
"""
import httpx
import logging
import json
from typing import Dict, Any, Optional, List
# 支持直接运行和模块导入两种方式
try:
from .env_config import get_backend_base_url
except ImportError:
from env_config import get_backend_base_url
# 获取日志记录器
logger = logging.getLogger(__name__)
class DataSourceAPIClient:
"""数据源API客户端"""
def __init__(
self,
base_url: Optional[str] = None,
token: Optional[str] = None
):
"""
初始化API客户端
Args:
base_url: API基础URL默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088
token: 认证令牌Bearer Token
"""
# 如果没有传入 base_url则从环境变量读取
if base_url is None:
base_url = get_backend_base_url()
self.base_url = base_url.rstrip('/')
self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw"
self.client = httpx.Client(timeout=30.0)
def _get_headers(self) -> Dict[str, str]:
"""
获取请求头
Returns:
请求头字典
"""
return {
'Authorization': f'Bearer {self.token}',
}
def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]:
"""
根据技能ID获取技能信息
Args:
skill_id: 技能ID
Returns:
API响应数据
Raises:
Exception: 请求失败时抛出
"""
try:
url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}"
logger.info(f"正在调用API: {url}")
logger.info(f"请求参数 - skill_id: {skill_id}")
response = self.client.get(
url,
headers=self._get_headers()
)
# 检查HTTP状态码
response.raise_for_status()
# 解析JSON响应
data = response.json()
logger.info(f"API调用成功: {url}")
logger.debug(f"响应数据: {data}")
return data
except httpx.TimeoutException:
error_msg = f"API请求超时: {url}"
logger.error(error_msg)
raise Exception(error_msg)
except httpx.HTTPStatusError as e:
error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg)
except httpx.RequestError as e:
error_msg = f"API请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg)
raise Exception(error_msg)
except Exception as e:
error_msg = f"处理API响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True)
raise Exception(error_msg)
def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
"""
测试SQL语句并返回执行结果
Args:
request_data: 请求数据,包含以下字段:
- datasourceId: 数据源ID
- businessName: 业务名称
- businessDescription: 业务描述
- sqlTemplate: SQL模板
- parameters: 参数定义
- testParams: 测试参数
Returns:
API响应数据
Raises:
Exception: 请求失败时抛出
"""
try:
url = f"{self.base_url}/datasource/sqlExecutionLog/testBatchSqlWithSchema"
# 构建请求头包含Content-Type
headers = self._get_headers()
headers['Content-Type'] = 'application/json'
headers['Accept'] = '*/*'
logger.info(f"正在调用测试SQL API: {url}")
logger.info(f"请求参数: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
# 发送POST请求
response = self.client.post(
url,
headers=headers,
json=request_data
)
# 检查HTTP状态码
response.raise_for_status()
# 解析JSON响应
result = response.json()
logger.info(f"测试SQL API调用成功")
logger.debug(f"响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}")
# 处理返回数据结构: {code, data: [{errorMessage, data}, ...], msg}
# 检查外层 code
if result.get("code") != 200:
error_msg = result.get("msg", "接口返回错误")
logger.error(f"接口返回错误: code={result.get('code')}, msg={error_msg}")
raise Exception(error_msg)
# data 是一个数组,每个元素包含 errorMessage 和 data
data_list = result.get("data", [])
# 如果不是数组,兼容旧格式
if isinstance(data_list, dict):
if data_list.get("errorMessage"):
error_msg = data_list.get("errorMessage")
logger.error(f"接口业务错误: {error_msg}")
raise Exception(error_msg)
return data_list.get("data")
# 处理数组格式,提取每个 item 的 data 并组合
combined_data = []
for item in data_list:
if item.get("errorMessage"):
error_msg = item.get("errorMessage")
logger.error(f"接口业务错误: {error_msg}")
raise Exception(error_msg)
item_data = item.get("data", [])
combined_data.append(item_data)
# 返回组合后的数据 [data1, data2, ...]
return combined_data
except httpx.TimeoutException:
error_msg = f"测试SQL API请求超时: {url}"
logger.error(error_msg)
raise Exception(error_msg)
except httpx.HTTPStatusError as e:
error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg)
except httpx.RequestError as e:
error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg)
raise Exception(error_msg)
except Exception as e:
error_msg = f"处理测试SQL API响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True)
raise Exception(error_msg)
def close(self):
"""关闭HTTP客户端"""
self.client.close()
# 创建默认客户端实例
default_client = DataSourceAPIClient()
def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
"""
便捷函数根据技能ID获取技能信息
Args:
skill_id: 技能ID
base_url: API基础URL可选默认从环境变量 BACKEND_BASE_URL 读取)
token: 认证令牌(可选,使用默认值)
Returns:
API响应数据
"""
if base_url or token:
client = DataSourceAPIClient(
base_url=base_url,
token=token
)
return client.get_skill_by_id(skill_id)
else:
return default_client.get_skill_by_id(skill_id)
def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
"""
便捷函数测试SQL语句并返回执行结果
Args:
request_data: 请求数据,包含以下字段:
- datasourceId: 数据源ID
- businessName: 业务名称
- businessDescription: 业务描述
- sqlTemplate: SQL模板
- parameters: 参数定义
- testParams: 测试参数
base_url: API基础URL可选默认从环境变量 BACKEND_BASE_URL 读取)
token: 认证令牌(可选,使用默认值)
Returns:
API响应数据
"""
if base_url or token:
client = DataSourceAPIClient(
base_url=base_url,
token=token
)
return client.test_sql_with_schema(request_data)
else:
return default_client.test_sql_with_schema(request_data)
def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
处理API响应数据映射为businessQueries格式
Args:
response: API原始响应数据
Returns:
处理后的查询配置列表
"""
try:
# 提取data数组
data_list = response.get("data", [])
# 默认的员工ID参数schema
default_employee_schema = {
"type": "object",
"required": ["employeeId"],
"properties": {
"employeeId": {
"type": "number",
"description": "员工ID用于标识员工的唯一数字标识符",
"examples": [1001, 2002]
}
}
}
# 映射每个skill为businessQuery格式
queries = []
for skill in data_list:
# 解析sqlParams字符串为JSON对象
sql_params = json.loads(skill.get("sqlParams", "{}"))
# 判断sqlParams是否为空对象
is_empty_params = (
not sql_params.get("properties") or
len(sql_params.get("properties", {})) == 0
) and (
not sql_params.get("required") or
len(sql_params.get("required", [])) == 0
)
# 如果是空对象使用默认的员工ID参数
if is_empty_params:
logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空使用默认员工ID参数")
sql_params = default_employee_schema
# 映射字段
query = {
"id": skill.get("id"),
"businessName": skill.get("name"),
"businessDescription": skill.get("description"),
"sqlTemplate": skill.get("sqlTemplate"),
"parameters": sql_params,
"datasourceId": skill.get("datasourceId")
}
queries.append(query)
logger.info(f"成功处理 {len(queries)} 条技能数据")
return queries
except Exception as e:
logger.error(f"处理API响应数据失败: {e}", exc_info=True)
raise

View File

@@ -1,106 +0,0 @@
"""环境变量配置模块"""
import os
from typing import Optional
def get_database_id(default: str = "29") -> str:
"""
获取数据库ID环境变量
Args:
default: 默认值(默认为 "29"
Returns:
str: 数据库ID
Environment Variables:
databaseId: 数据库ID
"""
return os.environ.get("databaseId", default)
def get_datasource_id(default: str = "") -> str:
"""
获取数据源ID环境变量
Args:
default: 默认值(默认为 ""
Returns:
str: 数据源ID
Environment Variables:
datasourceId: 数据源ID
"""
return os.environ.get("datasourceId", default)
def get_skill_id(default: str = "") -> str:
"""
获取技能ID环境变量
Args:
default: 默认值(默认为 ""
Returns:
str: 技能ID
Environment Variables:
skillId: 技能ID
"""
return os.environ.get("skillId", default)
def get_backend_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str:
"""
获取后端API基础URL环境变量
Args:
default: 默认值(默认为 "http://lzwcai-demp-corp-manager:8086"
Returns:
str: 后端API基础URL
Environment Variables:
backendBaseUrl: 后端API基础URL
"""
return os.environ.get("backendBaseUrl", default)
def get_env_config() -> dict:
"""
获取所有环境配置
Returns:
dict: 包含所有配置的字典
Example:
config = get_env_config()
print(config['database_id']) # 输出: "29"
print(config['datasource_id']) # 输出: ""
print(config['skill_id']) # 输出: ""
print(config['backend_base_url']) # 输出: "http://lzwcai-demp-corp-manager:8086"
"""
return {
"database_id": get_database_id(),
"datasource_id": get_datasource_id(),
"skill_id": get_skill_id(),
"backend_base_url": get_backend_base_url()
}
def set_env_variable(key: str, value: str) -> None:
"""
设置环境变量(仅在当前进程中有效)
Args:
key: 环境变量名
value: 环境变量值
Example:
set_env_variable("databaseId", "30")
set_env_variable("skillId", "1234567890")
"""
os.environ[key] = value

View File

@@ -1,60 +0,0 @@
"""JSON 文件读取工具"""
import json
from pathlib import Path
from typing import Any, Union
def load_json(json_path: Union[str, Path]) -> Any:
"""
读取 JSON 文件并返回其内容
Args:
json_path: JSON 文件的路径(支持字符串或 Path 对象)
Returns:
JSON 文件中解析后的数据(可以是字典、列表或其他 JSON 类型)
Raises:
FileNotFoundError: 当文件不存在时
json.JSONDecodeError: 当 JSON 格式无效时
Exception: 其他读取错误
Example:
>>> data = load_json('config.json')
>>> print(data)
{'key': 'value'}
>>> data = load_json(Path('data/users.json'))
>>> print(data)
[{'id': 1, 'name': 'Alice'}]
"""
try:
# 转换为 Path 对象
path = Path(json_path)
# 检查文件是否存在
if not path.exists():
raise FileNotFoundError(f"JSON 文件不存在: {json_path}")
# 检查是否为文件
if not path.is_file():
raise ValueError(f"路径不是一个文件: {json_path}")
# 读取并解析 JSON 文件
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
except json.JSONDecodeError as e:
raise json.JSONDecodeError(
f"JSON 格式错误: {e.msg}",
e.doc,
e.pos
)
except FileNotFoundError:
raise
except Exception as e:
raise Exception(f"读取 JSON 文件时发生错误: {str(e)}")

View File

@@ -1,489 +0,0 @@
# -*- coding: utf-8 -*-
"""
统一日志配置模块
提供系统级别的日志配置和管理
"""
import os
import sys
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from datetime import datetime
from pathlib import Path
class LoggerConfig:
"""日志配置管理类"""
def __init__(self, logs_dir: str = None):
"""初始化日志配置
Args:
logs_dir: 日志目录路径默认为项目根目录下的logs文件夹
"""
# 确定日志目录
if logs_dir:
self.logs_dir = Path(logs_dir)
else:
# 获取项目根目录logger_config.py 在 utils 目录下,需要上升两层到达项目根目录)
project_root = Path(__file__).parent.parent
self.logs_dir = project_root / "logs"
# 创建日志目录
self.logs_dir.mkdir(exist_ok=True)
# 日志格式
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
self.date_format = '%Y-%m-%d %H:%M:%S'
# 从环境变量获取日志级别默认为INFO
self.log_level = self._get_log_level_from_env()
# 是否已初始化
self._initialized = False
def _get_log_level_from_env(self) -> int:
"""从环境变量获取日志级别
Returns:
int: 日志级别
"""
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
# 日志级别映射
level_mapping = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'WARN': logging.WARNING, # 兼容性别名
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
'FATAL': logging.CRITICAL # 兼容性别名
}
return level_mapping.get(log_level_str, logging.INFO)
def setup_logging(self,
app_name: str = "lzwcai_mcp_sqlexecutor",
log_level: int = logging.INFO,
max_file_size: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5,
console_output: bool = True) -> logging.Logger:
"""设置系统日志配置
Args:
app_name: 应用名称,用于日志文件命名
log_level: 日志级别
max_file_size: 单个日志文件最大大小(字节)
backup_count: 保留的备份文件数量
console_output: 是否输出到控制台
Returns:
logging.Logger: 配置好的根日志器
"""
if self._initialized:
return logging.getLogger()
# 设置根日志器
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# 清除现有的处理器
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# 创建格式化器
formatter = logging.Formatter(self.log_format, self.date_format)
# 1. 主日志文件 - 按大小滚动
main_log_file = self.logs_dir / f"{app_name}.log"
file_handler = RotatingFileHandler(
main_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setLevel(log_level)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
# 2. 错误日志文件 - 只记录ERROR及以上级别
error_log_file = self.logs_dir / f"{app_name}_error.log"
error_handler = RotatingFileHandler(
error_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
root_logger.addHandler(error_handler)
# 3. 按日期滚动的日志文件
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
daily_handler = TimedRotatingFileHandler(
daily_log_file,
when='midnight',
interval=1,
backupCount=30, # 保留30天
encoding='utf-8'
)
daily_handler.setLevel(log_level)
daily_handler.setFormatter(formatter)
daily_handler.suffix = "%Y-%m-%d"
root_logger.addHandler(daily_handler)
# 4. 控制台输出
# 重要MCP协议使用stdio时必须将日志输出到stderrstdout仅用于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

View File

@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
"""
名称生成工具模块
"""
from pypinyin import lazy_pinyin, Style
import logging
logger = logging.getLogger(__name__)
def generate_tool_name(business_name: str, tool_id: str) -> str:
"""
根据业务名称和ID生成工具名称
格式: tool_拼音_id
Args:
business_name: 业务名称(中文)
tool_id: 工具ID
Returns:
str: 格式化的工具名称
"""
try:
# 将中文转换为拼音(无音调,小写)
pinyin_list = lazy_pinyin(business_name, style=Style.NORMAL)
# 拼接拼音
pinyin_str = ''.join(pinyin_list)
# 将 ID 中的 '-' 替换为 '_'
formatted_id = tool_id.replace('-', '_')
# 组合成最终的工具名称
tool_name = f"tool_{pinyin_str}_{formatted_id}"
return tool_name
except Exception as e:
logger.error(f"生成工具名称失败: {business_name}, {tool_id}, 错误: {e}", exc_info=True)
# 降级处理:如果拼音转换失败,使用 ID
return f"tool_{tool_id.replace('-', '_')}"

View File

@@ -1,166 +0,0 @@
# -*- coding: utf-8 -*-
"""
Schema 生成工具模块
"""
from typing import Any, Dict, List
def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]:
"""
从查询配置的参数定义生成 MCP 工具的 inputSchema
此函数会保留完整的 JSON Schema 信息,包括:
- type: Schema 类型(通常是 "object"
- required: 必填字段列表
- properties: 属性定义(包括每个属性的 type, description, format, examples 等)
- description: Schema 的整体描述(如果有)
- 以及其他任何 JSON Schema 标准字段
此函数还会自动添加以下字段(如果原始 parameters 中未定义):
- targetDatabaseName: 目标数据库名称(非必填,默认为空字符串)
Args:
parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象
Returns:
Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象
Example:
>>> params = {
... "type": "object",
... "required": ["userId", "startTime"],
... "properties": {
... "userId": {
... "type": "integer",
... "description": "用户的唯一标识符",
... "examples": [10086]
... },
... "startTime": {
... "type": "string",
... "format": "date-time",
... "description": "查询的起始时间",
... "examples": ["2023-01-01 00:00:00"]
... }
... }
... }
>>> schema = generate_input_schema(params)
>>> # schema 将包含所有原始信息,包括 format 和 examples
>>> # 同时会自动添加 targetDatabaseName 字段
"""
# 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用
# 但确保至少包含 type 和 properties
if not parameters:
# 如果 parameters 为空,返回一个空的 object schema
return {
"type": "object",
"properties": {},
"required": []
}
# 检查 parameters 是否已经是完整的 JSON Schema 格式
# 完整格式应该有 "type": "object" 和 "properties" 字段
if "type" in parameters and parameters.get("type") == "object" and "properties" in parameters:
# 已经是完整的 JSON Schema 格式,直接使用
input_schema = dict(parameters)
else:
# parameters 是简化格式(直接是参数定义),需要转换为 JSON Schema 格式
# 例如: {"workOrderNumber": {"type": "string", "description": "...", "required": true}}
properties = {}
required = []
for param_name, param_def in parameters.items():
if isinstance(param_def, dict):
# 提取 required 标记
if param_def.get("required", False):
required.append(param_name)
# 复制参数定义,但移除 required 字段(它应该在顶层的 required 数组中)
prop_def = {k: v for k, v in param_def.items() if k != "required"}
properties[param_name] = prop_def
input_schema = {
"type": "object",
"properties": properties,
"required": required
}
# 确保必需的字段存在
if "type" not in input_schema:
input_schema["type"] = "object"
if "properties" not in input_schema:
input_schema["properties"] = {}
if "required" not in input_schema:
input_schema["required"] = []
# 添加 targetDatabaseName 字段(如果不存在)
if "targetDatabaseName" not in input_schema["properties"]:
input_schema["properties"]["targetDatabaseName"] = {
"type": "string",
"description": "目标数据库名称",
"default": ""
}
# 保留所有其他字段,如 description, examples, format 等
# JSON Schema 标准支持的字段都会被保留:
# - additionalProperties
# - patternProperties
# - minProperties / maxProperties
# - dependencies
# - 等等
return input_schema
def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
"""
验证 inputSchema 是否符合基本的 JSON Schema 规范
Args:
schema: 要验证的 schema 对象
Returns:
tuple[bool, str]: (是否有效, 错误消息或成功消息)
Example:
>>> schema = {"type": "object", "properties": {"id": {"type": "string"}}}
>>> is_valid, msg = validate_input_schema(schema)
>>> print(is_valid, msg)
True, "Schema 验证通过"
"""
if not isinstance(schema, dict):
return False, "Schema 必须是一个字典对象"
if schema.get("type") != "object":
return False, "Schema 的 type 字段必须是 'object'"
if "properties" not in schema:
return False, "Schema 必须包含 properties 字段"
if not isinstance(schema.get("properties"), dict):
return False, "Schema 的 properties 字段必须是一个字典对象"
# 验证 required 字段(如果存在)
if "required" in schema:
required = schema["required"]
if not isinstance(required, list):
return False, "Schema 的 required 字段必须是一个列表"
# 验证所有 required 的字段都在 properties 中定义
properties = schema["properties"]
for field in required:
if field not in properties:
return False, f"必填字段 '{field}' 未在 properties 中定义"
# 验证 properties 中每个字段的定义
for prop_name, prop_def in schema["properties"].items():
if not isinstance(prop_def, dict):
return False, f"属性 '{prop_name}' 的定义必须是一个字典对象"
if "type" not in prop_def:
return False, f"属性 '{prop_name}' 必须包含 type 字段"
return True, "Schema 验证通过"

View File

@@ -1,497 +0,0 @@
version = 1
revision = 2
requires-python = ">=3.13"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aff/07c09a53a08bc/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/1f0/2e8b43a8fbbc3/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" },
]
[[package]]
name = "anyio"
version = "4.11.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/82a/8d0b81e318cc5/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/028/7e96f4d26d414/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" },
]
[[package]]
name = "attrs"
version = "25.4.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" },
]
[[package]]
name = "certifi"
version = "2025.10.5"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/47c/09d31ccf2acf0/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/0f2/12c2744a9bb6d/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" },
]
[[package]]
name = "click"
version = "8.3.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e7b/8232224eba16f/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/9b9/f285302c6e306/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/086/95f5cb7ed6e05/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4f1/d9991f5acc0ca/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/4e3/5b956cf45792e/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/63c/f8bbe7522de3b/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/6e3/4463af53fd2ab/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/2d4/00746a40668fc/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/75e/98c5f16b0f35b/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/d90/9fcccc110f8c7/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" },
]
[[package]]
name = "httpx-sse"
version = "0.4.3"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9b1/ed0127459a660/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/0ac/1c9fe3c0afad2/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/795/dafcc9c04ed0c/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/771/a87f49d9defaf/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" },
]
[[package]]
name = "jsonschema"
version = "4.25.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "jsonschema-specifications" },
{ name = "referencing" },
{ name = "rpds-py" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/e4a/9655ce0da0c0b/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/3fb/a0169e345c717/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "referencing" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/b54/0987f239e7456/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/988/02fee3a11ee76/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" },
]
[[package]]
name = "lzwcai-mcpskills-analyzeWorkOrder"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "httpx" },
{ name = "mcp", extra = ["cli"] },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.10.1" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/cb0/a2b4aa34f932c/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/873/27c59b172c501/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" },
]
[[package]]
name = "mcp"
version = "1.10.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/aaa/0957d8307feef/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4d0/8301aefe906dc/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5" },
]
[package.optional-dependencies]
cli = [
{ name = "python-dotenv" },
{ name = "typer" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/bb4/13d29f5eea38f/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/840/08a41e51615a4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" },
]
[[package]]
name = "pydantic"
version = "2.12.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7b8/fa15b831a4bbd/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/25f/f718ee909acd8/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" },
]
[[package]]
name = "pydantic-core"
version = "2.41.4"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/70e/47929a9d4a190/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/85e/050ad9e5f6fe1/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e73/93f1d64792763/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/94d/ab0940b0d1fb2/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/de7/c42f897e689ee/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/664/b319919326227/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d95/b253b88f7d308/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a13/51f5bbdbbabc6/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/1af/fa4798520b148/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/7b7/4e18052fea4aa/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/285/b643d75c0e30a/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f52/679ff4218d713/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ecd/e6dedd6fff127/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d08/1a1f3800f0540/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f8e/49c9c364a7edc/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed9/7fd56a561f5eb/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a87/0c307bf1ee91f/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d25/e97bc1f5f8f79/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d40/5d14bea042f16/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/19f/3684868309db5/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e92/05d97ed08a82e/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/82d/f1f432b37d832/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fc3/b4cc4539e055c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b1e/b1754fce47c63/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e6a/b5ab30ef325b4/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/31a/41030b1d9ca49/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a44/ac1738591472c/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d72/f2b5e6e82ab8f/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c4d/1e854aaf04448/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b56/8af94267729d7/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6d5/5fb8b1e8929b3/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/5b6/6584e549e2e32/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/557/a0aab88664cc5/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/3f1/ea6f48a045745/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6c1/fe4c5404c448b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/523/e7da4d43b113b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/572/9225de81fb65b/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/de2/cfbb09e88f0f7/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d34/f950ae05a83e0/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" },
]
[[package]]
name = "pydantic-settings"
version = "2.11.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/d0e/87a1c7d33593b/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/fe2/cea3413b9530d/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/636/cb2477cec7f89/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/865/40386c03d588b/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" },
]
[[package]]
name = "python-dotenv"
version = "1.1.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/a8a/6399716257f45/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/31f/23644fe2602f8/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8dd/0cab45b8e2306/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/8a6/2d3a8335e0658/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104" },
]
[[package]]
name = "referencing"
version = "0.37.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/44a/efc3142c5b842/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/381/329a9f99628c9/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/73f/f50c7c0c1c77c/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/76b/c51fe2e57d2b1/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" },
]
[[package]]
name = "rpds-py"
version = "0.27.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/26a/1c73171d10b7a/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/e4b/9fcfbc0216338/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/144/1811a96eadca9/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/552/66dafa22e672f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d78/827d7ac08627e/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ae9/2443798a40a92/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c46/c9dd2403b66a2/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/2ef/e4eb1d01b7f5f/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/15d/3b4d83582d10c/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/4ed/2e16abbc982a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a75/f305c9b013289/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/67c/e762070474588/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/9d9/92ac10eb86d9b/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/4f7/5e4bd8ab8db62/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f90/25faafc62ed0b/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed1/0dc32829e7d22/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/920/22bbbad0d4426/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/471/62fdab9407ec3/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fb8/9bec23fddc489/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/e48/af21883ded2b3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/6f5/b7bd8e219ed50/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/08f/1e20bccf73b08/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/0dc/5dceeaefcc96d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d76/f9cc8665acdc0/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/134/fae0e36022eda/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/eb1/1a4f1b2b63337/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/13e/608ac9f50a0ed/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dd2/135527aa40f06/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/302/0724ade63fe32/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/8ee/50c3e41739886/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/acb/9aafccaae278f/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b7f/b801aa7f845dd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fe0/dd05afb46597b/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/b6d/fb0e058adb12d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ed0/90ccd235f6fa8/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/bf8/76e79763eecf3/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/12e/d005216a51b1d/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ee4/308f409a40e50/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/0b0/8d152555acf1f/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dce/51c828941973a/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/c14/76d6f29eb81aa/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/3ce/0cac322b0d69b/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/dfb/fac137d2a3d07/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a6e/57b0abfe7cc51/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/faf/8d146f3d476ab/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/ba8/1d2b56b6d4911/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/84f/7d509870098de/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/a9e/960fc78fecd11/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/62f/85b665cedab1a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fed/467af29776f65/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f27/29615f9d430af/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/1b2/07d881a9aef7b/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/639/fd5efec029f99/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/fec/c80cb2a90e28a/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/42a/89282d711711d/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/cf9/931f14223de59/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/f39/f58a27cc6e59f/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/d5f/a0ee122dc09e2/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a" },
{ url = "http://devpi.iepai.fun/root/pypi/+f/656/7d2bb951e2123/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/8db/ca0739d487e5b/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/7ec/fff8f2fd72616/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/f43/24edc670a0f49/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/2f6/da418d1f1e0fd/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" },
]
[[package]]
name = "sse-starlette"
version = "3.0.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ccd/60b5765ebb358/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/16b/7cbfddbcd4eac/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a" },
]
[[package]]
name = "starlette"
version = "0.48.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/7e8/cee469a8ab235/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/076/4ca97b0975825/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" },
]
[[package]]
name = "typer"
version = "0.19.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/9ad/824308ded0ad0/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/755/e7e19670ffad8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/ba5/61c48a67c5958/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/4ed/1cacbdc298c22/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" },
]
[[package]]
name = "uvicorn"
version = "0.37.0"
source = { registry = "http://devpi.iepai.fun/lzwc/dev/+simple/" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "http://devpi.iepai.fun/root/pypi/+f/411/5c8add6d3fd53/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" }
wheels = [
{ url = "http://devpi.iepai.fun/root/pypi/+f/913/b2b8867234373/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" },
]

View File

@@ -1,13 +0,0 @@
"""
Entry point for lzwcai-mcpskills-mfg-data-agent (制造业数据智能体)
Runs the MCP server for manufacturing data intelligence
"""
import os
os.environ["databaseId"] = "57"
os.environ["datasourceId"] = "57"
os.environ["backendBaseUrl"] = "http://192.167.30.2:8088"
if __name__ == "__main__":
# Import and run the actual MCP server
from lzwcai_mcpskills_mfg_data_agent.main import main
main()

View File

@@ -1,38 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcpskills-mfg-data-agent"
version = "0.1.6"
description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcpskills-mfg-data-agent = "lzwcai_mcpskills_mfg_data_agent.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcpskills_mfg_data_agent"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcpskills_mfg_data_agent/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agent/businessQueries.json"

View File

@@ -1,317 +0,0 @@
-- =====================================================
-- 一页式决策简报SQL
-- One-Page Decision Brief
-- 数据库: PostgreSQL
-- =====================================================
-- =====================================================
-- 主查询:一页式决策简报
-- =====================================================
WITH
sales_summary AS (
SELECT
COUNT(*) AS total_orders,
SUM(deal_amount) AS total_sales_amount,
AVG(deal_amount) AS avg_order_amount,
SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders,
SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders,
SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders,
SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount,
SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount
FROM fact_sales_order
),
production_summary AS (
SELECT
COUNT(*) AS total_work_orders,
SUM(planned_qty) AS total_planned_qty,
SUM(completed_qty) AS total_completed_qty,
SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders,
SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders,
SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders
FROM fact_work_order
),
ar_summary AS (
SELECT
COUNT(*) AS receipt_count,
SUM(amount) AS total_receipt_amount,
AVG(amount) AS avg_receipt_amount
FROM fact_ar_receipt
),
ap_summary AS (
SELECT
COUNT(*) AS payment_count,
SUM(amount) AS total_payment_amount,
AVG(amount) AS avg_payment_amount
FROM fact_ap_payment
),
invoice_summary AS (
SELECT
COUNT(*) AS invoice_count,
SUM(invoice_amount) AS total_invoice_amount,
AVG(invoice_amount) AS avg_invoice_amount
FROM fact_invoice
),
return_summary AS (
SELECT
COUNT(*) AS return_count,
SUM(amount) AS total_return_amount,
AVG(amount) AS avg_return_amount
FROM fact_sales_return
),
shipment_summary AS (
SELECT
COUNT(*) AS shipment_count,
SUM(amount) AS total_shipment_amount,
AVG(amount) AS avg_shipment_amount
FROM fact_sales_shipment
),
purchase_summary AS (
SELECT COUNT(*) AS purchase_order_count
FROM fact_purchase_order
),
quality_summary AS (
SELECT
COUNT(*) AS inspection_count,
SUM(pass_qty) AS total_pass_qty,
SUM(fail_qty) AS total_fail_qty
FROM fact_quality_inspection
),
labor_summary AS (
SELECT
COUNT(DISTINCT worker_name) AS worker_count,
SUM(duration_minutes) AS total_work_minutes,
SUM(report_qty) AS total_output_qty
FROM fact_labor_report
),
scrap_summary AS (
SELECT COUNT(*) AS scrap_count
FROM fact_scrap
)
SELECT
'Decision Brief' AS report_title,
CURRENT_DATE AS report_date,
-- 销售板块
ss.total_orders AS sales_order_count,
ROUND(ss.total_sales_amount, 2) AS total_sales_amount,
ROUND(ss.avg_order_amount, 2) AS avg_order_amount,
ss.paid_orders AS paid_order_count,
ss.partial_orders AS partial_paid_count,
ss.unpaid_orders AS unpaid_order_count,
ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate,
ROUND(ss.unpaid_amount, 2) AS receivable_amount,
-- 生产板块
ps.total_work_orders AS work_order_count,
ps.closed_orders AS completed_work_orders,
ps.started_orders AS in_progress_work_orders,
ps.open_orders AS pending_work_orders,
ROUND(ps.total_planned_qty, 0) AS planned_qty,
ROUND(ps.total_completed_qty, 0) AS completed_qty,
ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate,
-- 人效板块
ls.worker_count AS active_worker_count,
ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours,
ROUND(ls.total_output_qty, 0) AS total_output,
ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker,
ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour,
-- 质量板块
qs.inspection_count AS qc_batch_count,
ROUND(qs.total_pass_qty, 0) AS pass_qty,
ROUND(qs.total_fail_qty, 0) AS fail_qty,
ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate,
ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate,
scr.scrap_count AS scrap_record_count,
-- 财务板块
ar.receipt_count AS ar_receipt_count,
ROUND(ar.total_receipt_amount, 2) AS total_ar_amount,
ap.payment_count AS ap_payment_count,
ROUND(ap.total_payment_amount, 2) AS total_ap_amount,
ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow,
inv.invoice_count AS invoice_count,
ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount,
-- 物流板块
sh.shipment_count AS shipment_count,
ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount,
pur.purchase_order_count AS purchase_order_count,
-- 售后板块
ret.return_count AS return_count,
ROUND(ret.total_return_amount, 2) AS total_return_amount,
ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate,
-- 综合健康度评估
CASE
WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95
AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80
AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5
THEN 'EXCELLENT'
WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90
AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60
AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10
THEN 'GOOD'
ELSE 'WARNING'
END AS health_status
FROM sales_summary ss
CROSS JOIN production_summary ps
CROSS JOIN ar_summary ar
CROSS JOIN ap_summary ap
CROSS JOIN invoice_summary inv
CROSS JOIN return_summary ret
CROSS JOIN shipment_summary sh
CROSS JOIN purchase_summary pur
CROSS JOIN quality_summary qs
CROSS JOIN labor_summary ls
CROSS JOIN scrap_summary scr;
-- =====================================================
-- 补充查询1本月 vs 上月对比分析
-- =====================================================
WITH
current_month AS (
SELECT
'current_month' AS period,
COUNT(*) AS order_count,
SUM(deal_amount) AS sales_amount
FROM fact_sales_order
WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE)
),
last_month AS (
SELECT
'last_month' AS period,
COUNT(*) AS order_count,
SUM(deal_amount) AS sales_amount
FROM fact_sales_order
WHERE DATE_TRUNC('month', order_date_utc::timestamp) = DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')
)
SELECT
period,
order_count,
ROUND(sales_amount, 2) AS sales_amount
FROM current_month
UNION ALL
SELECT * FROM last_month;
-- =====================================================
-- 补充查询2客户贡献度TOP10
-- =====================================================
SELECT
c.customer_name,
COUNT(so.sales_order_id) AS order_count,
ROUND(SUM(so.deal_amount), 2) AS total_order_amount,
ROUND(SUM(so.deal_amount) * 100.0 / (SELECT SUM(deal_amount) FROM fact_sales_order), 2) AS contribution_rate,
SUM(CASE WHEN so.payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_order_count,
SUM(CASE WHEN so.payment_status = 'UNPAID' THEN so.deal_amount ELSE 0 END) AS receivable_amount
FROM fact_sales_order so
INNER JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't'
GROUP BY c.customer_name
ORDER BY total_order_amount DESC
LIMIT 10;
-- =====================================================
-- 补充查询3产品类别业绩分析
-- =====================================================
SELECT
p.product_category,
COUNT(DISTINCT wo.work_order_id) AS work_order_count,
ROUND(SUM(wo.planned_qty), 0) AS planned_qty,
ROUND(SUM(wo.completed_qty), 0) AS completed_qty,
ROUND(SUM(wo.completed_qty) * 100.0 / NULLIF(SUM(wo.planned_qty), 0), 1) AS completion_rate,
COUNT(DISTINCT lr.worker_name) AS worker_count,
ROUND(SUM(lr.duration_minutes) / 60.0, 1) AS total_work_hours
FROM dim_product p
LEFT JOIN fact_work_order wo ON p.product_id = wo.product_id
LEFT JOIN fact_labor_report lr ON p.product_id = lr.product_id
WHERE p.is_current = 't'
GROUP BY p.product_category
ORDER BY completed_qty DESC;
-- =====================================================
-- 补充查询4关键预警指标
-- =====================================================
SELECT
'warning_metrics' AS category,
'unpaid_order_amount' AS metric_name,
ROUND(SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END), 2) AS current_value,
50000 AS threshold,
CASE
WHEN SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) > 50000
THEN 'EXCEEDED' ELSE 'NORMAL'
END AS status
FROM fact_sales_order
UNION ALL
SELECT
'warning_metrics',
'defect_rate_pct',
ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0), 2),
5,
CASE
WHEN SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty) + SUM(fail_qty), 0) > 5
THEN 'EXCEEDED' ELSE 'NORMAL'
END
FROM fact_quality_inspection
UNION ALL
SELECT
'warning_metrics',
'production_completion_rate',
ROUND(SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0), 2),
70,
CASE
WHEN SUM(completed_qty) * 100.0 / NULLIF(SUM(planned_qty), 0) < 70
THEN 'LOW' ELSE 'NORMAL'
END
FROM fact_work_order
UNION ALL
SELECT
'warning_metrics',
'return_rate_pct',
ROUND((SELECT SUM(amount) FROM fact_sales_return) * 100.0 /
NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0), 2),
5,
CASE
WHEN (SELECT SUM(amount) FROM fact_sales_return) * 100.0 /
NULLIF((SELECT SUM(deal_amount) FROM fact_sales_order), 0) > 5
THEN 'EXCEEDED' ELSE 'NORMAL'
END;
-- =====================================================
-- 补充查询5月度趋势汇总
-- =====================================================
SELECT
TO_CHAR(DATE_TRUNC('month', order_date_utc::timestamp), 'YYYY-MM') AS month,
COUNT(*) AS order_count,
ROUND(SUM(deal_amount), 2) AS sales_amount,
ROUND(AVG(deal_amount), 2) AS avg_order_amount,
SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_count,
SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_count
FROM fact_sales_order
GROUP BY DATE_TRUNC('month', order_date_utc::timestamp)
ORDER BY month DESC
LIMIT 12;

View File

@@ -1,193 +0,0 @@
-- =====================================================
-- 人效-产值-损耗三维模型仪表盘SQL
-- Efficiency-Output-Loss 3D Model Dashboard
-- 数据库: PostgreSQL
-- 维度: 产品类别(化工/机械/电子)作为部门维度
-- =====================================================
-- 1. 部门级综合仪表盘(主查询)
WITH
labor_stats AS (
SELECT
p.product_category AS department,
COUNT(DISTINCT lr.worker_name) AS worker_count,
SUM(lr.duration_minutes) AS total_work_minutes,
SUM(lr.report_qty) AS total_output_qty,
COUNT(DISTINCT lr.work_order_number) AS work_order_count
FROM fact_labor_report lr
INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't'
GROUP BY p.product_category
),
work_order_stats AS (
SELECT
p.product_category AS department,
COUNT(*) AS total_work_orders,
SUM(wo.planned_qty) AS total_planned_qty,
SUM(wo.completed_qty) AS total_completed_qty,
SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders,
SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders,
SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders
FROM fact_work_order wo
INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't'
GROUP BY p.product_category
),
quality_stats AS (
SELECT
p.product_category AS department,
SUM(qi.pass_qty) AS total_pass_qty,
SUM(qi.fail_qty) AS total_fail_qty,
COUNT(*) AS inspection_count
FROM fact_quality_inspection qi
INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't'
GROUP BY p.product_category
),
sales_stats AS (
SELECT
AVG(deal_amount) AS avg_order_amount,
SUM(deal_amount) AS total_sales_amount,
COUNT(*) AS order_count
FROM fact_sales_order
),
scrap_stats AS (
SELECT COUNT(*) AS total_scrap_count
FROM fact_scrap
)
SELECT
ls.department,
-- 人效维度
ls.worker_count,
ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours,
ls.total_output_qty,
ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker,
ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour,
ROUND(
ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0),
2
) AS efficiency_index,
-- 产值维度
ws.total_planned_qty AS planned_qty,
ws.total_completed_qty AS completed_qty,
ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate,
ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value,
ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker,
-- 损耗维度
qs.total_pass_qty AS qc_pass_qty,
qs.total_fail_qty AS qc_fail_qty,
ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate,
ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate,
-- 综合评分
ROUND(
(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 +
(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 +
(qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25,
2
) AS performance_score,
-- 预警等级
CASE
WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10
OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50
THEN 'RED'
WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5
OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70
THEN 'YELLOW'
ELSE 'GREEN'
END AS warning_level
FROM labor_stats ls
LEFT JOIN work_order_stats ws ON ls.department = ws.department
LEFT JOIN quality_stats qs ON ls.department = qs.department
CROSS JOIN sales_stats ss
CROSS JOIN scrap_stats scr
ORDER BY performance_score DESC;
-- =====================================================
-- 2. 人员级明细报表
-- =====================================================
SELECT
lr.worker_name,
p.product_category AS department,
COUNT(DISTINCT lr.work_order_number) AS work_order_count,
SUM(lr.duration_minutes) AS total_work_minutes,
ROUND(SUM(lr.duration_minutes) / 60.0, 2) AS total_work_hours,
SUM(lr.report_qty) AS total_output,
ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour,
RANK() OVER (PARTITION BY p.product_category ORDER BY SUM(lr.report_qty) DESC) AS dept_output_rank
FROM fact_labor_report lr
INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't'
GROUP BY lr.worker_name, p.product_category
ORDER BY p.product_category, total_output DESC;
-- =====================================================
-- 3. 月度趋势分析
-- =====================================================
SELECT
TO_CHAR(DATE_TRUNC('month', lr.event_time_utc::timestamp), 'YYYY-MM') AS month,
p.product_category AS department,
COUNT(DISTINCT lr.worker_name) AS active_worker_count,
SUM(lr.duration_minutes) AS total_work_minutes,
SUM(lr.report_qty) AS total_output,
ROUND(SUM(lr.report_qty) / NULLIF(COUNT(DISTINCT lr.worker_name), 0), 2) AS output_per_worker,
ROUND(SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0), 2) AS output_per_hour
FROM fact_labor_report lr
INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't'
GROUP BY DATE_TRUNC('month', lr.event_time_utc::timestamp), p.product_category
ORDER BY month DESC, p.product_category;
-- =====================================================
-- 4. 产品级损耗分析
-- =====================================================
SELECT
p.product_category AS department,
p.product_name,
p.product_code,
COALESCE(SUM(qi.pass_qty), 0) AS pass_qty,
COALESCE(SUM(qi.fail_qty), 0) AS fail_qty,
ROUND(
COALESCE(SUM(qi.fail_qty), 0) /
NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100,
2
) AS defect_rate,
CASE
WHEN COALESCE(SUM(qi.fail_qty), 0) /
NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 10
THEN 'HIGH'
WHEN COALESCE(SUM(qi.fail_qty), 0) /
NULLIF(COALESCE(SUM(qi.pass_qty), 0) + COALESCE(SUM(qi.fail_qty), 0), 0) * 100 >= 5
THEN 'MEDIUM'
ELSE 'LOW'
END AS loss_level
FROM dim_product p
LEFT JOIN fact_quality_inspection qi ON p.product_id = qi.product_id
WHERE p.is_current = 't'
GROUP BY p.product_category, p.product_name, p.product_code
ORDER BY defect_rate DESC NULLS LAST;
-- =====================================================
-- 5. 工单完成率分析
-- =====================================================
SELECT
p.product_category AS department,
wo.status,
COUNT(*) AS order_count,
SUM(wo.planned_qty) AS total_planned,
SUM(wo.completed_qty) AS total_completed,
ROUND(SUM(wo.completed_qty) / NULLIF(SUM(wo.planned_qty), 0) * 100, 2) AS completion_rate,
ROUND(AVG(wo.completed_qty / NULLIF(wo.planned_qty, 0) * 100), 2) AS avg_completion_rate
FROM fact_work_order wo
INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't'
GROUP BY p.product_category, wo.status
ORDER BY p.product_category, wo.status;

View File

@@ -1,416 +0,0 @@
-- =====================================================
-- 供应链风险预警SQL
-- Supply Chain Risk Warning
-- 数据库: PostgreSQL
-- =====================================================
-- =====================================================
-- 1. 供应商历史交期表现分析
-- =====================================================
WITH supplier_delivery AS (
SELECT
po.supplier_id,
COUNT(*) AS order_count,
COUNT(pr.purchase_receipt_id) AS receipt_count,
AVG(
CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)
ELSE NULL END
) AS avg_delivery_days,
MAX(
CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)
ELSE NULL END
) AS max_delivery_days,
STDDEV(
CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)
ELSE NULL END
) AS stddev_delivery_days
FROM fact_purchase_order po
LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id
GROUP BY po.supplier_id
),
supplier_quality AS (
SELECT
pr.supplier_id,
COUNT(pr.purchase_receipt_id) AS total_receipts,
SUM(pr.receipt_qty_total) AS total_qty,
SUM(pr.amount) AS total_amount
FROM fact_purchase_receipt pr
GROUP BY pr.supplier_id
),
supplier_returns AS (
SELECT
pret.supplier_id,
COUNT(*) AS return_count,
SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count
FROM fact_purchase_return pret
GROUP BY pret.supplier_id
),
supplier_quality_rate AS (
SELECT
sq.supplier_id,
sq.total_receipts,
sq.total_qty,
sq.total_amount,
COALESCE(sr.return_count, 0) AS return_count,
COALESCE(sr.damage_count, 0) AS damage_count,
CASE WHEN sq.total_receipts > 0
THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts
ELSE 100 END AS quality_rate
FROM supplier_quality sq
LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id
),
supplier_risk AS (
SELECT
s.supplier_id,
s.supplier_name,
s.supplier_category,
COALESCE(sd.order_count, 0) AS order_count,
COALESCE(sd.receipt_count, 0) AS receipt_count,
ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days,
ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days,
ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility,
COALESCE(sqr.total_receipts, 0) AS total_receipts,
COALESCE(sqr.return_count, 0) AS return_count,
ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate,
CASE
WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40
WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30
WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20
ELSE 10
END +
CASE
WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30
WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20
ELSE 10
END AS delivery_risk_score,
CASE
WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50
WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30
WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15
ELSE 5
END AS quality_risk_score
FROM dim_supplier s
LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id
LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id
WHERE s.is_current = 't'
),
supplier_risk_level AS (
SELECT
*,
delivery_risk_score + quality_risk_score AS total_risk_score,
CASE
WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH'
WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM'
ELSE 'LOW'
END AS risk_level,
CASE
WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY'
WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE'
WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE'
ELSE 'NORMAL'
END AS risk_pattern
FROM supplier_risk
)
SELECT
supplier_name,
supplier_category,
order_count,
receipt_count,
avg_delivery_days,
max_delivery_days,
delivery_volatility,
total_receipts,
return_count,
quality_rate,
delivery_risk_score,
quality_risk_score,
total_risk_score,
risk_level,
risk_pattern
FROM supplier_risk_level
ORDER BY total_risk_score DESC, supplier_name;
-- =====================================================
-- 补充查询1高风险订单预警清单
-- =====================================================
WITH supplier_risk_info AS (
SELECT
s.supplier_id,
s.supplier_name,
s.supplier_category,
COALESCE(
(SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp))
FROM fact_purchase_order po2
LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id
WHERE po2.supplier_id = s.supplier_id
AND pr.doc_date_utc IS NOT NULL), 0
) AS avg_delivery_days,
COALESCE(
(SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0
) AS return_count,
COALESCE(
(SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0
) AS receipt_count
FROM dim_supplier s
WHERE s.is_current = 't'
),
order_risk AS (
SELECT
po.purchase_order_number,
po.doc_date_utc::date AS order_date,
sri.supplier_name,
sri.supplier_category,
ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days,
sri.return_count,
sri.receipt_count,
CASE WHEN sri.receipt_count > 0
THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1)
ELSE 100 END AS quality_rate,
EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order,
CASE
WHEN sri.avg_delivery_days > 45 AND sri.return_count > 0 THEN 'HIGH'
WHEN sri.avg_delivery_days > 45 OR sri.return_count > 0 THEN 'MEDIUM'
ELSE 'LOW'
END AS risk_level
FROM fact_purchase_order po
JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id
)
SELECT
purchase_order_number,
order_date,
supplier_name,
supplier_category,
supplier_avg_days,
quality_rate,
days_since_order,
risk_level,
CASE
WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP'
WHEN risk_level = 'MEDIUM' THEN 'MONITOR'
ELSE 'NORMAL'
END AS action_required
FROM order_risk
WHERE risk_level IN ('HIGH', 'MEDIUM')
ORDER BY
CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END,
days_since_order DESC;
-- =====================================================
-- 补充查询2供应商类别风险分布
-- =====================================================
WITH supplier_stats AS (
SELECT
s.supplier_category,
COUNT(DISTINCT s.supplier_id) AS supplier_count,
COUNT(DISTINCT po.purchase_order_id) AS order_count,
COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count,
COUNT(DISTINCT pret.purchase_return_id) AS return_count,
SUM(pr.amount) AS total_amount
FROM dim_supplier s
LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id
LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id
LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id
WHERE s.is_current = 't'
GROUP BY s.supplier_category
)
SELECT
supplier_category,
supplier_count,
order_count,
receipt_count,
return_count,
CASE WHEN receipt_count > 0
THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1)
ELSE 100 END AS quality_rate,
ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount,
CASE
WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 'HIGH'
WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 5 THEN 'MEDIUM'
ELSE 'LOW'
END AS category_risk_level
FROM supplier_stats
ORDER BY return_count DESC;
-- =====================================================
-- 补充查询3近期交期异常趋势
-- =====================================================
WITH monthly_delivery AS (
SELECT
DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start,
COUNT(*) AS receipt_count,
AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days
FROM fact_purchase_receipt pr
JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id
WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)
),
monthly_returns AS (
SELECT
DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start,
COUNT(*) AS return_count
FROM fact_purchase_return pret
GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)
),
monthly_combined AS (
SELECT
md.month_start,
md.receipt_count,
ROUND(md.avg_delivery_days, 1) AS avg_delivery_days,
COALESCE(mr.return_count, 0) AS return_count
FROM monthly_delivery md
LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start
),
with_trend AS (
SELECT
*,
LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days,
LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count
FROM monthly_combined
)
SELECT
month_start,
receipt_count,
avg_delivery_days,
return_count,
CASE WHEN receipt_count > 0
THEN ROUND(return_count * 100.0 / receipt_count, 1)
ELSE 0 END AS return_rate,
CASE
WHEN prev_avg_days IS NULL THEN 'NONE'
WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING'
WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING'
ELSE 'STABLE'
END AS delivery_trend,
CASE
WHEN prev_return_count IS NULL THEN 'NONE'
WHEN return_count > prev_return_count THEN 'INCREASING'
WHEN return_count < prev_return_count THEN 'DECREASING'
ELSE 'STABLE'
END AS return_trend
FROM with_trend
ORDER BY month_start DESC;
-- =====================================================
-- 补充查询4风险预警汇总看板
-- =====================================================
WITH risk_summary AS (
SELECT
s.supplier_id,
s.supplier_name,
COALESCE(
(SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0
) AS return_count,
COALESCE(
(SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0
) AS receipt_count
FROM dim_supplier s
WHERE s.is_current = 't'
),
risk_counts AS (
SELECT
SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 1 ELSE 0 END) AS high_risk_suppliers,
SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 5 AND 10 THEN 1 ELSE 0 END) AS medium_risk_suppliers,
SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 5 THEN 1 ELSE 0 END) AS low_risk_suppliers,
COUNT(*) AS total_suppliers
FROM risk_summary
),
order_stats AS (
SELECT
COUNT(*) AS pending_orders,
COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders
FROM fact_purchase_order
),
return_stats AS (
SELECT
COUNT(*) AS total_returns,
COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns
FROM fact_purchase_return
)
SELECT
'high_risk_suppliers' AS metric_name,
rc.high_risk_suppliers::text AS metric_value,
CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status
FROM risk_counts rc
UNION ALL
SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text,
CASE WHEN rc.medium_risk_suppliers > 2 THEN 'MONITOR' ELSE 'NORMAL' END
FROM risk_counts rc
UNION ALL
SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL'
FROM risk_counts rc
UNION ALL
SELECT 'pending_orders', os.pending_orders::text,
CASE WHEN os.pending_orders > 20 THEN 'BACKLOG' ELSE 'NORMAL' END
FROM order_stats os
UNION ALL
SELECT 'overdue_orders_30d', os.overdue_orders::text,
CASE WHEN os.overdue_orders > 5 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END
FROM order_stats os
UNION ALL
SELECT 'recent_returns_30d', rs.recent_returns::text,
CASE WHEN rs.recent_returns > 3 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END
FROM return_stats rs;
-- =====================================================
-- 补充查询5供应商风险排行榜
-- =====================================================
WITH supplier_metrics AS (
SELECT
s.supplier_id,
s.supplier_name,
s.supplier_category,
COUNT(DISTINCT po.purchase_order_id) AS order_count,
COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count,
COUNT(DISTINCT pret.purchase_return_id) AS return_count,
SUM(pr.amount) AS total_amount
FROM dim_supplier s
LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id
LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id
LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id
WHERE s.is_current = 't'
GROUP BY s.supplier_id, s.supplier_name, s.supplier_category
),
ranked_suppliers AS (
SELECT
*,
CASE WHEN receipt_count > 0
THEN ROUND(return_count * 100.0 / receipt_count, 1)
ELSE 0 END AS return_rate,
ROW_NUMBER() OVER (ORDER BY
CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC,
return_count DESC
) AS risk_rank
FROM supplier_metrics
)
SELECT
risk_rank,
supplier_name,
supplier_category,
order_count,
receipt_count,
return_count,
return_rate,
ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount,
CASE
WHEN return_rate > 10 THEN 'HIGH_RISK'
WHEN return_rate > 5 THEN 'MEDIUM_RISK'
WHEN return_count > 0 THEN 'LOW_RISK'
ELSE 'EXCELLENT'
END AS risk_assessment
FROM ranked_suppliers
ORDER BY risk_rank
LIMIT 20;

View File

@@ -1,409 +0,0 @@
-- =====================================================
-- 工单执行进度与异常节点SQL
-- 数据库: PostgreSQL
-- 实时拉取工单数据,动态映射订单各环节状态
-- =====================================================
-- =====================================================
-- 1. 工单执行进度主视图
-- =====================================================
WITH work_order_base AS (
SELECT
wo.work_order_id,
wo.work_order_number,
wo.product_id,
wo.status,
wo.planned_qty,
wo.completed_qty,
wo.event_time_utc::timestamp AS start_time,
wo.last_updated_utc::timestamp AS last_update,
wo.source_system
FROM fact_work_order wo
),
product_info AS (
SELECT
product_id,
product_name,
product_category
FROM dim_product
WHERE is_current = 't'
),
labor_summary AS (
SELECT
work_order_number,
COUNT(DISTINCT worker_name) AS worker_count,
SUM(report_qty) AS total_report_qty,
SUM(duration_minutes) AS total_minutes,
MAX(event_time_utc::timestamp) AS last_report_time
FROM fact_labor_report
GROUP BY work_order_number
),
quality_summary AS (
SELECT
work_order_number,
SUM(pass_qty) AS pass_qty,
SUM(fail_qty) AS fail_qty
FROM fact_quality_inspection
GROUP BY work_order_number
),
-- =====================================================
-- 2. 工单进度计算与状态映射
-- =====================================================
work_order_progress AS (
SELECT
wb.work_order_id,
wb.work_order_number,
p.product_name,
p.product_category,
wb.status AS raw_status,
-- 状态映射
CASE wb.status
WHEN 'OPEN' THEN '待生产'
WHEN 'STARTED' THEN '生产中'
WHEN 'CLOSED' THEN '已完成'
ELSE '未知'
END AS status_name,
wb.planned_qty,
wb.completed_qty,
-- 完成进度
CASE WHEN wb.planned_qty > 0
THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1)
ELSE 0 END AS completion_rate,
-- 剩余数量
GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty,
wb.start_time,
wb.last_update,
-- 已用时间(小时)
ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours,
-- 报工信息
COALESCE(ls.worker_count, 0) AS worker_count,
COALESCE(ls.total_report_qty, 0) AS total_report_qty,
COALESCE(ls.total_minutes, 0) AS total_work_minutes,
ls.last_report_time,
-- 质检信息
COALESCE(qs.pass_qty, 0) AS qc_pass_qty,
COALESCE(qs.fail_qty, 0) AS qc_fail_qty
FROM work_order_base wb
LEFT JOIN product_info p ON wb.product_id = p.product_id
LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number
LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number
),
-- =====================================================
-- 3. 异常节点检测
-- =====================================================
anomaly_detection AS (
SELECT
*,
-- 进度异常:进行中但完成率过低
CASE
WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN '进度严重滞后'
WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后'
ELSE NULL
END AS progress_anomaly,
-- 质量异常:废品率过高
CASE
WHEN qc_pass_qty + qc_fail_qty > 0
AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN '质量异常'
ELSE NULL
END AS quality_anomaly,
-- 报工异常:长时间无报工
CASE
WHEN raw_status = 'STARTED'
AND last_report_time IS NOT NULL
AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '报工停滞'
WHEN raw_status = 'STARTED'
AND last_report_time IS NULL
AND elapsed_hours > 24 THEN '无报工记录'
ELSE NULL
END AS labor_anomaly,
-- 效率异常:人效过低
CASE
WHEN total_work_minutes > 0
AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN '效率偏低'
ELSE NULL
END AS efficiency_anomaly
FROM work_order_progress
)
-- =====================================================
-- 输出:工单执行进度明细
-- =====================================================
SELECT
work_order_number AS "工单号",
product_name AS "产品名称",
product_category AS "产品类别",
status_name AS "状态",
planned_qty AS "计划数量",
completed_qty AS "完成数量",
remaining_qty AS "剩余数量",
completion_rate AS "完成率(%)",
worker_count AS "参与人数",
ROUND(total_work_minutes / 60.0, 1) AS "累计工时(小时)",
elapsed_hours AS "已用时间(小时)",
-- 异常标记
COALESCE(progress_anomaly, '') ||
CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END ||
COALESCE(quality_anomaly, '') ||
CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END ||
COALESCE(labor_anomaly, '') ||
CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END ||
COALESCE(efficiency_anomaly, '') AS "异常标记",
-- 风险等级
CASE
WHEN progress_anomaly = '进度严重滞后' OR quality_anomaly IS NOT NULL THEN ''
WHEN progress_anomaly = '进度滞后' OR labor_anomaly IS NOT NULL THEN ''
WHEN efficiency_anomaly IS NOT NULL THEN ''
ELSE '-'
END AS "风险等级"
FROM anomaly_detection
ORDER BY
CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END,
CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END,
completion_rate ASC;
-- =====================================================
-- 补充查询1工单状态分布汇总
-- =====================================================
WITH status_summary AS (
SELECT
CASE status
WHEN 'OPEN' THEN '待生产'
WHEN 'STARTED' THEN '生产中'
WHEN 'CLOSED' THEN '已完成'
ELSE '未知'
END AS status_name,
status AS raw_status,
COUNT(*) AS order_count,
SUM(planned_qty) AS total_planned,
SUM(completed_qty) AS total_completed
FROM fact_work_order
GROUP BY status
)
SELECT
status_name AS "状态",
order_count AS "工单数",
ROUND(order_count * 100.0 / SUM(order_count) OVER (), 1) AS "占比(%)",
ROUND(total_planned, 0) AS "计划总量",
ROUND(total_completed, 0) AS "完成总量",
CASE WHEN total_planned > 0
THEN ROUND(total_completed * 100.0 / total_planned, 1)
ELSE 0 END AS "完成率(%)"
FROM status_summary
ORDER BY
CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END;
-- =====================================================
-- 补充查询2异常工单预警清单
-- =====================================================
WITH work_order_anomaly AS (
SELECT
wo.work_order_number,
p.product_name,
wo.status,
wo.planned_qty,
wo.completed_qty,
CASE WHEN wo.planned_qty > 0
THEN ROUND(wo.completed_qty * 100.0 / wo.planned_qty, 1)
ELSE 0 END AS completion_rate,
ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1) AS elapsed_hours,
(SELECT MAX(lr.event_time_utc::timestamp)
FROM fact_labor_report lr
WHERE lr.work_order_number = wo.work_order_number) AS last_report_time
FROM fact_work_order wo
LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't'
WHERE wo.status = 'STARTED'
)
SELECT
work_order_number AS "工单号",
product_name AS "产品",
ROUND(planned_qty, 0) AS "计划数量",
ROUND(completed_qty, 0) AS "完成数量",
completion_rate AS "完成率(%)",
elapsed_hours AS "已用时间(小时)",
CASE
WHEN elapsed_hours > 48 AND completion_rate < 20 THEN '进度严重滞后'
WHEN elapsed_hours > 24 AND completion_rate < 30 THEN '进度滞后'
WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN '无报工记录'
WHEN last_report_time IS NOT NULL
AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '报工停滞'
ELSE '正常'
END AS "异常类型",
CASE
WHEN elapsed_hours > 48 AND completion_rate < 20 THEN '立即跟进,排查生产瓶颈'
WHEN elapsed_hours > 24 AND completion_rate < 30 THEN '关注进度,协调资源'
WHEN last_report_time IS NULL AND elapsed_hours > 24 THEN '确认工单是否已开工'
WHEN last_report_time IS NOT NULL
AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN '跟进报工情况'
ELSE '-'
END AS "处理建议"
FROM work_order_anomaly
WHERE elapsed_hours > 24 AND completion_rate < 50
OR (last_report_time IS NULL AND elapsed_hours > 24)
OR (last_report_time IS NOT NULL
AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24)
ORDER BY
CASE
WHEN elapsed_hours > 48 AND completion_rate < 20 THEN 1
WHEN elapsed_hours > 24 AND completion_rate < 30 THEN 2
ELSE 3
END,
completion_rate ASC;
-- =====================================================
-- 补充查询3产品类别执行进度汇总
-- =====================================================
SELECT
p.product_category AS "产品类别",
COUNT(*) AS "工单数",
SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS "待生产",
SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS "生产中",
SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS "已完成",
ROUND(SUM(wo.planned_qty), 0) AS "计划总量",
ROUND(SUM(wo.completed_qty), 0) AS "完成总量",
CASE WHEN SUM(wo.planned_qty) > 0
THEN ROUND(SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty), 1)
ELSE 0 END AS "整体完成率(%)"
FROM fact_work_order wo
LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't'
GROUP BY p.product_category
ORDER BY "工单数" DESC;
-- =====================================================
-- 补充查询4工单执行时间线
-- =====================================================
WITH timeline AS (
SELECT
wo.work_order_number,
p.product_name,
wo.status,
wo.event_time_utc::timestamp AS start_time,
wo.last_updated_utc::timestamp AS last_update,
CASE WHEN wo.status = 'CLOSED'
THEN ROUND(EXTRACT(EPOCH FROM (wo.last_updated_utc::timestamp - wo.event_time_utc::timestamp)) / 3600, 1)
ELSE ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600, 1)
END AS duration_hours,
wo.planned_qty,
wo.completed_qty
FROM fact_work_order wo
LEFT JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't'
)
SELECT
work_order_number AS "工单号",
product_name AS "产品",
CASE status
WHEN 'OPEN' THEN '待生产'
WHEN 'STARTED' THEN '生产中'
WHEN 'CLOSED' THEN '已完成'
END AS "状态",
TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI') AS "开始时间",
TO_CHAR(last_update, 'YYYY-MM-DD HH24:MI') AS "最后更新",
duration_hours AS "持续时间(小时)",
ROUND(planned_qty, 0) AS "计划数量",
ROUND(completed_qty, 0) AS "完成数量",
CASE WHEN planned_qty > 0
THEN ROUND(completed_qty * 100.0 / planned_qty, 1)
ELSE 0 END AS "完成率(%)"
FROM timeline
ORDER BY start_time DESC
LIMIT 50;
-- =====================================================
-- 补充查询5实时生产看板汇总
-- =====================================================
WITH current_stats AS (
SELECT
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders,
SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders,
SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders,
SUM(planned_qty) AS total_planned,
SUM(completed_qty) AS total_completed
FROM fact_work_order
),
anomaly_stats AS (
SELECT
COUNT(*) AS anomaly_count
FROM fact_work_order wo
WHERE wo.status = 'STARTED'
AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wo.event_time_utc::timestamp)) / 3600 > 24
AND wo.completed_qty * 100.0 / NULLIF(wo.planned_qty, 0) < 30
),
today_stats AS (
SELECT
COUNT(*) AS today_completed
FROM fact_work_order
WHERE status = 'CLOSED'
AND last_updated_utc::date = CURRENT_DATE
)
SELECT
'总工单数' AS "指标",
cs.total_orders::text AS "数值",
'-' AS "状态"
FROM current_stats cs
UNION ALL
SELECT
'待生产',
cs.open_orders::text,
CASE WHEN cs.open_orders > 20 THEN '积压' ELSE '正常' END
FROM current_stats cs
UNION ALL
SELECT
'生产中',
cs.started_orders::text,
'进行中'
FROM current_stats cs
UNION ALL
SELECT
'已完成',
cs.closed_orders::text,
'正常'
FROM current_stats cs
UNION ALL
SELECT
'整体完成率',
ROUND(cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0), 1)::text || '%',
CASE
WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 80 THEN '良好'
WHEN cs.total_completed * 100.0 / NULLIF(cs.total_planned, 0) >= 50 THEN '正常'
ELSE '偏低'
END
FROM current_stats cs
UNION ALL
SELECT
'异常工单数',
ans.anomaly_count::text,
CASE WHEN ans.anomaly_count > 5 THEN '需关注' ELSE '正常' END
FROM anomaly_stats ans
UNION ALL
SELECT
'今日完成',
ts.today_completed::text,
'-'
FROM today_stats ts;

View File

@@ -1,426 +0,0 @@
-- =====================================================
-- 指标趋势分析与拐点预警SQL
-- Metric Trend Analysis and Turning Point Warning
-- 数据库: PostgreSQL
-- =====================================================
-- =====================================================
-- 1. 日度指标基础数据
-- =====================================================
WITH daily_metrics AS (
SELECT
DATE(lr.event_time_utc::timestamp) AS metric_date,
COUNT(DISTINCT lr.worker_name) AS worker_count,
SUM(lr.duration_minutes) / 60.0 AS total_hours,
SUM(lr.report_qty) AS total_output,
CASE WHEN SUM(lr.duration_minutes) > 0
THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0)
ELSE 0 END AS hourly_efficiency
FROM fact_labor_report lr
GROUP BY DATE(lr.event_time_utc::timestamp)
),
daily_quality AS (
SELECT
DATE(qi.event_time_utc::timestamp) AS metric_date,
SUM(qi.pass_qty) AS pass_qty,
SUM(qi.fail_qty) AS fail_qty,
CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0
THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty))
ELSE 0 END AS defect_rate
FROM fact_quality_inspection qi
GROUP BY DATE(qi.event_time_utc::timestamp)
),
daily_production AS (
SELECT
DATE(wo.event_time_utc::timestamp) AS metric_date,
SUM(wo.planned_qty) AS planned_qty,
SUM(wo.completed_qty) AS completed_qty,
CASE WHEN SUM(wo.planned_qty) > 0
THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty)
ELSE 0 END AS completion_rate
FROM fact_work_order wo
GROUP BY DATE(wo.event_time_utc::timestamp)
),
combined_daily AS (
SELECT
COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date,
COALESCE(dm.worker_count, 0) AS worker_count,
COALESCE(dm.total_hours, 0) AS total_hours,
COALESCE(dm.total_output, 0) AS total_output,
COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency,
COALESCE(dq.defect_rate, 0) AS defect_rate,
COALESCE(dp.completion_rate, 0) AS completion_rate
FROM daily_metrics dm
FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date
FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date
WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL
),
numbered_data AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq
FROM combined_daily
),
-- =====================================================
-- 2. 移动平均计算 (7日移动平均)
-- =====================================================
moving_avg_step1 AS (
SELECT
metric_date,
day_seq,
worker_count,
total_output,
hourly_efficiency,
defect_rate,
completion_rate,
AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency,
AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output,
AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate,
AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate,
AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x
FROM numbered_data
),
moving_avg AS (
SELECT
*,
LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency,
LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output,
LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate
FROM moving_avg_step1
),
-- =====================================================
-- 3. 简化的趋势斜率计算
-- =====================================================
slope_calc AS (
SELECT
*,
(ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency,
(ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output,
(ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect
FROM moving_avg
),
-- =====================================================
-- 4. 趋势判断与异常检测
-- =====================================================
trend_analysis AS (
SELECT
metric_date,
hourly_efficiency,
total_output,
defect_rate,
completion_rate,
ROUND(ma7_efficiency, 2) AS ma7_efficiency,
ROUND(ma7_output, 2) AS ma7_output,
ROUND(ma7_defect_rate, 2) AS ma7_defect_rate,
ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency,
ROUND(COALESCE(slope_output, 0), 4) AS slope_output,
ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect,
prev_ma7_efficiency,
prev_ma7_output,
prev_ma7_defect_rate,
CASE
WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING'
WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING'
ELSE 'STABLE'
END AS efficiency_trend,
CASE
WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING'
WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING'
ELSE 'STABLE'
END AS output_trend,
CASE
WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING'
WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING'
ELSE 'STABLE'
END AS defect_trend,
CASE
WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY'
ELSE 'NORMAL'
END AS efficiency_status,
CASE
WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY'
ELSE 'NORMAL'
END AS output_status,
CASE
WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY'
ELSE 'NORMAL'
END AS defect_status
FROM slope_calc
)
-- =====================================================
-- 输出:日度趋势分析明细
-- =====================================================
SELECT
metric_date,
ROUND(hourly_efficiency, 2) AS hourly_output,
ma7_efficiency AS efficiency_ma7,
slope_efficiency,
efficiency_trend,
efficiency_status,
CASE
WHEN prev_ma7_efficiency IS NOT NULL
AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT'
WHEN prev_ma7_efficiency IS NOT NULL
AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT'
ELSE 'NONE'
END AS efficiency_turning_point,
ROUND(total_output, 0) AS daily_output,
ma7_output AS output_ma7,
slope_output,
output_trend,
output_status,
CASE
WHEN prev_ma7_output IS NOT NULL
AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT'
WHEN prev_ma7_output IS NOT NULL
AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT'
ELSE 'NONE'
END AS output_turning_point,
ROUND(defect_rate, 2) AS defect_rate,
ma7_defect_rate AS defect_rate_ma7,
slope_defect,
defect_trend,
defect_status,
CASE
WHEN prev_ma7_defect_rate IS NOT NULL
AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT'
WHEN prev_ma7_defect_rate IS NOT NULL
AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT'
ELSE 'NONE'
END AS defect_turning_point
FROM trend_analysis
ORDER BY metric_date DESC;
-- =====================================================
-- 补充查询1周度趋势汇总
-- =====================================================
WITH weekly_metrics AS (
SELECT
DATE_TRUNC('week', lr.event_time_utc::timestamp)::date AS week_start,
COUNT(DISTINCT lr.worker_name) AS worker_count,
SUM(lr.duration_minutes) / 60.0 AS total_hours,
SUM(lr.report_qty) AS total_output,
CASE WHEN SUM(lr.duration_minutes) > 0
THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0)
ELSE 0 END AS hourly_efficiency
FROM fact_labor_report lr
GROUP BY DATE_TRUNC('week', lr.event_time_utc::timestamp)
),
weekly_quality AS (
SELECT
DATE_TRUNC('week', qi.event_time_utc::timestamp)::date AS week_start,
SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate
FROM fact_quality_inspection qi
GROUP BY DATE_TRUNC('week', qi.event_time_utc::timestamp)
),
weekly_combined AS (
SELECT
wm.week_start,
wm.worker_count,
wm.total_hours,
wm.total_output,
wm.hourly_efficiency,
COALESCE(wq.defect_rate, 0) AS defect_rate
FROM weekly_metrics wm
LEFT JOIN weekly_quality wq ON wm.week_start = wq.week_start
),
weekly_with_lag AS (
SELECT
*,
LAG(hourly_efficiency, 1) OVER (ORDER BY week_start) AS prev_efficiency,
LAG(total_output, 1) OVER (ORDER BY week_start) AS prev_output,
LAG(defect_rate, 1) OVER (ORDER BY week_start) AS prev_defect_rate
FROM weekly_combined
)
SELECT
week_start,
worker_count,
ROUND(total_hours, 1) AS total_hours,
ROUND(total_output, 0) AS total_output,
ROUND(hourly_efficiency, 2) AS hourly_output,
ROUND(defect_rate, 2) AS defect_rate,
CASE
WHEN prev_efficiency IS NULL THEN 'NONE'
WHEN hourly_efficiency > prev_efficiency * 1.1 THEN 'RISING'
WHEN hourly_efficiency < prev_efficiency * 0.9 THEN 'FALLING'
ELSE 'STABLE'
END AS efficiency_trend,
CASE
WHEN prev_output IS NULL THEN 'NONE'
WHEN total_output > prev_output * 1.1 THEN 'RISING'
WHEN total_output < prev_output * 0.9 THEN 'FALLING'
ELSE 'STABLE'
END AS output_trend,
CASE
WHEN prev_defect_rate IS NULL THEN 'NONE'
WHEN defect_rate > prev_defect_rate * 1.2 THEN 'RISING'
WHEN defect_rate < prev_defect_rate * 0.8 THEN 'FALLING'
ELSE 'STABLE'
END AS defect_trend,
ROUND((hourly_efficiency - COALESCE(prev_efficiency, hourly_efficiency)) / NULLIF(prev_efficiency, 0) * 100, 1) AS efficiency_wow_pct
FROM weekly_with_lag
ORDER BY week_start DESC;
-- =====================================================
-- 补充查询2指标趋势汇总报告
-- =====================================================
WITH
recent_data AS (
SELECT
DATE(lr.event_time_utc::timestamp) AS metric_date,
SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency,
SUM(lr.report_qty) AS total_output
FROM fact_labor_report lr
WHERE lr.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days'
GROUP BY DATE(lr.event_time_utc::timestamp)
),
recent_quality AS (
SELECT
DATE(qi.event_time_utc::timestamp) AS metric_date,
SUM(qi.fail_qty) * 100.0 / NULLIF(SUM(qi.pass_qty) + SUM(qi.fail_qty), 0) AS defect_rate
FROM fact_quality_inspection qi
WHERE qi.event_time_utc::timestamp >= CURRENT_DATE - INTERVAL '14 days'
GROUP BY DATE(qi.event_time_utc::timestamp)
),
period_stats AS (
SELECT
AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_7d,
AVG(CASE WHEN rd.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_7d,
AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.hourly_efficiency END) AS avg_eff_prev7d,
AVG(CASE WHEN rd.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rd.total_output END) AS avg_output_prev7d
FROM recent_data rd
),
quality_stats AS (
SELECT
AVG(CASE WHEN rq.metric_date >= CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_7d,
AVG(CASE WHEN rq.metric_date < CURRENT_DATE - INTERVAL '7 days' THEN rq.defect_rate END) AS avg_defect_prev7d
FROM recent_quality rq
)
SELECT
'efficiency_per_hour' AS metric_name,
ROUND(ps.avg_eff_7d, 2) AS last_7d_avg,
ROUND(ps.avg_eff_prev7d, 2) AS prev_7d_avg,
ROUND((ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) * 100, 1) AS change_rate_pct,
CASE
WHEN ps.avg_eff_7d > ps.avg_eff_prev7d * 1.05 THEN 'RISING'
WHEN ps.avg_eff_7d < ps.avg_eff_prev7d * 0.95 THEN 'FALLING'
ELSE 'STABLE'
END AS trend,
CASE
WHEN ABS(ps.avg_eff_7d - ps.avg_eff_prev7d) / NULLIF(ps.avg_eff_prev7d, 0) > 0.2 THEN 'ANOMALY'
ELSE 'NORMAL'
END AS warning
FROM period_stats ps
UNION ALL
SELECT
'daily_output',
ROUND(ps.avg_output_7d, 0),
ROUND(ps.avg_output_prev7d, 0),
ROUND((ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) * 100, 1),
CASE
WHEN ps.avg_output_7d > ps.avg_output_prev7d * 1.05 THEN 'RISING'
WHEN ps.avg_output_7d < ps.avg_output_prev7d * 0.95 THEN 'FALLING'
ELSE 'STABLE'
END,
CASE
WHEN ABS(ps.avg_output_7d - ps.avg_output_prev7d) / NULLIF(ps.avg_output_prev7d, 0) > 0.2 THEN 'ANOMALY'
ELSE 'NORMAL'
END
FROM period_stats ps
UNION ALL
SELECT
'defect_rate_pct',
ROUND(qs.avg_defect_7d, 2),
ROUND(qs.avg_defect_prev7d, 2),
ROUND((qs.avg_defect_7d - qs.avg_defect_prev7d) / NULLIF(qs.avg_defect_prev7d, 0) * 100, 1),
CASE
WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.1 THEN 'RISING'
WHEN qs.avg_defect_7d < qs.avg_defect_prev7d * 0.9 THEN 'FALLING'
ELSE 'STABLE'
END,
CASE
WHEN qs.avg_defect_7d > 8 THEN 'THRESHOLD_EXCEEDED'
WHEN qs.avg_defect_7d > qs.avg_defect_prev7d * 1.3 THEN 'ANOMALY_RISING'
ELSE 'NORMAL'
END
FROM quality_stats qs;
-- =====================================================
-- 补充查询3拐点预警汇总
-- =====================================================
WITH daily_eff AS (
SELECT
DATE(lr.event_time_utc::timestamp) AS metric_date,
SUM(lr.report_qty) / NULLIF(SUM(lr.duration_minutes) / 60.0, 0) AS hourly_efficiency
FROM fact_labor_report lr
GROUP BY DATE(lr.event_time_utc::timestamp)
),
with_ma_step1 AS (
SELECT
metric_date,
hourly_efficiency,
AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7
FROM daily_eff
),
with_ma AS (
SELECT
metric_date,
hourly_efficiency,
ma7,
LAG(ma7, 1) OVER (ORDER BY metric_date) AS prev_ma7,
LAG(ma7, 2) OVER (ORDER BY metric_date) AS prev2_ma7
FROM with_ma_step1
),
turning_points AS (
SELECT
metric_date,
hourly_efficiency,
ma7,
prev_ma7,
prev2_ma7,
CASE
WHEN prev2_ma7 IS NOT NULL
AND prev_ma7 < prev2_ma7 AND ma7 > prev_ma7 THEN 'UPWARD'
WHEN prev2_ma7 IS NOT NULL
AND prev_ma7 > prev2_ma7 AND ma7 < prev_ma7 THEN 'DOWNWARD'
ELSE NULL
END AS turning_type
FROM with_ma
)
SELECT
metric_date,
ROUND(hourly_efficiency, 2) AS daily_efficiency,
ROUND(ma7, 2) AS ma7_line,
turning_type,
CASE
WHEN turning_type = 'UPWARD' THEN 'MAINTAIN_CURRENT_MEASURES'
WHEN turning_type = 'DOWNWARD' THEN 'INVESTIGATE_AND_IMPROVE'
ELSE 'NONE'
END AS recommendation
FROM turning_points
WHERE turning_type IS NOT NULL
ORDER BY metric_date DESC
LIMIT 10;

View File

@@ -1,154 +0,0 @@
-- 订单延迟预警分析(最终优化版)
-- Order Delay Warning Analysis
WITH
-- 1. 历史生产周期统计(全局平均,仅统计已完成工单)
production_cycle_stats AS (
SELECT
COALESCE(
AVG(
GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))
),
0
) AS avg_production_days
FROM fact_work_order
WHERE status = 'CLOSED'
AND last_updated_utc >= event_time_utc
),
-- 2. 物流延误统计(按客户维度)
logistics_delay_stats AS (
SELECT
customer_id,
AVG(
GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))
) AS avg_logistics_delay_days,
SUM(
CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3
THEN 1 ELSE 0 END
) AS delay_count
FROM fact_sales_shipment
WHERE doc_date_utc IS NOT NULL
AND event_time_utc IS NOT NULL
GROUP BY customer_id
),
-- 3. 质量问题统计(全局平均)
quality_issue_stats AS (
SELECT
COALESCE(
ROUND(
SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0),
2
),
0
) AS defect_rate_pct
FROM fact_quality_inspection
WHERE pass_qty IS NOT NULL
AND fail_qty IS NOT NULL
),
-- 4. 报废率(全局)
scrap_stats AS (
SELECT
COALESCE(
(SELECT COUNT(*) FROM fact_scrap) * 100.0 /
NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0),
0
) AS scrap_rate_pct
),
-- 5. 当前生产滞后风险(全局)
active_work_order_risk AS (
SELECT
COUNT(*) AS active_wo_count,
COALESCE(
SUM(
CASE
WHEN planned_qty > 0
AND (completed_qty / planned_qty) < 0.3
AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7
THEN 1
ELSE 0
END
),
0
) AS lagging_wo_count
FROM fact_work_order
WHERE status IN ('OPEN', 'STARTED')
),
-- 6. 合并全局指标为单行
global_metrics AS (
SELECT
pcs.avg_production_days,
qis.defect_rate_pct,
ss.scrap_rate_pct,
awr.active_wo_count,
awr.lagging_wo_count
FROM production_cycle_stats pcs,
quality_issue_stats qis,
scrap_stats ss,
active_work_order_risk awr
)
-- 主查询
SELECT
so.sales_order_number AS order_number,
c.customer_name AS customer_name,
so.order_date_utc::DATE AS order_date,
so.deal_amount AS order_amount,
so.payment_status AS payment_status,
-- 风险指标
ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days,
ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days,
COALESCE(lds.delay_count, 0)::INT AS historical_delay_count,
ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct,
ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct,
gm.active_wo_count::INT AS active_work_order_count,
gm.lagging_wo_count::INT AS lagging_work_order_count,
-- 延迟概率计算
ROUND(
LEAST(100, GREATEST(0,
LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) +
LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) +
LEAST(25, gm.defect_rate_pct * 2.5) +
LEAST(20, gm.lagging_wo_count * 10)
))::NUMERIC, 1
) AS delay_probability_pct,
-- 预警等级
CASE
WHEN (
LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) +
LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) +
LEAST(25, gm.defect_rate_pct * 2.5) +
LEAST(20, gm.lagging_wo_count * 10)
) >= 60 THEN 'RED'
WHEN (
LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) +
LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) +
LEAST(25, gm.defect_rate_pct * 2.5) +
LEAST(20, gm.lagging_wo_count * 10)
) >= 30 THEN 'YELLOW'
ELSE 'GREEN'
END AS warning_level,
-- 主要风险因素
CASE
WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED'
WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK'
WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE'
WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES'
ELSE 'NORMAL'
END AS primary_risk_factor
FROM fact_sales_order so
LEFT JOIN dim_customer c
ON so.customer_id = c.customer_id
AND c.is_current = 't'
CROSS JOIN global_metrics gm
LEFT JOIN logistics_delay_stats lds
ON so.customer_id = lds.customer_id
ORDER BY delay_probability_pct DESC, so.order_date_utc DESC;

View File

@@ -1,111 +0,0 @@
import requests
import json
def main(**kwargs) -> dict:
"""
函数节点的入口函数
Args:
**kwargs: 从前端配置的参数传入,可通过变量引用获取工作流上下文
Returns:
dict: 返回结果将作为节点输出,可被后续节点引用
"""
# 从 kwargs 获取参数,如果没有提供则使用默认值
url = kwargs.get('url', 'http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema')
# 请求头从kwargs获取或使用默认值
authorization_token = kwargs.get('authorization_token', 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjAxNzZiNjEyLTc2YWItNDdhMS1iYTRiLTdjNWU2ZTMxNDlmZCJ9.w7aOfDJDHtA4bwNKIvUVK2cf1yO_2F27d_eYuos-p1-XGrSQOX0D4ny0b8Js36MhXwBnF4GDcy8V1VobEN6zBA')
headers = {
'Authorization': authorization_token
}
# 请求体参数从kwargs获取或使用默认值
payload_id = kwargs.get('id', '2006300000000000001')
business_name = kwargs.get('businessName', 'OrderDelayWarningAnalysis')
business_description = kwargs.get('businessDescription', '订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级')
datasource_id = kwargs.get('datasourceId', '57')
sql_template = kwargs.get('sqlTemplate', 'WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = \'CLOSED\' AND last_updated_utc &gt;= 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) &gt; 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 &gt; 0 AND (completed_qty / planned_qty) 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN (\'OPEN\', \'STARTED\')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) &gt;= 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)) &gt;= 30 THEN \'YELLOW\' ELSE \'GREEN\' END AS warning_level, CASE WHEN gm.lagging_wo_count &gt;= 2 THEN \'PRODUCTION_SEVERELY_DELAYED\' WHEN COALESCE(lds.avg_logistics_delay_days, 0) &gt; 5 THEN \'HIGH_LOGISTICS_DELAY_RISK\' WHEN gm.avg_production_days &gt; 15 THEN \'LONG_PRODUCTION_CYCLE\' WHEN gm.defect_rate_pct &gt; 10 THEN \'QUALITY_ISSUES\' ELSE \'NORMAL\' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = \'t\' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id WHERE EXTRACT(YEAR FROM so.order_date_utc) = 2025 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 30')
parameters = kwargs.get('parameters', {})
# 请求体从kwargs获取或使用默认值
payload = {
"id": payload_id,
"businessName": business_name,
"businessDescription": business_description,
"datasourceId": datasource_id,
"sqlTemplate": sql_template,
"parameters": parameters
}
# 超时时间从kwargs获取或使用默认值
timeout = kwargs.get('timeout', 30)
try:
# 发送POST请求
response = requests.post(
url=url,
headers=headers,
json=payload,
timeout=timeout
)
# 检查HTTP响应状态码
response.raise_for_status()
# 解析响应结果
result = response.json()
# 返回接口调用结果
return {
"status": "success",
"status_code": response.status_code,
"result": result
}
except requests.exceptions.Timeout:
return {
"status": "error",
"error_type": "timeout",
"message": f"接口调用超时({timeout}秒)",
"result": None
}
except requests.exceptions.ConnectionError:
return {
"status": "error",
"error_type": "connection_error",
"message": "无法连接到目标服务器",
"result": None
}
except requests.exceptions.HTTPError as e:
return {
"status": "error",
"error_type": "http_error",
"status_code": response.status_code if 'response' in locals() else None,
"message": f"HTTP请求错误: {str(e)}",
"response_text": response.text if 'response' in locals() else "",
"result": None
}
except json.JSONDecodeError:
return {
"status": "error",
"error_type": "json_decode_error",
"message": "接口返回的不是有效的JSON格式",
"response_text": response.text if 'response' in locals() else "",
"result": None
}
except Exception as e:
return {
"status": "error",
"error_type": "unknown_error",
"message": f"接口调用失败: {str(e)}",
"result": None
}
if __name__ == "__main__":
result = main()
print(json.dumps(result, ensure_ascii=False, indent=2))

View File

@@ -1,138 +0,0 @@
# lzwcai-mcpskills-mfg-data-agent (制造业数据智能体)
一个基于 MCP (Model Context Protocol) 的制造业数据智能体服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志:详细的操作日志记录
- 🌐 中文支持:工具名称自动转换为拼音
- 🏭 制造业场景:专注于制造业数据分析与智能决策
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcpskills-mfg-data-agent
```
### 从源码安装
```bash
git clone <repository-url>
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 <repository-url>
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

View File

@@ -1,10 +0,0 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@@ -1,154 +0,0 @@
# lzwcai-mcpskills-analyzeWorkOrder
一个基于 MCP (Model Context Protocol) 的 SQL 查询执行服务器,支持从 JSON 配置文件动态生成查询工具。
## 功能特性
- 🚀 动态工具生成:从 `businessQueries.json` 自动生成 MCP 工具
- 🔧 灵活配置:支持自定义业务查询和参数验证
- 📝 完整日志详细的操作日志记录仅输出到文件不干扰MCP通信
- 🌐 中文支持:工具名称自动转换为拼音
## 安装
### 使用 pip 安装
```bash
pip install lzwcai-mcpskills-analyzeWorkOrder
```
### 从源码安装
```bash
git clone <repository-url>
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 <repository-url>
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

View File

@@ -1,9 +0,0 @@
"""
lzwcai-mcpskills-mfg-data-agentv2 - MCP server for manufacturing data intelligence
"""
__version__ = "0.1.2"
__author__ = "lzwcai"
__all__ = []

File diff suppressed because one or more lines are too long

View File

@@ -1,148 +0,0 @@
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-09 18:44:45 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-09 18:44:46 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-09 18:44:46 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-09 18:44:46 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-09 18:44:46 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-09 18:44:46 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-09 18:44:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 18:44:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:44:47 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 18:49:19 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics
2026-01-09 18:49:19 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:49:19 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 18:49:24 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard
2026-01-09 18:49:24 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:49:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 18:49:27 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel
2026-01-09 18:49:27 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:49:27 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 18:57:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 18:57:52 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:57:52 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 18:58:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment
2026-01-09 18:58:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:58:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 18:58:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 18:58:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 18:58:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:02:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 19:02:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:02:47 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-09 19:31:56 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-09 19:31:58 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-09 19:31:58 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-09 19:31:58 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-09 19:31:58 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-09 19:31:58 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-09 19:32:01 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment
2026-01-09 19:32:01 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:32:01 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:32:04 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 19:32:04 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:32:04 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:32:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics
2026-01-09 19:32:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:32:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:32:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard
2026-01-09 19:32:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:32:12 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:32:16 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel
2026-01-09 19:32:16 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:32:16 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:32:33 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 19:32:33 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:32:34 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:34:32 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-09 19:34:32 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:34:32 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:48:17 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment
2026-01-09 19:48:17 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:48:17 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 19:57:13 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics
2026-01-09 19:57:13 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 19:57:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 20:02:14 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard
2026-01-09 20:02:14 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 20:02:14 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 20:06:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SmartCostPredictionModel
2026-01-09 20:06:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 20:06:58 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 20:23:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics
2026-01-09 20:23:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 20:23:12 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 20:26:12 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics
2026-01-09 20:26:12 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 20:26:13 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 20:38:58 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics
2026-01-09 20:38:58 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 20:38:58 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-09 20:43:32 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-09 20:43:33 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-09 20:43:33 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-09 20:43:33 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-09 20:43:33 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-09 20:43:33 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-09 20:43:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics
2026-01-09 20:43:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-09 20:43:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:313] - ============================================================
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:316] - ============================================================
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID:
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:324] - ============================================================
2026-01-16 12:40:03 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接...
2026-01-16 12:40:04 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-01-16 12:40:04 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2026-01-16 12:40:04 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置
2026-01-16 12:40:04 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条
2026-01-16 12:40:04 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具
2026-01-16 12:40:05 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplierEvaluationAndSmartReplenishment
2026-01-16 12:40:05 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:40:05 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:40:06 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesBIIntelligentAnalyticsPlatform
2026-01-16 12:40:06 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:40:06 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:40:07 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SalesPerformanceIntelligentStatistics
2026-01-16 12:40:07 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:40:07 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:40:08 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: FinancialAnalyticsDashboard
2026-01-16 12:40:08 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:40:09 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功
2026-01-16 12:40:11 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: HumanResourcesAnalytics
2026-01-16 12:40:11 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API...
2026-01-16 12:40:11 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功

View File

@@ -1,373 +0,0 @@
from pathlib import Path
from typing import Any
import asyncio
import logging
# 支持直接运行和模块导入两种方式
try:
from .utils import load_json, generate_tool_name, generate_input_schema
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
from .utils.logger_config import logger_config
except ImportError:
from utils import load_json, generate_tool_name, generate_input_schema
from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
from utils.logger_config import logger_config
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
import mcp.types as types
# 初始化 MCP 专用日志器
mcp_logger = logger_config.setup_mcp_logging()
# ========== 数据源配置 ==========
# 数据源类型常量
DATA_SOURCE_API = "api" # 仅使用API数据
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
# 默认数据源(可修改)
DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL
# ================================
def get_queries():
"""
获取业务查询配置
Returns:
list: 包含所有业务查询配置的列表
"""
try:
# 获取当前文件所在目录
current_dir = Path(__file__).parent
# 构建 businessQueries.json 的路径
json_path = current_dir / "businessQueries.json"
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
# 使用 load_json 方法读取 JSON 文件
queries = load_json(json_path)
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
return queries
except Exception as e:
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
raise
def generate_tool_schema_from_query(query: dict) -> types.Tool:
"""
根据查询配置生成 MCP 工具模式
Args:
query: 单个查询配置字典
Returns:
types.Tool: MCP 工具对象
"""
try:
# 获取参数定义并生成 inputSchema
parameters = query.get('parameters', {})
input_schema = generate_input_schema(parameters)
# 生成工具名称(格式: tool_拼音_id
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
# 构建工具描述,包含业务名称和业务描述
description = f"{query['businessName']}: {query['businessDescription']}"
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
return types.Tool(
name=tool_name,
description=description,
inputSchema=input_schema
)
except Exception as e:
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
raise
# 创建 MCP 服务器实例
server = Server("lzwcai-mcpskills-mfg-data-agentv2")
# 缓存查询配置,避免重复加载
_queries_cache = None
async def get_queries_cache(source: str = None):
"""
获取或初始化查询配置缓存
Args:
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE
- "api": 仅使用API数据
- "local": 仅使用本地JSON数据
- "both": 合并本地和API数据
Returns:
查询配置列表
"""
global _queries_cache
if _queries_cache is None:
source = source or DEFAULT_DATA_SOURCE
mcp_logger.info(f"初始化查询配置(数据源: {source}...")
if source == DATA_SOURCE_LOCAL:
_queries_cache = get_queries()
mcp_logger.info(f"本地配置: {len(_queries_cache)}")
elif source == DATA_SOURCE_API:
try:
_queries_cache = await call_third_party_api()
mcp_logger.info(f"API配置: {len(_queries_cache)}")
mcp_logger.info(f"API配置数组: {_queries_cache}")
except Exception as e:
mcp_logger.warning(f"API获取失败降级使用本地配置: {e}")
_queries_cache = get_queries()
else: # DATA_SOURCE_BOTH
local = get_queries()
try:
api = await call_third_party_api()
except Exception as e:
mcp_logger.warning(f"API获取失败: {e}")
api = []
_queries_cache = local + api
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)}")
return _queries_cache
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
列出所有动态生成的 MCP 工具
Returns:
list[types.Tool]: 所有可用的工具列表
"""
try:
mcp_logger.info("收到列出工具请求")
queries = await get_queries_cache()
tools = []
for query in queries:
tool = generate_tool_schema_from_query(query)
tools.append(tool)
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
return tools
except Exception as e:
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
raise
@server.call_tool()
async def handle_call_tool(
name: str,
arguments: dict[str, Any] | None
) -> list[types.TextContent]:
"""
处理工具调用请求
Args:
name: 工具名称
arguments: 工具参数
Returns:
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
"""
try:
mcp_logger.info(f"收到工具调用请求: {name}")
mcp_logger.debug(f"工具参数: {arguments}")
# 获取查询配置缓存
queries = await get_queries_cache()
# 根据工具名称查找对应的 item接口配置
tool_item = None
for query in queries:
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
if tool_name == name:
tool_item = query
break
# 构建返回结果
import json
if tool_item:
request_data = {
"datasourceId": get_datasource_id(),
"businessName": tool_item.get("businessName"),
"businessDescription": tool_item.get("businessDescription"),
"sqlTemplate": tool_item.get("sqlTemplate"),
"parameters": tool_item.get("parameters"),
"testParams": arguments or {}
}
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
if arguments and arguments.get("targetDatabaseName"):
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
# 调用测试SQL API
try:
mcp_logger.info("正在调用测试SQL API...")
api_response = test_sql_with_schema(request_data)
mcp_logger.info("测试SQL API调用成功")
# 返回包含 data 字段的结果
result = {
"success": True,
"data": api_response
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
except Exception as e:
error_msg = f"调用测试SQL API失败: {str(e)}"
mcp_logger.error(error_msg, exc_info=True)
result = {
"success": False,
"error": error_msg,
"data": None
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
else:
error_msg = f"未找到工具 {name} 对应的配置"
result = {
"success": False,
"error": error_msg,
"data": None
}
result_text = json.dumps(result, ensure_ascii=False, indent=2)
mcp_logger.debug(f"工具调用结果: {result_text}")
return [
types.TextContent(
type="text",
text=result_text
)
]
except Exception as e:
error_msg = f"工具调用失败: {name}, 错误: {e}"
mcp_logger.error(error_msg, exc_info=True)
return [
types.TextContent(
type="text",
text=f"错误: {error_msg}"
)
]
async def call_third_party_api(skill_id: str = None) -> list:
"""
调用第三方API获取技能信息并返回处理后的数据
Args:
skill_id: 技能ID默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178
Returns:
处理后的查询配置列表businessQueries格式
Example:
queries = await call_third_party_api()
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
"""
try:
# 如果没有传入 skill_id则从环境变量读取
if skill_id is None:
skill_id = get_skill_id()
mcp_logger.info(f"调用第三方APIskill_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()

View File

@@ -1,35 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcpskills-mfg-data-agentv2"
version = "0.1.1"
description = "制造业数据智能体 - MCP server for manufacturing data intelligence with dynamic tool generation"
readme = "README.md"
requires-python = ">=3.13"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "sql", "manufacturing", "data", "agent", "智能体"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
"pypinyin>=0.53.0",
]
[project.scripts]
lzwcai-mcpskills-mfg-data-agentv2 = "lzwcai_mcpskills_mfg_data_agentv2.main:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcpskills_mfg_data_agentv2"]
[tool.hatch.build.targets.wheel.force-include]
"lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json" = "lzwcai_mcpskills_mfg_data_agentv2/businessQueries.json"

View File

@@ -1,169 +0,0 @@
-- =====================================================
-- 交付风险预测:延迟概率与红/黄/绿预警等级
-- 基于历史订单的生产周期、物流延误、设备故障等特征
-- =====================================================
WITH
-- 1. 全局生产特征(汇总所有工单)
global_production AS (
SELECT
COUNT(*) AS total_wo_count,
AVG(CASE WHEN planned_qty > 0 THEN completed_qty / planned_qty ELSE 0 END) AS avg_completion_rate,
SUM(CASE WHEN status IN ('OPEN', 'STARTED') THEN 1 ELSE 0 END) AS pending_wo_count,
SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_wo_count
FROM fact_work_order
),
-- 2. 全局质检特征
global_quality AS (
SELECT
COUNT(*) AS total_inspection_count,
SUM(COALESCE(pass_qty, 0)) AS total_pass_qty,
SUM(COALESCE(fail_qty, 0)) AS total_fail_qty,
CASE WHEN SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0)) > 0
THEN SUM(COALESCE(pass_qty, 0))::FLOAT / SUM(COALESCE(pass_qty, 0) + COALESCE(fail_qty, 0))
ELSE 1 END AS qc_pass_rate
FROM fact_quality_inspection
),
-- 3. 全局工序不良特征
global_operation AS (
SELECT
COUNT(*) AS total_task_count,
SUM(COALESCE(good_qty, 0)) AS total_good_qty,
SUM(COALESCE(bad_qty, 0)) AS total_bad_qty,
CASE WHEN SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0)) > 0
THEN SUM(COALESCE(bad_qty, 0))::FLOAT / SUM(COALESCE(good_qty, 0) + COALESCE(bad_qty, 0))
ELSE 0 END AS operation_defect_rate
FROM fact_operation_task
),
-- 4. 客户级别发货统计
customer_shipment AS (
SELECT
customer_id,
COUNT(*) AS shipment_count,
SUM(COALESCE(amount, 0)) AS total_shipment_amount
FROM fact_sales_shipment
GROUP BY customer_id
),
-- 5. 客户级别退货统计
customer_return AS (
SELECT
customer_id,
COUNT(*) AS return_count,
SUM(COALESCE(amount, 0)) AS total_return_amount
FROM fact_sales_return
GROUP BY customer_id
),
-- 6. 订单风险评估
order_risk AS (
SELECT
so.sales_order_id,
so.sales_order_number,
c.customer_name,
so.order_date_utc,
so.deal_amount,
so.payment_status,
-- 全局生产指标
gp.avg_completion_rate AS production_completion_rate,
gp.pending_wo_count,
gp.total_wo_count AS work_order_count,
-- 全局质检指标
gq.qc_pass_rate,
gq.total_fail_qty,
-- 全局工序指标
go.operation_defect_rate,
-- 客户级别指标
COALESCE(cs.shipment_count, 0) AS shipment_count,
COALESCE(cr.return_count, 0) AS return_count,
CASE WHEN COALESCE(cs.shipment_count, 0) > 0
THEN COALESCE(cr.return_count, 0)::FLOAT / cs.shipment_count
ELSE 0 END AS return_rate
FROM fact_sales_order so
LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = true
CROSS JOIN global_production gp
CROSS JOIN global_quality gq
CROSS JOIN global_operation go
LEFT JOIN customer_shipment cs ON so.customer_id = cs.customer_id
LEFT JOIN customer_return cr ON so.customer_id = cr.customer_id
)
-- 7. 最终输出
SELECT
sales_order_id,
sales_order_number,
customer_name,
order_date_utc,
deal_amount,
payment_status,
-- 风险特征
work_order_count,
ROUND(production_completion_rate::NUMERIC, 2) AS production_completion_rate,
pending_wo_count,
ROUND(qc_pass_rate::NUMERIC, 2) AS qc_pass_rate,
ROUND(operation_defect_rate::NUMERIC, 4) AS operation_defect_rate,
return_count,
ROUND(return_rate::NUMERIC, 4) AS return_rate,
-- 延迟概率
ROUND((
CASE WHEN production_completion_rate < 0.3 THEN 0.30
WHEN production_completion_rate < 0.5 THEN 0.20
WHEN production_completion_rate < 0.8 THEN 0.10
ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25
WHEN qc_pass_rate < 0.9 THEN 0.15
WHEN qc_pass_rate < 0.95 THEN 0.08
ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20
WHEN operation_defect_rate > 0.05 THEN 0.12
WHEN operation_defect_rate > 0.02 THEN 0.05
ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15
WHEN return_rate > 0.05 THEN 0.08
WHEN return_rate > 0.02 THEN 0.03
ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10
WHEN payment_status = 'PARTIAL' THEN 0.05
ELSE 0 END
)::NUMERIC, 2) AS delay_probability,
-- 红/黄/绿预警
CASE
WHEN (
CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END
) >= 0.50 THEN 'RED'
WHEN (
CASE WHEN production_completion_rate < 0.3 THEN 0.30 WHEN production_completion_rate < 0.5 THEN 0.20 WHEN production_completion_rate < 0.8 THEN 0.10 ELSE 0 END
+ CASE WHEN qc_pass_rate < 0.8 THEN 0.25 WHEN qc_pass_rate < 0.9 THEN 0.15 WHEN qc_pass_rate < 0.95 THEN 0.08 ELSE 0 END
+ CASE WHEN operation_defect_rate > 0.1 THEN 0.20 WHEN operation_defect_rate > 0.05 THEN 0.12 WHEN operation_defect_rate > 0.02 THEN 0.05 ELSE 0 END
+ CASE WHEN return_rate > 0.1 THEN 0.15 WHEN return_rate > 0.05 THEN 0.08 WHEN return_rate > 0.02 THEN 0.03 ELSE 0 END
+ CASE WHEN payment_status = 'UNPAID' THEN 0.10 WHEN payment_status = 'PARTIAL' THEN 0.05 ELSE 0 END
) >= 0.25 THEN 'YELLOW'
ELSE 'GREEN'
END AS risk_level,
-- 风险原因
CONCAT_WS(' | ',
CASE WHEN production_completion_rate < 0.5 THEN '生产进度滞后' END,
CASE WHEN qc_pass_rate < 0.9 THEN '质检通过率低' END,
CASE WHEN operation_defect_rate > 0.05 THEN '工序不良率高' END,
CASE WHEN return_rate > 0.05 THEN '历史退货率高' END,
CASE WHEN payment_status = 'UNPAID' THEN '未付款' END
) AS risk_reasons
FROM order_risk
ORDER BY delay_probability DESC, deal_amount DESC;

View File

@@ -1,25 +0,0 @@
"""Utils package for lzwcai_mcp_sqlexecutor"""
from .json_helper import load_json
from .name_helper import generate_tool_name
from .schema_helper import generate_input_schema, validate_input_schema
from .api_client import DataSourceAPIClient, get_skill_by_id, process_skill_response, test_sql_with_schema
from .env_config import get_database_id, get_datasource_id, get_skill_id, get_backend_base_url, get_env_config, set_env_variable
__all__ = [
'load_json',
'generate_tool_name',
'generate_input_schema',
'validate_input_schema',
'DataSourceAPIClient',
'get_skill_by_id',
'process_skill_response',
'test_sql_with_schema',
'get_database_id',
'get_datasource_id',
'get_skill_id',
'get_backend_base_url',
'get_env_config',
'set_env_variable'
]

Some files were not shown because too many files have changed in this diff Show More