```
docs: 删除项目文档和配置文件 移除主 README.md 文件,包含项目概述、安装指南和配置示例 移除 lzwcai_mcp_sqlexecutor 模块的完整文档、配置文件和初始化文件 删除 businessQueries.json 配置模板和 .python-version 版本指定文件 更新 .gitignore 文件以清理 Python 缓存文件 ```
This commit is contained in:
68
README.md
68
README.md
@@ -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
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
@@ -1 +0,0 @@
|
||||
3.13
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"""
|
||||
lzwcai-mcp-sqlexecutor - MCP server for executing business SQL queries
|
||||
"""
|
||||
|
||||
__version__ = "0.1.2"
|
||||
__author__ = "lzwcai"
|
||||
|
||||
__all__ = []
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,289 +0,0 @@
|
||||
2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||
2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
|
||||
2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
|
||||
2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
|
||||
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================
|
||||
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据:
|
||||
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: <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] - 调用第三方 API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
|
||||
2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||
2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id:
|
||||
2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
|
||||
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
Traceback (most recent call last):
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
||||
resp = self._pool.handle_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
|
||||
raise exc from None
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
|
||||
response = connection.handle_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
|
||||
raise exc
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
|
||||
stream = self._connect(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
|
||||
stream = self._network_backend.connect_tcp(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
|
||||
with map_exceptions(exc_map):
|
||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||
self.gen.throw(value)
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
||||
raise to_exc(exc) from exc
|
||||
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
|
||||
response = self.client.get(url, headers=self._get_headers())
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
||||
return self.request(
|
||||
^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
|
||||
return self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
|
||||
response = self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
|
||||
response = self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
|
||||
response = self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
|
||||
response = transport.handle_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
||||
with map_httpcore_exceptions():
|
||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||
self.gen.throw(value)
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.ConnectError: [Errno 11001] getaddrinfo failed
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
|
||||
return default_client.get_skill_by_id(skill_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
|
||||
raise Exception(error_msg)
|
||||
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||
2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具
|
||||
@@ -1,289 +0,0 @@
|
||||
2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||
2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
|
||||
2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
|
||||
2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
|
||||
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================
|
||||
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据:
|
||||
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: <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] - 调用第三方 API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
|
||||
2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||
2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id:
|
||||
2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
|
||||
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
Traceback (most recent call last):
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
||||
resp = self._pool.handle_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
|
||||
raise exc from None
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
|
||||
response = connection.handle_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
|
||||
raise exc
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
|
||||
stream = self._connect(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
|
||||
stream = self._network_backend.connect_tcp(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
|
||||
with map_exceptions(exc_map):
|
||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||
self.gen.throw(value)
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
||||
raise to_exc(exc) from exc
|
||||
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
|
||||
response = self.client.get(url, headers=self._get_headers())
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
||||
return self.request(
|
||||
^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
|
||||
return self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
|
||||
response = self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
|
||||
response = self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
|
||||
response = self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
|
||||
response = transport.handle_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
||||
with map_httpcore_exceptions():
|
||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||
self.gen.throw(value)
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.ConnectError: [Errno 11001] getaddrinfo failed
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
|
||||
return default_client.get_skill_by_id(skill_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
|
||||
raise Exception(error_msg)
|
||||
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||
2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具
|
||||
@@ -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
|
||||
@@ -1,149 +0,0 @@
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
|
||||
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
|
||||
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================
|
||||
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id:
|
||||
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
Traceback (most recent call last):
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
||||
resp = self._pool.handle_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
|
||||
raise exc from None
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
|
||||
response = connection.handle_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
|
||||
raise exc
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
|
||||
stream = self._connect(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
|
||||
stream = self._network_backend.connect_tcp(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
|
||||
with map_exceptions(exc_map):
|
||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||
self.gen.throw(value)
|
||||
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
||||
raise to_exc(exc) from exc
|
||||
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
|
||||
response = self.client.get(url, headers=self._get_headers())
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
||||
return self.request(
|
||||
^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
|
||||
return self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
|
||||
response = self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
|
||||
response = self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
|
||||
response = self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
|
||||
response = transport.handle_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
||||
with map_httpcore_exceptions():
|
||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||
self.gen.throw(value)
|
||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.ConnectError: [Errno 11001] getaddrinfo failed
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
|
||||
return default_client.get_skill_by_id(skill_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
|
||||
raise Exception(error_msg)
|
||||
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具
|
||||
@@ -1,268 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
|
||||
try:
|
||||
from .utils import load_json, generate_input_schema
|
||||
from .utils import get_skill_by_id, process_skill_response, test_sql_with_schema
|
||||
from .utils import get_skill_id, get_env_config
|
||||
from .utils.logger_config import logger_config
|
||||
except ImportError:
|
||||
from utils import load_json, generate_input_schema
|
||||
from utils import get_skill_by_id, process_skill_response, test_sql_with_schema
|
||||
from utils import get_skill_id, get_env_config
|
||||
from utils.logger_config import logger_config
|
||||
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.types as types
|
||||
|
||||
|
||||
mcp_logger = logger_config.setup_mcp_logging()
|
||||
|
||||
DATA_SOURCE_API = "api"
|
||||
DATA_SOURCE_LOCAL = "local"
|
||||
DATA_SOURCE_BOTH = "both"
|
||||
DEFAULT_DATA_SOURCE = DATA_SOURCE_API
|
||||
|
||||
|
||||
def _text_response(payload: dict[str, Any]) -> list[types.TextContent]:
|
||||
"""Build a JSON text response."""
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=json.dumps(payload, ensure_ascii=False, indent=2)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def _extract_user_id(arguments: dict[str, Any]) -> Any:
|
||||
"""Allow numeric 0, reject None and blank strings."""
|
||||
user_id = arguments.get("userId")
|
||||
if user_id is None:
|
||||
return None
|
||||
if isinstance(user_id, str) and not user_id.strip():
|
||||
return None
|
||||
return user_id
|
||||
|
||||
|
||||
def get_queries():
|
||||
"""Read local business query config."""
|
||||
try:
|
||||
current_dir = Path(__file__).parent
|
||||
json_path = current_dir / "businessQueries.json"
|
||||
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
|
||||
queries = load_json(json_path)
|
||||
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
|
||||
return queries
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
||||
"""Generate an MCP tool definition from one query config."""
|
||||
try:
|
||||
parameters = query.get("parameters", {})
|
||||
input_schema = generate_input_schema(parameters)
|
||||
tool_name = query["businessName"]
|
||||
description = f"{query['businessName']}: {query['businessDescription']}"
|
||||
|
||||
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
|
||||
|
||||
return types.Tool(
|
||||
name=tool_name,
|
||||
description=description,
|
||||
inputSchema=input_schema
|
||||
)
|
||||
except Exception as e:
|
||||
mcp_logger.error(
|
||||
f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
server = Server("lzwcai-mcp-sqlexecutor")
|
||||
_queries_cache = None
|
||||
|
||||
|
||||
async def get_queries_cache(source: str = None):
|
||||
"""Get or initialize query config cache."""
|
||||
global _queries_cache
|
||||
if _queries_cache is None:
|
||||
source = source or DEFAULT_DATA_SOURCE
|
||||
mcp_logger.info(f"初始化查询配置,数据源: {source}")
|
||||
|
||||
if source == DATA_SOURCE_LOCAL:
|
||||
_queries_cache = get_queries()
|
||||
mcp_logger.info(f"本地配置: {len(_queries_cache)} 条")
|
||||
elif source == DATA_SOURCE_API:
|
||||
try:
|
||||
_queries_cache = await call_third_party_api()
|
||||
mcp_logger.info(f"API 配置: {len(_queries_cache)} 条")
|
||||
mcp_logger.info(f"API 配置数组: {_queries_cache}")
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API 获取失败,降级使用本地配置: {e}")
|
||||
_queries_cache = get_queries()
|
||||
else:
|
||||
local = get_queries()
|
||||
try:
|
||||
api = await call_third_party_api()
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API 获取失败: {e}")
|
||||
api = []
|
||||
_queries_cache = local + api
|
||||
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条,本地 {len(local)} + API {len(api)}")
|
||||
|
||||
return _queries_cache
|
||||
|
||||
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""List all dynamically generated MCP tools."""
|
||||
try:
|
||||
mcp_logger.info("收到列出工具请求")
|
||||
queries = await get_queries_cache()
|
||||
tools = [generate_tool_schema_from_query(query) for query in queries]
|
||||
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
|
||||
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
|
||||
return tools
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str,
|
||||
arguments: dict[str, Any] | None
|
||||
) -> list[types.TextContent]:
|
||||
"""Handle MCP tool invocation."""
|
||||
try:
|
||||
mcp_logger.info(f"收到工具调用请求: {name}")
|
||||
mcp_logger.debug(f"工具参数: {arguments}")
|
||||
|
||||
queries = await get_queries_cache()
|
||||
tool_item = next((query for query in queries if query["businessName"] == name), None)
|
||||
|
||||
if not tool_item:
|
||||
return _text_response({"error": f"未找到工具 {name} 对应的配置"})
|
||||
|
||||
normalized_parameters = generate_input_schema(tool_item.get("parameters"))
|
||||
normalized_arguments = arguments or {}
|
||||
user_id = _extract_user_id(normalized_arguments)
|
||||
|
||||
if user_id is None:
|
||||
error_msg = f"工具 {name} 缺少必填参数 userId"
|
||||
mcp_logger.warning(error_msg)
|
||||
return _text_response({"error": error_msg, "required": ["userId"]})
|
||||
|
||||
request_data = {
|
||||
"datasourceId": tool_item.get("datasourceId"),
|
||||
"businessName": tool_item.get("businessName"),
|
||||
"businessDescription": tool_item.get("businessDescription"),
|
||||
"sqlTemplate": tool_item.get("sqlTemplate"),
|
||||
"parameters": normalized_parameters,
|
||||
"testParams": normalized_arguments,
|
||||
"userId": user_id
|
||||
}
|
||||
|
||||
target_database_name = normalized_arguments.get("targetDatabaseName")
|
||||
if target_database_name:
|
||||
request_data["targetDatabaseName"] = target_database_name
|
||||
mcp_logger.debug(f"添加目标数据库名称: {target_database_name}")
|
||||
|
||||
try:
|
||||
mcp_logger.info("正在调用测试 SQL API...")
|
||||
result_payload = test_sql_with_schema(request_data)
|
||||
mcp_logger.info("测试 SQL API 调用成功")
|
||||
except Exception as e:
|
||||
error_msg = f"调用测试 SQL API 失败: {str(e)}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
result_payload = {"error": error_msg}
|
||||
|
||||
mcp_logger.debug(f"工具调用结果: {result_payload}")
|
||||
return _text_response(result_payload)
|
||||
except Exception as e:
|
||||
error_msg = f"工具调用失败: {name}, 错误: {e}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
return [types.TextContent(type="text", text=f"错误: {error_msg}")]
|
||||
|
||||
|
||||
async def call_third_party_api(skill_id: str = None) -> list:
|
||||
"""Call backend API and map the response into query configs."""
|
||||
try:
|
||||
if skill_id is None:
|
||||
skill_id = get_skill_id()
|
||||
|
||||
mcp_logger.info(f"调用第三方 API,skill_id: {skill_id}")
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
mcp_logger.info(f"成功获取原始响应: {raw_result}")
|
||||
|
||||
processed_queries = process_skill_response(raw_result)
|
||||
mcp_logger.info(f"成功处理 {len(processed_queries)} 条数据")
|
||||
return processed_queries
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"API 调用失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""Async entry for the MCP server."""
|
||||
try:
|
||||
mcp_logger.info("=" * 60)
|
||||
mcp_logger.info("正在启动 MCP 服务: lzwcai-mcp-sqlexecutor")
|
||||
mcp_logger.info("版本: 0.1.0")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
env_config = get_env_config()
|
||||
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
|
||||
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
|
||||
mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
from mcp.server.stdio import stdio_server
|
||||
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
mcp_logger.info("MCP 服务已启动,等待客户端连接...")
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="lzwcai-mcp-sqlexecutor",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
mcp_logger.info("MCP 服务已关闭")
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"MCP 服务运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
"""Console entrypoint."""
|
||||
try:
|
||||
logger_config.setup_logging(
|
||||
app_name="lzwcai_mcp_sqlexecutor",
|
||||
log_level=logging.INFO,
|
||||
console_output=False
|
||||
)
|
||||
mcp_logger.info("开始运行 MCP SQL Executor 服务")
|
||||
asyncio.run(async_main())
|
||||
except KeyboardInterrupt:
|
||||
mcp_logger.info("收到中断信号,正在关闭服务...")
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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"
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
统一日志配置模块
|
||||
提供系统级别的日志配置和管理
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LoggerConfig:
|
||||
"""日志配置管理类"""
|
||||
|
||||
def __init__(self, logs_dir: str = None):
|
||||
"""初始化日志配置
|
||||
|
||||
Args:
|
||||
logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹
|
||||
"""
|
||||
# 确定日志目录
|
||||
if logs_dir:
|
||||
self.logs_dir = Path(logs_dir)
|
||||
else:
|
||||
# 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录)
|
||||
project_root = Path(__file__).parent.parent
|
||||
self.logs_dir = project_root / "logs"
|
||||
|
||||
# 创建日志目录(包括父目录)
|
||||
self.logs_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 日志格式
|
||||
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
||||
self.date_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# 从环境变量获取日志级别,默认为INFO
|
||||
self.log_level = self._get_log_level_from_env()
|
||||
|
||||
# 是否已初始化
|
||||
self._initialized = False
|
||||
|
||||
def _get_log_level_from_env(self) -> int:
|
||||
"""从环境变量获取日志级别
|
||||
|
||||
Returns:
|
||||
int: 日志级别
|
||||
"""
|
||||
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||
|
||||
# 日志级别映射
|
||||
level_mapping = {
|
||||
'DEBUG': logging.DEBUG,
|
||||
'INFO': logging.INFO,
|
||||
'WARNING': logging.WARNING,
|
||||
'WARN': logging.WARNING, # 兼容性别名
|
||||
'ERROR': logging.ERROR,
|
||||
'CRITICAL': logging.CRITICAL,
|
||||
'FATAL': logging.CRITICAL # 兼容性别名
|
||||
}
|
||||
|
||||
return level_mapping.get(log_level_str, logging.INFO)
|
||||
|
||||
def setup_logging(self,
|
||||
app_name: str = "lzwcai_mcp_sqlexecutor",
|
||||
log_level: int = logging.INFO,
|
||||
max_file_size: int = 10 * 1024 * 1024, # 10MB
|
||||
backup_count: int = 5,
|
||||
console_output: bool = True) -> logging.Logger:
|
||||
"""设置系统日志配置
|
||||
|
||||
Args:
|
||||
app_name: 应用名称,用于日志文件命名
|
||||
log_level: 日志级别
|
||||
max_file_size: 单个日志文件最大大小(字节)
|
||||
backup_count: 保留的备份文件数量
|
||||
console_output: 是否输出到控制台
|
||||
|
||||
Returns:
|
||||
logging.Logger: 配置好的根日志器
|
||||
"""
|
||||
if self._initialized:
|
||||
return logging.getLogger()
|
||||
|
||||
# 设置根日志器
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(log_level)
|
||||
|
||||
# 清除现有的处理器
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# 创建格式化器
|
||||
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||
|
||||
# 1. 主日志文件 - 按大小滚动
|
||||
main_log_file = self.logs_dir / f"{app_name}.log"
|
||||
file_handler = RotatingFileHandler(
|
||||
main_log_file,
|
||||
maxBytes=max_file_size,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(log_level)
|
||||
file_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(file_handler)
|
||||
|
||||
# 2. 错误日志文件 - 只记录ERROR及以上级别
|
||||
error_log_file = self.logs_dir / f"{app_name}_error.log"
|
||||
error_handler = RotatingFileHandler(
|
||||
error_log_file,
|
||||
maxBytes=max_file_size,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
error_handler.setLevel(logging.ERROR)
|
||||
error_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(error_handler)
|
||||
|
||||
# 3. 按日期滚动的日志文件
|
||||
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
|
||||
daily_handler = TimedRotatingFileHandler(
|
||||
daily_log_file,
|
||||
when='midnight',
|
||||
interval=1,
|
||||
backupCount=30, # 保留30天
|
||||
encoding='utf-8'
|
||||
)
|
||||
daily_handler.setLevel(log_level)
|
||||
daily_handler.setFormatter(formatter)
|
||||
daily_handler.suffix = "%Y-%m-%d"
|
||||
root_logger.addHandler(daily_handler)
|
||||
|
||||
# 4. 控制台输出
|
||||
# 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信
|
||||
if console_output:
|
||||
console_handler = logging.StreamHandler(sys.stderr)
|
||||
console_handler.setLevel(log_level)
|
||||
console_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
self.date_format
|
||||
)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
self._initialized = True
|
||||
|
||||
# 记录初始化信息
|
||||
root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}")
|
||||
root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}")
|
||||
|
||||
return root_logger
|
||||
|
||||
def get_module_logger(self, module_name: str) -> logging.Logger:
|
||||
"""获取模块专用日志器
|
||||
|
||||
Args:
|
||||
module_name: 模块名称
|
||||
|
||||
Returns:
|
||||
logging.Logger: 模块日志器
|
||||
"""
|
||||
return logging.getLogger(module_name)
|
||||
|
||||
def create_component_logger(self,
|
||||
component_name: str,
|
||||
log_file: str = None,
|
||||
log_level: int = None) -> logging.Logger:
|
||||
"""为特定组件创建独立的日志器
|
||||
|
||||
Args:
|
||||
component_name: 组件名称
|
||||
log_file: 独立日志文件名(可选)
|
||||
log_level: 日志级别(可选)
|
||||
|
||||
Returns:
|
||||
logging.Logger: 组件日志器
|
||||
"""
|
||||
logger = logging.getLogger(component_name)
|
||||
|
||||
if log_file:
|
||||
# 为组件创建独立的日志文件
|
||||
component_log_file = self.logs_dir / log_file
|
||||
handler = RotatingFileHandler(
|
||||
component_log_file,
|
||||
maxBytes=5 * 1024 * 1024, # 5MB
|
||||
backupCount=3,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
if log_level:
|
||||
handler.setLevel(log_level)
|
||||
|
||||
logger.addHandler(handler)
|
||||
logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}")
|
||||
|
||||
return logger
|
||||
|
||||
def setup_mqtt_logging(self) -> logging.Logger:
|
||||
"""设置MQTT专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: MQTT日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"mqtt_communication",
|
||||
"mqtt_communication.log",
|
||||
logging.DEBUG
|
||||
)
|
||||
|
||||
def setup_mcp_logging(self) -> logging.Logger:
|
||||
"""设置MCP专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: MCP日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"mcp_services",
|
||||
"mcp_services.log",
|
||||
logging.DEBUG
|
||||
)
|
||||
|
||||
def setup_api_logging(self) -> logging.Logger:
|
||||
"""设置API专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: API日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"api_requests",
|
||||
"api_requests.log",
|
||||
logging.INFO
|
||||
)
|
||||
|
||||
def get_logs_info(self) -> dict:
|
||||
"""获取日志系统信息
|
||||
|
||||
Returns:
|
||||
dict: 日志系统信息
|
||||
"""
|
||||
log_files = []
|
||||
if self.logs_dir.exists():
|
||||
for log_file in self.logs_dir.glob("*.log*"):
|
||||
stat = log_file.stat()
|
||||
log_files.append({
|
||||
"name": log_file.name,
|
||||
"size": stat.st_size,
|
||||
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
|
||||
})
|
||||
|
||||
return {
|
||||
"logs_directory": str(self.logs_dir),
|
||||
"initialized": self._initialized,
|
||||
"log_files": log_files,
|
||||
"total_files": len(log_files)
|
||||
}
|
||||
|
||||
def cleanup_old_logs(self, days: int = 30):
|
||||
"""清理旧日志文件
|
||||
|
||||
Args:
|
||||
days: 保留天数
|
||||
"""
|
||||
if not self.logs_dir.exists():
|
||||
return
|
||||
|
||||
from datetime import timedelta
|
||||
cutoff_time = datetime.now() - timedelta(days=days)
|
||||
|
||||
cleaned_files = []
|
||||
for log_file in self.logs_dir.glob("*.log*"):
|
||||
if log_file.stat().st_mtime < cutoff_time.timestamp():
|
||||
try:
|
||||
log_file.unlink()
|
||||
cleaned_files.append(log_file.name)
|
||||
except Exception as e:
|
||||
logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}")
|
||||
|
||||
if cleaned_files:
|
||||
logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}")
|
||||
|
||||
def set_log_level(self, level: int, logger_name: str = None):
|
||||
"""动态调整日志级别
|
||||
|
||||
Args:
|
||||
level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
"""
|
||||
if logger_name:
|
||||
logger = logging.getLogger(logger_name)
|
||||
else:
|
||||
logger = logging.getLogger()
|
||||
|
||||
old_level = logger.level
|
||||
logger.setLevel(level)
|
||||
|
||||
# 同时调整所有处理器的级别
|
||||
for handler in logger.handlers:
|
||||
if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr):
|
||||
# 不调整控制台处理器的级别,保持原有设置
|
||||
handler.setLevel(level)
|
||||
|
||||
level_name = logging.getLevelName(level)
|
||||
old_level_name = logging.getLevelName(old_level)
|
||||
target = logger_name or "根日志器"
|
||||
|
||||
logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}")
|
||||
|
||||
def set_temporary_log_level(self, level: int, logger_name: str = None):
|
||||
"""临时调整日志级别(会保存原始级别用于恢复)
|
||||
|
||||
Args:
|
||||
level: 临时日志级别
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
"""
|
||||
if not hasattr(self, '_original_levels'):
|
||||
self._original_levels = {}
|
||||
|
||||
target_name = logger_name or 'root'
|
||||
|
||||
if logger_name:
|
||||
logger = logging.getLogger(logger_name)
|
||||
else:
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 保存原始级别
|
||||
if target_name not in self._original_levels:
|
||||
self._original_levels[target_name] = logger.level
|
||||
|
||||
# 设置新级别
|
||||
self.set_log_level(level, logger_name)
|
||||
|
||||
level_name = logging.getLevelName(level)
|
||||
target = logger_name or "根日志器"
|
||||
logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)")
|
||||
|
||||
def restore_log_level(self, logger_name: str = None):
|
||||
"""恢复日志级别到调整前的状态
|
||||
|
||||
Args:
|
||||
logger_name: 指定日志器名称,None表示恢复根日志器
|
||||
"""
|
||||
if not hasattr(self, '_original_levels'):
|
||||
logging.warning("没有找到保存的原始日志级别")
|
||||
return
|
||||
|
||||
target_name = logger_name or 'root'
|
||||
|
||||
if target_name not in self._original_levels:
|
||||
logging.warning(f"没有找到 {target_name} 的原始日志级别")
|
||||
return
|
||||
|
||||
original_level = self._original_levels[target_name]
|
||||
self.set_log_level(original_level, logger_name)
|
||||
|
||||
# 清除保存的级别
|
||||
del self._original_levels[target_name]
|
||||
|
||||
target = logger_name or "根日志器"
|
||||
level_name = logging.getLevelName(original_level)
|
||||
logging.info(f"已恢复日志级别: {target} -> {level_name}")
|
||||
|
||||
def get_current_log_levels(self) -> dict:
|
||||
"""获取当前所有日志器的级别信息
|
||||
|
||||
Returns:
|
||||
dict: 日志器级别信息
|
||||
"""
|
||||
levels_info = {}
|
||||
|
||||
# 根日志器
|
||||
root_logger = logging.getLogger()
|
||||
levels_info['root'] = {
|
||||
'level': root_logger.level,
|
||||
'level_name': logging.getLevelName(root_logger.level),
|
||||
'handlers_count': len(root_logger.handlers)
|
||||
}
|
||||
|
||||
# 其他已创建的日志器
|
||||
for name, logger in logging.Logger.manager.loggerDict.items():
|
||||
if isinstance(logger, logging.Logger):
|
||||
levels_info[name] = {
|
||||
'level': logger.level,
|
||||
'level_name': logging.getLevelName(logger.level),
|
||||
'handlers_count': len(logger.handlers)
|
||||
}
|
||||
|
||||
return levels_info
|
||||
|
||||
|
||||
# 全局日志配置实例
|
||||
logger_config = LoggerConfig()
|
||||
|
||||
|
||||
def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor",
|
||||
log_level: int = logging.INFO) -> logging.Logger:
|
||||
"""系统日志初始化快捷函数
|
||||
|
||||
Args:
|
||||
app_name: 应用名称
|
||||
log_level: 日志级别
|
||||
|
||||
Returns:
|
||||
logging.Logger: 根日志器
|
||||
"""
|
||||
return logger_config.setup_logging(app_name, log_level)
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""获取日志器的快捷函数
|
||||
|
||||
Args:
|
||||
name: 日志器名称
|
||||
|
||||
Returns:
|
||||
logging.Logger: 日志器实例
|
||||
"""
|
||||
return logger_config.get_module_logger(name)
|
||||
|
||||
|
||||
def set_log_level(level: int, logger_name: str = None):
|
||||
"""动态调整日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
|
||||
Examples:
|
||||
# 调整根日志器为DEBUG级别
|
||||
set_log_level(logging.DEBUG)
|
||||
|
||||
# 调整特定模块日志器为WARNING级别
|
||||
set_log_level(logging.WARNING, "agent_ontology.core")
|
||||
"""
|
||||
logger_config.set_log_level(level, logger_name)
|
||||
|
||||
|
||||
def set_temporary_log_level(level: int, logger_name: str = None):
|
||||
"""临时调整日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
level: 临时日志级别
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
|
||||
Examples:
|
||||
# 临时调整为DEBUG级别进行调试
|
||||
set_temporary_log_level(logging.DEBUG)
|
||||
# ... 进行调试 ...
|
||||
# 恢复原始级别
|
||||
restore_log_level()
|
||||
"""
|
||||
logger_config.set_temporary_log_level(level, logger_name)
|
||||
|
||||
|
||||
def restore_log_level(logger_name: str = None):
|
||||
"""恢复日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
logger_name: 指定日志器名称,None表示恢复根日志器
|
||||
"""
|
||||
logger_config.restore_log_level(logger_name)
|
||||
|
||||
|
||||
def get_current_log_levels() -> dict:
|
||||
"""获取当前日志级别信息的快捷函数
|
||||
|
||||
Returns:
|
||||
dict: 日志器级别信息
|
||||
|
||||
Examples:
|
||||
levels = get_current_log_levels()
|
||||
print(f"根日志器级别: {levels['root']['level_name']}")
|
||||
"""
|
||||
return logger_config.get_current_log_levels()
|
||||
|
||||
|
||||
# 便捷的日志级别常量
|
||||
class LogLevel:
|
||||
"""日志级别常量类"""
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
ERROR = logging.ERROR
|
||||
CRITICAL = logging.CRITICAL
|
||||
@@ -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('-', '_')}"
|
||||
|
||||
@@ -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 验证通过"
|
||||
497
lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/uv.lock
generated
497
lzwcai_mcp_sqlexecutor/lzwcai_mcp_sqlexecutor/uv.lock
generated
@@ -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" },
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
842
lzwcai_mcp_sqlexecutor/uv.lock
generated
842
lzwcai_mcp_sqlexecutor/uv.lock
generated
@@ -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" },
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
@@ -1 +0,0 @@
|
||||
3.13
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -1,373 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
# 支持直接运行和模块导入两种方式
|
||||
try:
|
||||
from .utils import load_json, generate_tool_name, generate_input_schema
|
||||
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
||||
from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
|
||||
from .utils.logger_config import logger_config
|
||||
except ImportError:
|
||||
from utils import load_json, generate_tool_name, generate_input_schema
|
||||
from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
||||
from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
|
||||
from utils.logger_config import logger_config
|
||||
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.types as types
|
||||
|
||||
# 初始化 MCP 专用日志器
|
||||
mcp_logger = logger_config.setup_mcp_logging()
|
||||
|
||||
# ========== 数据源配置 ==========
|
||||
# 数据源类型常量
|
||||
DATA_SOURCE_API = "api" # 仅使用API数据
|
||||
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
|
||||
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
|
||||
|
||||
# 默认数据源(可修改)
|
||||
DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL
|
||||
# ================================
|
||||
|
||||
|
||||
def get_queries():
|
||||
"""
|
||||
获取业务查询配置
|
||||
|
||||
Returns:
|
||||
list: 包含所有业务查询配置的列表
|
||||
"""
|
||||
try:
|
||||
# 获取当前文件所在目录
|
||||
current_dir = Path(__file__).parent
|
||||
|
||||
# 构建 businessQueries.json 的路径
|
||||
json_path = current_dir / "businessQueries.json"
|
||||
|
||||
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
|
||||
|
||||
# 使用 load_json 方法读取 JSON 文件
|
||||
queries = load_json(json_path)
|
||||
|
||||
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
|
||||
|
||||
return queries
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
||||
"""
|
||||
根据查询配置生成 MCP 工具模式
|
||||
|
||||
Args:
|
||||
query: 单个查询配置字典
|
||||
|
||||
Returns:
|
||||
types.Tool: MCP 工具对象
|
||||
"""
|
||||
try:
|
||||
# 获取参数定义并生成 inputSchema
|
||||
parameters = query.get('parameters', {})
|
||||
input_schema = generate_input_schema(parameters)
|
||||
|
||||
# 生成工具名称(格式: tool_拼音_id)
|
||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
||||
tool_name = query['businessName']
|
||||
# 构建工具描述,包含业务名称和业务描述
|
||||
description = f"{query['businessName']}: {query['businessDescription']}"
|
||||
|
||||
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
|
||||
|
||||
return types.Tool(
|
||||
name=tool_name,
|
||||
description=description,
|
||||
inputSchema=input_schema
|
||||
)
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
# 创建 MCP 服务器实例
|
||||
server = Server("lzwcai-mcpskills-analyzeOrder")
|
||||
|
||||
# 缓存查询配置,避免重复加载
|
||||
_queries_cache = None
|
||||
|
||||
|
||||
async def get_queries_cache(source: str = None):
|
||||
"""
|
||||
获取或初始化查询配置缓存
|
||||
|
||||
Args:
|
||||
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE)
|
||||
- "api": 仅使用API数据
|
||||
- "local": 仅使用本地JSON数据
|
||||
- "both": 合并本地和API数据
|
||||
|
||||
Returns:
|
||||
查询配置列表
|
||||
"""
|
||||
global _queries_cache
|
||||
if _queries_cache is None:
|
||||
source = source or DEFAULT_DATA_SOURCE
|
||||
mcp_logger.info(f"初始化查询配置(数据源: {source})...")
|
||||
|
||||
if source == DATA_SOURCE_LOCAL:
|
||||
_queries_cache = get_queries()
|
||||
mcp_logger.info(f"本地配置: {len(_queries_cache)} 条")
|
||||
|
||||
elif source == DATA_SOURCE_API:
|
||||
try:
|
||||
_queries_cache = await call_third_party_api()
|
||||
mcp_logger.info(f"API配置: {len(_queries_cache)} 条")
|
||||
mcp_logger.info(f"API配置数组: {_queries_cache}")
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}")
|
||||
_queries_cache = get_queries()
|
||||
|
||||
else: # DATA_SOURCE_BOTH
|
||||
local = get_queries()
|
||||
try:
|
||||
api = await call_third_party_api()
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API获取失败: {e}")
|
||||
api = []
|
||||
_queries_cache = local + api
|
||||
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})")
|
||||
|
||||
return _queries_cache
|
||||
|
||||
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""
|
||||
列出所有动态生成的 MCP 工具
|
||||
|
||||
Returns:
|
||||
list[types.Tool]: 所有可用的工具列表
|
||||
"""
|
||||
try:
|
||||
mcp_logger.info("收到列出工具请求")
|
||||
|
||||
queries = await get_queries_cache()
|
||||
tools = []
|
||||
|
||||
for query in queries:
|
||||
tool = generate_tool_schema_from_query(query)
|
||||
tools.append(tool)
|
||||
|
||||
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
|
||||
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
|
||||
|
||||
return tools
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str,
|
||||
arguments: dict[str, Any] | None
|
||||
) -> list[types.TextContent]:
|
||||
"""
|
||||
处理工具调用请求
|
||||
|
||||
Args:
|
||||
name: 工具名称
|
||||
arguments: 工具参数
|
||||
|
||||
Returns:
|
||||
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
|
||||
"""
|
||||
try:
|
||||
mcp_logger.info(f"收到工具调用请求: {name}")
|
||||
mcp_logger.debug(f"工具参数: {arguments}")
|
||||
|
||||
# 获取查询配置缓存
|
||||
queries = await get_queries_cache()
|
||||
|
||||
# 根据工具名称查找对应的 item(接口配置)
|
||||
tool_item = None
|
||||
for query in queries:
|
||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
||||
tool_name = query['businessName']
|
||||
if tool_name == name:
|
||||
tool_item = query
|
||||
break
|
||||
|
||||
# 构建返回结果
|
||||
import json
|
||||
|
||||
if tool_item:
|
||||
request_data = {
|
||||
"datasourceId": get_datasource_id(),
|
||||
"businessName": tool_item.get("businessName"),
|
||||
"businessDescription": tool_item.get("businessDescription"),
|
||||
"sqlTemplate": tool_item.get("sqlTemplate"),
|
||||
"parameters": tool_item.get("parameters"),
|
||||
"testParams": arguments or {}
|
||||
}
|
||||
|
||||
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
|
||||
if arguments and arguments.get("targetDatabaseName"):
|
||||
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
|
||||
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
|
||||
|
||||
# 调用测试SQL API
|
||||
try:
|
||||
mcp_logger.info("正在调用测试SQL API...")
|
||||
api_response = test_sql_with_schema(request_data)
|
||||
mcp_logger.info("测试SQL API调用成功")
|
||||
|
||||
# 返回包含 data 字段的结果
|
||||
result = {
|
||||
"success": True,
|
||||
"data": api_response
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"调用测试SQL API失败: {str(e)}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
result = {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"data": None
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
error_msg = f"未找到工具 {name} 对应的配置"
|
||||
result = {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"data": None
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
mcp_logger.debug(f"工具调用结果: {result_text}")
|
||||
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=result_text
|
||||
)
|
||||
]
|
||||
except Exception as e:
|
||||
error_msg = f"工具调用失败: {name}, 错误: {e}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=f"错误: {error_msg}"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def call_third_party_api(skill_id: str = None) -> list:
|
||||
"""
|
||||
调用第三方API获取技能信息并返回处理后的数据
|
||||
|
||||
Args:
|
||||
skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178)
|
||||
|
||||
Returns:
|
||||
处理后的查询配置列表(businessQueries格式)
|
||||
|
||||
Example:
|
||||
queries = await call_third_party_api()
|
||||
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
|
||||
"""
|
||||
try:
|
||||
# 如果没有传入 skill_id,则从环境变量读取
|
||||
if skill_id is None:
|
||||
skill_id = get_skill_id()
|
||||
|
||||
mcp_logger.info(f"调用第三方API,skill_id: {skill_id}")
|
||||
|
||||
# 获取原始数据
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
|
||||
mcp_logger.info(f"成功{raw_result}")
|
||||
|
||||
# 处理并返回
|
||||
processed_queries = process_skill_response(raw_result)
|
||||
|
||||
mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据")
|
||||
return processed_queries
|
||||
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"API调用失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""MCP 服务器异步主函数"""
|
||||
try:
|
||||
mcp_logger.info("=" * 60)
|
||||
mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder")
|
||||
mcp_logger.info("版本: 0.1.0")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
# 输出环境配置信息
|
||||
env_config = get_env_config()
|
||||
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
|
||||
mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}")
|
||||
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
|
||||
mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
from mcp.server.stdio import stdio_server
|
||||
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
mcp_logger.info("MCP 服务器已启动,等待客户端连接...")
|
||||
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="lzwcai-mcpskills-analyzeOrder",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
mcp_logger.info("MCP 服务器已关闭")
|
||||
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
"""入口点函数(用于 console_scripts)"""
|
||||
try:
|
||||
# 初始化系统日志
|
||||
# MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信
|
||||
logger_config.setup_logging(
|
||||
app_name="lzwcai_mcp_sqlexecutor",
|
||||
log_level=logging.INFO,
|
||||
console_output=False # 禁用控制台输出
|
||||
)
|
||||
|
||||
mcp_logger.info("开始运行 MCP SQL Executor 服务器")
|
||||
asyncio.run(async_main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
mcp_logger.info("收到中断信号,正在关闭服务器...")
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
统一日志配置模块
|
||||
提供系统级别的日志配置和管理
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LoggerConfig:
|
||||
"""日志配置管理类"""
|
||||
|
||||
def __init__(self, logs_dir: str = None):
|
||||
"""初始化日志配置
|
||||
|
||||
Args:
|
||||
logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹
|
||||
"""
|
||||
# 确定日志目录
|
||||
if logs_dir:
|
||||
self.logs_dir = Path(logs_dir)
|
||||
else:
|
||||
# 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录)
|
||||
project_root = Path(__file__).parent.parent
|
||||
self.logs_dir = project_root / "logs"
|
||||
|
||||
# 创建日志目录
|
||||
self.logs_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 日志格式
|
||||
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
||||
self.date_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# 从环境变量获取日志级别,默认为INFO
|
||||
self.log_level = self._get_log_level_from_env()
|
||||
|
||||
# 是否已初始化
|
||||
self._initialized = False
|
||||
|
||||
def _get_log_level_from_env(self) -> int:
|
||||
"""从环境变量获取日志级别
|
||||
|
||||
Returns:
|
||||
int: 日志级别
|
||||
"""
|
||||
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||
|
||||
# 日志级别映射
|
||||
level_mapping = {
|
||||
'DEBUG': logging.DEBUG,
|
||||
'INFO': logging.INFO,
|
||||
'WARNING': logging.WARNING,
|
||||
'WARN': logging.WARNING, # 兼容性别名
|
||||
'ERROR': logging.ERROR,
|
||||
'CRITICAL': logging.CRITICAL,
|
||||
'FATAL': logging.CRITICAL # 兼容性别名
|
||||
}
|
||||
|
||||
return level_mapping.get(log_level_str, logging.INFO)
|
||||
|
||||
def setup_logging(self,
|
||||
app_name: str = "lzwcai_mcp_sqlexecutor",
|
||||
log_level: int = logging.INFO,
|
||||
max_file_size: int = 10 * 1024 * 1024, # 10MB
|
||||
backup_count: int = 5,
|
||||
console_output: bool = True) -> logging.Logger:
|
||||
"""设置系统日志配置
|
||||
|
||||
Args:
|
||||
app_name: 应用名称,用于日志文件命名
|
||||
log_level: 日志级别
|
||||
max_file_size: 单个日志文件最大大小(字节)
|
||||
backup_count: 保留的备份文件数量
|
||||
console_output: 是否输出到控制台
|
||||
|
||||
Returns:
|
||||
logging.Logger: 配置好的根日志器
|
||||
"""
|
||||
if self._initialized:
|
||||
return logging.getLogger()
|
||||
|
||||
# 设置根日志器
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(log_level)
|
||||
|
||||
# 清除现有的处理器
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# 创建格式化器
|
||||
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||
|
||||
# 1. 主日志文件 - 按大小滚动
|
||||
main_log_file = self.logs_dir / f"{app_name}.log"
|
||||
file_handler = RotatingFileHandler(
|
||||
main_log_file,
|
||||
maxBytes=max_file_size,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(log_level)
|
||||
file_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(file_handler)
|
||||
|
||||
# 2. 错误日志文件 - 只记录ERROR及以上级别
|
||||
error_log_file = self.logs_dir / f"{app_name}_error.log"
|
||||
error_handler = RotatingFileHandler(
|
||||
error_log_file,
|
||||
maxBytes=max_file_size,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
error_handler.setLevel(logging.ERROR)
|
||||
error_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(error_handler)
|
||||
|
||||
# 3. 按日期滚动的日志文件
|
||||
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
|
||||
daily_handler = TimedRotatingFileHandler(
|
||||
daily_log_file,
|
||||
when='midnight',
|
||||
interval=1,
|
||||
backupCount=30, # 保留30天
|
||||
encoding='utf-8'
|
||||
)
|
||||
daily_handler.setLevel(log_level)
|
||||
daily_handler.setFormatter(formatter)
|
||||
daily_handler.suffix = "%Y-%m-%d"
|
||||
root_logger.addHandler(daily_handler)
|
||||
|
||||
# 4. 控制台输出
|
||||
# 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信
|
||||
if console_output:
|
||||
console_handler = logging.StreamHandler(sys.stderr)
|
||||
console_handler.setLevel(log_level)
|
||||
console_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
self.date_format
|
||||
)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
self._initialized = True
|
||||
|
||||
# 记录初始化信息
|
||||
root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}")
|
||||
root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}")
|
||||
|
||||
return root_logger
|
||||
|
||||
def get_module_logger(self, module_name: str) -> logging.Logger:
|
||||
"""获取模块专用日志器
|
||||
|
||||
Args:
|
||||
module_name: 模块名称
|
||||
|
||||
Returns:
|
||||
logging.Logger: 模块日志器
|
||||
"""
|
||||
return logging.getLogger(module_name)
|
||||
|
||||
def create_component_logger(self,
|
||||
component_name: str,
|
||||
log_file: str = None,
|
||||
log_level: int = None) -> logging.Logger:
|
||||
"""为特定组件创建独立的日志器
|
||||
|
||||
Args:
|
||||
component_name: 组件名称
|
||||
log_file: 独立日志文件名(可选)
|
||||
log_level: 日志级别(可选)
|
||||
|
||||
Returns:
|
||||
logging.Logger: 组件日志器
|
||||
"""
|
||||
logger = logging.getLogger(component_name)
|
||||
|
||||
if log_file:
|
||||
# 为组件创建独立的日志文件
|
||||
component_log_file = self.logs_dir / log_file
|
||||
handler = RotatingFileHandler(
|
||||
component_log_file,
|
||||
maxBytes=5 * 1024 * 1024, # 5MB
|
||||
backupCount=3,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
if log_level:
|
||||
handler.setLevel(log_level)
|
||||
|
||||
logger.addHandler(handler)
|
||||
logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}")
|
||||
|
||||
return logger
|
||||
|
||||
def setup_mqtt_logging(self) -> logging.Logger:
|
||||
"""设置MQTT专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: MQTT日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"mqtt_communication",
|
||||
"mqtt_communication.log",
|
||||
logging.DEBUG
|
||||
)
|
||||
|
||||
def setup_mcp_logging(self) -> logging.Logger:
|
||||
"""设置MCP专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: MCP日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"mcp_services",
|
||||
"mcp_services.log",
|
||||
logging.DEBUG
|
||||
)
|
||||
|
||||
def setup_api_logging(self) -> logging.Logger:
|
||||
"""设置API专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: API日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"api_requests",
|
||||
"api_requests.log",
|
||||
logging.INFO
|
||||
)
|
||||
|
||||
def get_logs_info(self) -> dict:
|
||||
"""获取日志系统信息
|
||||
|
||||
Returns:
|
||||
dict: 日志系统信息
|
||||
"""
|
||||
log_files = []
|
||||
if self.logs_dir.exists():
|
||||
for log_file in self.logs_dir.glob("*.log*"):
|
||||
stat = log_file.stat()
|
||||
log_files.append({
|
||||
"name": log_file.name,
|
||||
"size": stat.st_size,
|
||||
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
|
||||
})
|
||||
|
||||
return {
|
||||
"logs_directory": str(self.logs_dir),
|
||||
"initialized": self._initialized,
|
||||
"log_files": log_files,
|
||||
"total_files": len(log_files)
|
||||
}
|
||||
|
||||
def cleanup_old_logs(self, days: int = 30):
|
||||
"""清理旧日志文件
|
||||
|
||||
Args:
|
||||
days: 保留天数
|
||||
"""
|
||||
if not self.logs_dir.exists():
|
||||
return
|
||||
|
||||
from datetime import timedelta
|
||||
cutoff_time = datetime.now() - timedelta(days=days)
|
||||
|
||||
cleaned_files = []
|
||||
for log_file in self.logs_dir.glob("*.log*"):
|
||||
if log_file.stat().st_mtime < cutoff_time.timestamp():
|
||||
try:
|
||||
log_file.unlink()
|
||||
cleaned_files.append(log_file.name)
|
||||
except Exception as e:
|
||||
logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}")
|
||||
|
||||
if cleaned_files:
|
||||
logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}")
|
||||
|
||||
def set_log_level(self, level: int, logger_name: str = None):
|
||||
"""动态调整日志级别
|
||||
|
||||
Args:
|
||||
level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
"""
|
||||
if logger_name:
|
||||
logger = logging.getLogger(logger_name)
|
||||
else:
|
||||
logger = logging.getLogger()
|
||||
|
||||
old_level = logger.level
|
||||
logger.setLevel(level)
|
||||
|
||||
# 同时调整所有处理器的级别
|
||||
for handler in logger.handlers:
|
||||
if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr):
|
||||
# 不调整控制台处理器的级别,保持原有设置
|
||||
handler.setLevel(level)
|
||||
|
||||
level_name = logging.getLevelName(level)
|
||||
old_level_name = logging.getLevelName(old_level)
|
||||
target = logger_name or "根日志器"
|
||||
|
||||
logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}")
|
||||
|
||||
def set_temporary_log_level(self, level: int, logger_name: str = None):
|
||||
"""临时调整日志级别(会保存原始级别用于恢复)
|
||||
|
||||
Args:
|
||||
level: 临时日志级别
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
"""
|
||||
if not hasattr(self, '_original_levels'):
|
||||
self._original_levels = {}
|
||||
|
||||
target_name = logger_name or 'root'
|
||||
|
||||
if logger_name:
|
||||
logger = logging.getLogger(logger_name)
|
||||
else:
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 保存原始级别
|
||||
if target_name not in self._original_levels:
|
||||
self._original_levels[target_name] = logger.level
|
||||
|
||||
# 设置新级别
|
||||
self.set_log_level(level, logger_name)
|
||||
|
||||
level_name = logging.getLevelName(level)
|
||||
target = logger_name or "根日志器"
|
||||
logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)")
|
||||
|
||||
def restore_log_level(self, logger_name: str = None):
|
||||
"""恢复日志级别到调整前的状态
|
||||
|
||||
Args:
|
||||
logger_name: 指定日志器名称,None表示恢复根日志器
|
||||
"""
|
||||
if not hasattr(self, '_original_levels'):
|
||||
logging.warning("没有找到保存的原始日志级别")
|
||||
return
|
||||
|
||||
target_name = logger_name or 'root'
|
||||
|
||||
if target_name not in self._original_levels:
|
||||
logging.warning(f"没有找到 {target_name} 的原始日志级别")
|
||||
return
|
||||
|
||||
original_level = self._original_levels[target_name]
|
||||
self.set_log_level(original_level, logger_name)
|
||||
|
||||
# 清除保存的级别
|
||||
del self._original_levels[target_name]
|
||||
|
||||
target = logger_name or "根日志器"
|
||||
level_name = logging.getLevelName(original_level)
|
||||
logging.info(f"已恢复日志级别: {target} -> {level_name}")
|
||||
|
||||
def get_current_log_levels(self) -> dict:
|
||||
"""获取当前所有日志器的级别信息
|
||||
|
||||
Returns:
|
||||
dict: 日志器级别信息
|
||||
"""
|
||||
levels_info = {}
|
||||
|
||||
# 根日志器
|
||||
root_logger = logging.getLogger()
|
||||
levels_info['root'] = {
|
||||
'level': root_logger.level,
|
||||
'level_name': logging.getLevelName(root_logger.level),
|
||||
'handlers_count': len(root_logger.handlers)
|
||||
}
|
||||
|
||||
# 其他已创建的日志器
|
||||
for name, logger in logging.Logger.manager.loggerDict.items():
|
||||
if isinstance(logger, logging.Logger):
|
||||
levels_info[name] = {
|
||||
'level': logger.level,
|
||||
'level_name': logging.getLevelName(logger.level),
|
||||
'handlers_count': len(logger.handlers)
|
||||
}
|
||||
|
||||
return levels_info
|
||||
|
||||
|
||||
# 全局日志配置实例
|
||||
logger_config = LoggerConfig()
|
||||
|
||||
|
||||
def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor",
|
||||
log_level: int = logging.INFO) -> logging.Logger:
|
||||
"""系统日志初始化快捷函数
|
||||
|
||||
Args:
|
||||
app_name: 应用名称
|
||||
log_level: 日志级别
|
||||
|
||||
Returns:
|
||||
logging.Logger: 根日志器
|
||||
"""
|
||||
return logger_config.setup_logging(app_name, log_level)
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""获取日志器的快捷函数
|
||||
|
||||
Args:
|
||||
name: 日志器名称
|
||||
|
||||
Returns:
|
||||
logging.Logger: 日志器实例
|
||||
"""
|
||||
return logger_config.get_module_logger(name)
|
||||
|
||||
|
||||
def set_log_level(level: int, logger_name: str = None):
|
||||
"""动态调整日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
|
||||
Examples:
|
||||
# 调整根日志器为DEBUG级别
|
||||
set_log_level(logging.DEBUG)
|
||||
|
||||
# 调整特定模块日志器为WARNING级别
|
||||
set_log_level(logging.WARNING, "agent_ontology.core")
|
||||
"""
|
||||
logger_config.set_log_level(level, logger_name)
|
||||
|
||||
|
||||
def set_temporary_log_level(level: int, logger_name: str = None):
|
||||
"""临时调整日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
level: 临时日志级别
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
|
||||
Examples:
|
||||
# 临时调整为DEBUG级别进行调试
|
||||
set_temporary_log_level(logging.DEBUG)
|
||||
# ... 进行调试 ...
|
||||
# 恢复原始级别
|
||||
restore_log_level()
|
||||
"""
|
||||
logger_config.set_temporary_log_level(level, logger_name)
|
||||
|
||||
|
||||
def restore_log_level(logger_name: str = None):
|
||||
"""恢复日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
logger_name: 指定日志器名称,None表示恢复根日志器
|
||||
"""
|
||||
logger_config.restore_log_level(logger_name)
|
||||
|
||||
|
||||
def get_current_log_levels() -> dict:
|
||||
"""获取当前日志级别信息的快捷函数
|
||||
|
||||
Returns:
|
||||
dict: 日志器级别信息
|
||||
|
||||
Examples:
|
||||
levels = get_current_log_levels()
|
||||
print(f"根日志器级别: {levels['root']['level_name']}")
|
||||
"""
|
||||
return logger_config.get_current_log_levels()
|
||||
|
||||
|
||||
# 便捷的日志级别常量
|
||||
class LogLevel:
|
||||
"""日志级别常量类"""
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
ERROR = logging.ERROR
|
||||
CRITICAL = logging.CRITICAL
|
||||
@@ -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('-', '_')}"
|
||||
|
||||
@@ -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 验证通过"
|
||||
|
||||
@@ -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" },
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,10 +0,0 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
@@ -1 +0,0 @@
|
||||
3.13
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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调用成功
|
||||
@@ -1,373 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
# 支持直接运行和模块导入两种方式
|
||||
try:
|
||||
from .utils import load_json, generate_tool_name, generate_input_schema
|
||||
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
||||
from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
|
||||
from .utils.logger_config import logger_config
|
||||
except ImportError:
|
||||
from utils import load_json, generate_tool_name, generate_input_schema
|
||||
from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
||||
from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
|
||||
from utils.logger_config import logger_config
|
||||
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.types as types
|
||||
|
||||
# 初始化 MCP 专用日志器
|
||||
mcp_logger = logger_config.setup_mcp_logging()
|
||||
|
||||
# ========== 数据源配置 ==========
|
||||
# 数据源类型常量
|
||||
DATA_SOURCE_API = "api" # 仅使用API数据
|
||||
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
|
||||
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
|
||||
|
||||
# 默认数据源(可修改)
|
||||
DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL
|
||||
# ================================
|
||||
|
||||
|
||||
def get_queries():
|
||||
"""
|
||||
获取业务查询配置
|
||||
|
||||
Returns:
|
||||
list: 包含所有业务查询配置的列表
|
||||
"""
|
||||
try:
|
||||
# 获取当前文件所在目录
|
||||
current_dir = Path(__file__).parent
|
||||
|
||||
# 构建 businessQueries.json 的路径
|
||||
json_path = current_dir / "businessQueries.json"
|
||||
|
||||
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
|
||||
|
||||
# 使用 load_json 方法读取 JSON 文件
|
||||
queries = load_json(json_path)
|
||||
|
||||
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
|
||||
|
||||
return queries
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
||||
"""
|
||||
根据查询配置生成 MCP 工具模式
|
||||
|
||||
Args:
|
||||
query: 单个查询配置字典
|
||||
|
||||
Returns:
|
||||
types.Tool: MCP 工具对象
|
||||
"""
|
||||
try:
|
||||
# 获取参数定义并生成 inputSchema
|
||||
parameters = query.get('parameters', {})
|
||||
input_schema = generate_input_schema(parameters)
|
||||
|
||||
# 生成工具名称(格式: tool_拼音_id)
|
||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
||||
tool_name = query['businessName']
|
||||
# 构建工具描述,包含业务名称和业务描述
|
||||
description = f"{query['businessName']}: {query['businessDescription']}"
|
||||
|
||||
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
|
||||
|
||||
return types.Tool(
|
||||
name=tool_name,
|
||||
description=description,
|
||||
inputSchema=input_schema
|
||||
)
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
# 创建 MCP 服务器实例
|
||||
server = Server("lzwcai-mcpskills-mfg-data-agent")
|
||||
|
||||
# 缓存查询配置,避免重复加载
|
||||
_queries_cache = None
|
||||
|
||||
|
||||
async def get_queries_cache(source: str = None):
|
||||
"""
|
||||
获取或初始化查询配置缓存
|
||||
|
||||
Args:
|
||||
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE)
|
||||
- "api": 仅使用API数据
|
||||
- "local": 仅使用本地JSON数据
|
||||
- "both": 合并本地和API数据
|
||||
|
||||
Returns:
|
||||
查询配置列表
|
||||
"""
|
||||
global _queries_cache
|
||||
if _queries_cache is None:
|
||||
source = source or DEFAULT_DATA_SOURCE
|
||||
mcp_logger.info(f"初始化查询配置(数据源: {source})...")
|
||||
|
||||
if source == DATA_SOURCE_LOCAL:
|
||||
_queries_cache = get_queries()
|
||||
mcp_logger.info(f"本地配置: {len(_queries_cache)} 条")
|
||||
|
||||
elif source == DATA_SOURCE_API:
|
||||
try:
|
||||
_queries_cache = await call_third_party_api()
|
||||
mcp_logger.info(f"API配置: {len(_queries_cache)} 条")
|
||||
mcp_logger.info(f"API配置数组: {_queries_cache}")
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}")
|
||||
_queries_cache = get_queries()
|
||||
|
||||
else: # DATA_SOURCE_BOTH
|
||||
local = get_queries()
|
||||
try:
|
||||
api = await call_third_party_api()
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API获取失败: {e}")
|
||||
api = []
|
||||
_queries_cache = local + api
|
||||
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})")
|
||||
|
||||
return _queries_cache
|
||||
|
||||
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""
|
||||
列出所有动态生成的 MCP 工具
|
||||
|
||||
Returns:
|
||||
list[types.Tool]: 所有可用的工具列表
|
||||
"""
|
||||
try:
|
||||
mcp_logger.info("收到列出工具请求")
|
||||
|
||||
queries = await get_queries_cache()
|
||||
tools = []
|
||||
|
||||
for query in queries:
|
||||
tool = generate_tool_schema_from_query(query)
|
||||
tools.append(tool)
|
||||
|
||||
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
|
||||
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
|
||||
|
||||
return tools
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str,
|
||||
arguments: dict[str, Any] | None
|
||||
) -> list[types.TextContent]:
|
||||
"""
|
||||
处理工具调用请求
|
||||
|
||||
Args:
|
||||
name: 工具名称
|
||||
arguments: 工具参数
|
||||
|
||||
Returns:
|
||||
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
|
||||
"""
|
||||
try:
|
||||
mcp_logger.info(f"收到工具调用请求: {name}")
|
||||
mcp_logger.debug(f"工具参数: {arguments}")
|
||||
|
||||
# 获取查询配置缓存
|
||||
queries = await get_queries_cache()
|
||||
|
||||
# 根据工具名称查找对应的 item(接口配置)
|
||||
tool_item = None
|
||||
for query in queries:
|
||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
||||
tool_name = query['businessName']
|
||||
if tool_name == name:
|
||||
tool_item = query
|
||||
break
|
||||
|
||||
# 构建返回结果
|
||||
import json
|
||||
|
||||
if tool_item:
|
||||
request_data = {
|
||||
"datasourceId": get_datasource_id(),
|
||||
"businessName": tool_item.get("businessName"),
|
||||
"businessDescription": tool_item.get("businessDescription"),
|
||||
"sqlTemplate": tool_item.get("sqlTemplate"),
|
||||
"parameters": tool_item.get("parameters"),
|
||||
"testParams": arguments or {}
|
||||
}
|
||||
|
||||
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
|
||||
if arguments and arguments.get("targetDatabaseName"):
|
||||
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
|
||||
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
|
||||
|
||||
# 调用测试SQL API
|
||||
try:
|
||||
mcp_logger.info("正在调用测试SQL API...")
|
||||
api_response = test_sql_with_schema(request_data)
|
||||
mcp_logger.info("测试SQL API调用成功")
|
||||
|
||||
# 返回包含 data 字段的结果
|
||||
result = {
|
||||
"success": True,
|
||||
"data": api_response
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"调用测试SQL API失败: {str(e)}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
result = {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"data": None
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
error_msg = f"未找到工具 {name} 对应的配置"
|
||||
result = {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"data": None
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
mcp_logger.debug(f"工具调用结果: {result_text}")
|
||||
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=result_text
|
||||
)
|
||||
]
|
||||
except Exception as e:
|
||||
error_msg = f"工具调用失败: {name}, 错误: {e}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=f"错误: {error_msg}"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def call_third_party_api(skill_id: str = None) -> list:
|
||||
"""
|
||||
调用第三方API获取技能信息并返回处理后的数据
|
||||
|
||||
Args:
|
||||
skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178)
|
||||
|
||||
Returns:
|
||||
处理后的查询配置列表(businessQueries格式)
|
||||
|
||||
Example:
|
||||
queries = await call_third_party_api()
|
||||
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
|
||||
"""
|
||||
try:
|
||||
# 如果没有传入 skill_id,则从环境变量读取
|
||||
if skill_id is None:
|
||||
skill_id = get_skill_id()
|
||||
|
||||
mcp_logger.info(f"调用第三方API,skill_id: {skill_id}")
|
||||
|
||||
# 获取原始数据
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
|
||||
mcp_logger.info(f"成功{raw_result}")
|
||||
|
||||
# 处理并返回
|
||||
processed_queries = process_skill_response(raw_result)
|
||||
|
||||
mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据")
|
||||
return processed_queries
|
||||
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"API调用失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""MCP 服务器异步主函数"""
|
||||
try:
|
||||
mcp_logger.info("=" * 60)
|
||||
mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder")
|
||||
mcp_logger.info("版本: 0.1.0")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
# 输出环境配置信息
|
||||
env_config = get_env_config()
|
||||
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
|
||||
mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}")
|
||||
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
|
||||
mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
from mcp.server.stdio import stdio_server
|
||||
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
mcp_logger.info("MCP 服务器已启动,等待客户端连接...")
|
||||
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="lzwcai-mcpskills-analyzeOrder",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
mcp_logger.info("MCP 服务器已关闭")
|
||||
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
"""入口点函数(用于 console_scripts)"""
|
||||
try:
|
||||
# 初始化系统日志
|
||||
# MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信
|
||||
logger_config.setup_logging(
|
||||
app_name="lzwcai_mcp_sqlexecutor",
|
||||
log_level=logging.INFO,
|
||||
console_output=False # 禁用控制台输出
|
||||
)
|
||||
|
||||
mcp_logger.info("开始运行 MCP SQL Executor 服务器")
|
||||
asyncio.run(async_main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
mcp_logger.info("收到中断信号,正在关闭服务器...")
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
统一日志配置模块
|
||||
提供系统级别的日志配置和管理
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class LoggerConfig:
|
||||
"""日志配置管理类"""
|
||||
|
||||
def __init__(self, logs_dir: str = None):
|
||||
"""初始化日志配置
|
||||
|
||||
Args:
|
||||
logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹
|
||||
"""
|
||||
# 确定日志目录
|
||||
if logs_dir:
|
||||
self.logs_dir = Path(logs_dir)
|
||||
else:
|
||||
# 获取项目根目录(logger_config.py 在 utils 目录下,需要上升两层到达项目根目录)
|
||||
project_root = Path(__file__).parent.parent
|
||||
self.logs_dir = project_root / "logs"
|
||||
|
||||
# 创建日志目录
|
||||
self.logs_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 日志格式
|
||||
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
||||
self.date_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# 从环境变量获取日志级别,默认为INFO
|
||||
self.log_level = self._get_log_level_from_env()
|
||||
|
||||
# 是否已初始化
|
||||
self._initialized = False
|
||||
|
||||
def _get_log_level_from_env(self) -> int:
|
||||
"""从环境变量获取日志级别
|
||||
|
||||
Returns:
|
||||
int: 日志级别
|
||||
"""
|
||||
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||
|
||||
# 日志级别映射
|
||||
level_mapping = {
|
||||
'DEBUG': logging.DEBUG,
|
||||
'INFO': logging.INFO,
|
||||
'WARNING': logging.WARNING,
|
||||
'WARN': logging.WARNING, # 兼容性别名
|
||||
'ERROR': logging.ERROR,
|
||||
'CRITICAL': logging.CRITICAL,
|
||||
'FATAL': logging.CRITICAL # 兼容性别名
|
||||
}
|
||||
|
||||
return level_mapping.get(log_level_str, logging.INFO)
|
||||
|
||||
def setup_logging(self,
|
||||
app_name: str = "lzwcai_mcp_sqlexecutor",
|
||||
log_level: int = logging.INFO,
|
||||
max_file_size: int = 10 * 1024 * 1024, # 10MB
|
||||
backup_count: int = 5,
|
||||
console_output: bool = True) -> logging.Logger:
|
||||
"""设置系统日志配置
|
||||
|
||||
Args:
|
||||
app_name: 应用名称,用于日志文件命名
|
||||
log_level: 日志级别
|
||||
max_file_size: 单个日志文件最大大小(字节)
|
||||
backup_count: 保留的备份文件数量
|
||||
console_output: 是否输出到控制台
|
||||
|
||||
Returns:
|
||||
logging.Logger: 配置好的根日志器
|
||||
"""
|
||||
if self._initialized:
|
||||
return logging.getLogger()
|
||||
|
||||
# 设置根日志器
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(log_level)
|
||||
|
||||
# 清除现有的处理器
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# 创建格式化器
|
||||
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||
|
||||
# 1. 主日志文件 - 按大小滚动
|
||||
main_log_file = self.logs_dir / f"{app_name}.log"
|
||||
file_handler = RotatingFileHandler(
|
||||
main_log_file,
|
||||
maxBytes=max_file_size,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(log_level)
|
||||
file_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(file_handler)
|
||||
|
||||
# 2. 错误日志文件 - 只记录ERROR及以上级别
|
||||
error_log_file = self.logs_dir / f"{app_name}_error.log"
|
||||
error_handler = RotatingFileHandler(
|
||||
error_log_file,
|
||||
maxBytes=max_file_size,
|
||||
backupCount=backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
error_handler.setLevel(logging.ERROR)
|
||||
error_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(error_handler)
|
||||
|
||||
# 3. 按日期滚动的日志文件
|
||||
daily_log_file = self.logs_dir / f"{app_name}_daily.log"
|
||||
daily_handler = TimedRotatingFileHandler(
|
||||
daily_log_file,
|
||||
when='midnight',
|
||||
interval=1,
|
||||
backupCount=30, # 保留30天
|
||||
encoding='utf-8'
|
||||
)
|
||||
daily_handler.setLevel(log_level)
|
||||
daily_handler.setFormatter(formatter)
|
||||
daily_handler.suffix = "%Y-%m-%d"
|
||||
root_logger.addHandler(daily_handler)
|
||||
|
||||
# 4. 控制台输出
|
||||
# 重要:MCP协议使用stdio时,必须将日志输出到stderr,stdout仅用于JSON-RPC通信
|
||||
if console_output:
|
||||
console_handler = logging.StreamHandler(sys.stderr)
|
||||
console_handler.setLevel(log_level)
|
||||
console_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
self.date_format
|
||||
)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
self._initialized = True
|
||||
|
||||
# 记录初始化信息
|
||||
root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}")
|
||||
root_logger.info(f"日志配置 - 级别: {logging.getLevelName(log_level)}, 文件大小限制: {max_file_size//1024//1024}MB, 备份数量: {backup_count}")
|
||||
|
||||
return root_logger
|
||||
|
||||
def get_module_logger(self, module_name: str) -> logging.Logger:
|
||||
"""获取模块专用日志器
|
||||
|
||||
Args:
|
||||
module_name: 模块名称
|
||||
|
||||
Returns:
|
||||
logging.Logger: 模块日志器
|
||||
"""
|
||||
return logging.getLogger(module_name)
|
||||
|
||||
def create_component_logger(self,
|
||||
component_name: str,
|
||||
log_file: str = None,
|
||||
log_level: int = None) -> logging.Logger:
|
||||
"""为特定组件创建独立的日志器
|
||||
|
||||
Args:
|
||||
component_name: 组件名称
|
||||
log_file: 独立日志文件名(可选)
|
||||
log_level: 日志级别(可选)
|
||||
|
||||
Returns:
|
||||
logging.Logger: 组件日志器
|
||||
"""
|
||||
logger = logging.getLogger(component_name)
|
||||
|
||||
if log_file:
|
||||
# 为组件创建独立的日志文件
|
||||
component_log_file = self.logs_dir / log_file
|
||||
handler = RotatingFileHandler(
|
||||
component_log_file,
|
||||
maxBytes=5 * 1024 * 1024, # 5MB
|
||||
backupCount=3,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
if log_level:
|
||||
handler.setLevel(log_level)
|
||||
|
||||
logger.addHandler(handler)
|
||||
logger.info(f"组件日志器创建完成: {component_name} -> {component_log_file}")
|
||||
|
||||
return logger
|
||||
|
||||
def setup_mqtt_logging(self) -> logging.Logger:
|
||||
"""设置MQTT专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: MQTT日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"mqtt_communication",
|
||||
"mqtt_communication.log",
|
||||
logging.DEBUG
|
||||
)
|
||||
|
||||
def setup_mcp_logging(self) -> logging.Logger:
|
||||
"""设置MCP专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: MCP日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"mcp_services",
|
||||
"mcp_services.log",
|
||||
logging.DEBUG
|
||||
)
|
||||
|
||||
def setup_api_logging(self) -> logging.Logger:
|
||||
"""设置API专用日志
|
||||
|
||||
Returns:
|
||||
logging.Logger: API日志器
|
||||
"""
|
||||
return self.create_component_logger(
|
||||
"api_requests",
|
||||
"api_requests.log",
|
||||
logging.INFO
|
||||
)
|
||||
|
||||
def get_logs_info(self) -> dict:
|
||||
"""获取日志系统信息
|
||||
|
||||
Returns:
|
||||
dict: 日志系统信息
|
||||
"""
|
||||
log_files = []
|
||||
if self.logs_dir.exists():
|
||||
for log_file in self.logs_dir.glob("*.log*"):
|
||||
stat = log_file.stat()
|
||||
log_files.append({
|
||||
"name": log_file.name,
|
||||
"size": stat.st_size,
|
||||
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
|
||||
})
|
||||
|
||||
return {
|
||||
"logs_directory": str(self.logs_dir),
|
||||
"initialized": self._initialized,
|
||||
"log_files": log_files,
|
||||
"total_files": len(log_files)
|
||||
}
|
||||
|
||||
def cleanup_old_logs(self, days: int = 30):
|
||||
"""清理旧日志文件
|
||||
|
||||
Args:
|
||||
days: 保留天数
|
||||
"""
|
||||
if not self.logs_dir.exists():
|
||||
return
|
||||
|
||||
from datetime import timedelta
|
||||
cutoff_time = datetime.now() - timedelta(days=days)
|
||||
|
||||
cleaned_files = []
|
||||
for log_file in self.logs_dir.glob("*.log*"):
|
||||
if log_file.stat().st_mtime < cutoff_time.timestamp():
|
||||
try:
|
||||
log_file.unlink()
|
||||
cleaned_files.append(log_file.name)
|
||||
except Exception as e:
|
||||
logging.error(f"清理日志文件失败: {log_file.name}, 错误: {e}")
|
||||
|
||||
if cleaned_files:
|
||||
logging.info(f"清理了 {len(cleaned_files)} 个旧日志文件: {cleaned_files}")
|
||||
|
||||
def set_log_level(self, level: int, logger_name: str = None):
|
||||
"""动态调整日志级别
|
||||
|
||||
Args:
|
||||
level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
"""
|
||||
if logger_name:
|
||||
logger = logging.getLogger(logger_name)
|
||||
else:
|
||||
logger = logging.getLogger()
|
||||
|
||||
old_level = logger.level
|
||||
logger.setLevel(level)
|
||||
|
||||
# 同时调整所有处理器的级别
|
||||
for handler in logger.handlers:
|
||||
if not isinstance(handler, logging.StreamHandler) or handler.stream not in (sys.stdout, sys.stderr):
|
||||
# 不调整控制台处理器的级别,保持原有设置
|
||||
handler.setLevel(level)
|
||||
|
||||
level_name = logging.getLevelName(level)
|
||||
old_level_name = logging.getLevelName(old_level)
|
||||
target = logger_name or "根日志器"
|
||||
|
||||
logger.info(f"日志级别已调整: {target} {old_level_name} -> {level_name}")
|
||||
|
||||
def set_temporary_log_level(self, level: int, logger_name: str = None):
|
||||
"""临时调整日志级别(会保存原始级别用于恢复)
|
||||
|
||||
Args:
|
||||
level: 临时日志级别
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
"""
|
||||
if not hasattr(self, '_original_levels'):
|
||||
self._original_levels = {}
|
||||
|
||||
target_name = logger_name or 'root'
|
||||
|
||||
if logger_name:
|
||||
logger = logging.getLogger(logger_name)
|
||||
else:
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 保存原始级别
|
||||
if target_name not in self._original_levels:
|
||||
self._original_levels[target_name] = logger.level
|
||||
|
||||
# 设置新级别
|
||||
self.set_log_level(level, logger_name)
|
||||
|
||||
level_name = logging.getLevelName(level)
|
||||
target = logger_name or "根日志器"
|
||||
logger.info(f"临时调整日志级别: {target} -> {level_name} (可通过restore_log_level恢复)")
|
||||
|
||||
def restore_log_level(self, logger_name: str = None):
|
||||
"""恢复日志级别到调整前的状态
|
||||
|
||||
Args:
|
||||
logger_name: 指定日志器名称,None表示恢复根日志器
|
||||
"""
|
||||
if not hasattr(self, '_original_levels'):
|
||||
logging.warning("没有找到保存的原始日志级别")
|
||||
return
|
||||
|
||||
target_name = logger_name or 'root'
|
||||
|
||||
if target_name not in self._original_levels:
|
||||
logging.warning(f"没有找到 {target_name} 的原始日志级别")
|
||||
return
|
||||
|
||||
original_level = self._original_levels[target_name]
|
||||
self.set_log_level(original_level, logger_name)
|
||||
|
||||
# 清除保存的级别
|
||||
del self._original_levels[target_name]
|
||||
|
||||
target = logger_name or "根日志器"
|
||||
level_name = logging.getLevelName(original_level)
|
||||
logging.info(f"已恢复日志级别: {target} -> {level_name}")
|
||||
|
||||
def get_current_log_levels(self) -> dict:
|
||||
"""获取当前所有日志器的级别信息
|
||||
|
||||
Returns:
|
||||
dict: 日志器级别信息
|
||||
"""
|
||||
levels_info = {}
|
||||
|
||||
# 根日志器
|
||||
root_logger = logging.getLogger()
|
||||
levels_info['root'] = {
|
||||
'level': root_logger.level,
|
||||
'level_name': logging.getLevelName(root_logger.level),
|
||||
'handlers_count': len(root_logger.handlers)
|
||||
}
|
||||
|
||||
# 其他已创建的日志器
|
||||
for name, logger in logging.Logger.manager.loggerDict.items():
|
||||
if isinstance(logger, logging.Logger):
|
||||
levels_info[name] = {
|
||||
'level': logger.level,
|
||||
'level_name': logging.getLevelName(logger.level),
|
||||
'handlers_count': len(logger.handlers)
|
||||
}
|
||||
|
||||
return levels_info
|
||||
|
||||
|
||||
# 全局日志配置实例
|
||||
logger_config = LoggerConfig()
|
||||
|
||||
|
||||
def setup_system_logging(app_name: str = "lzwcai_mcp_sqlexecutor",
|
||||
log_level: int = logging.INFO) -> logging.Logger:
|
||||
"""系统日志初始化快捷函数
|
||||
|
||||
Args:
|
||||
app_name: 应用名称
|
||||
log_level: 日志级别
|
||||
|
||||
Returns:
|
||||
logging.Logger: 根日志器
|
||||
"""
|
||||
return logger_config.setup_logging(app_name, log_level)
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""获取日志器的快捷函数
|
||||
|
||||
Args:
|
||||
name: 日志器名称
|
||||
|
||||
Returns:
|
||||
logging.Logger: 日志器实例
|
||||
"""
|
||||
return logger_config.get_module_logger(name)
|
||||
|
||||
|
||||
def set_log_level(level: int, logger_name: str = None):
|
||||
"""动态调整日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
level: 新的日志级别 (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
|
||||
Examples:
|
||||
# 调整根日志器为DEBUG级别
|
||||
set_log_level(logging.DEBUG)
|
||||
|
||||
# 调整特定模块日志器为WARNING级别
|
||||
set_log_level(logging.WARNING, "agent_ontology.core")
|
||||
"""
|
||||
logger_config.set_log_level(level, logger_name)
|
||||
|
||||
|
||||
def set_temporary_log_level(level: int, logger_name: str = None):
|
||||
"""临时调整日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
level: 临时日志级别
|
||||
logger_name: 指定日志器名称,None表示调整根日志器
|
||||
|
||||
Examples:
|
||||
# 临时调整为DEBUG级别进行调试
|
||||
set_temporary_log_level(logging.DEBUG)
|
||||
# ... 进行调试 ...
|
||||
# 恢复原始级别
|
||||
restore_log_level()
|
||||
"""
|
||||
logger_config.set_temporary_log_level(level, logger_name)
|
||||
|
||||
|
||||
def restore_log_level(logger_name: str = None):
|
||||
"""恢复日志级别的快捷函数
|
||||
|
||||
Args:
|
||||
logger_name: 指定日志器名称,None表示恢复根日志器
|
||||
"""
|
||||
logger_config.restore_log_level(logger_name)
|
||||
|
||||
|
||||
def get_current_log_levels() -> dict:
|
||||
"""获取当前日志级别信息的快捷函数
|
||||
|
||||
Returns:
|
||||
dict: 日志器级别信息
|
||||
|
||||
Examples:
|
||||
levels = get_current_log_levels()
|
||||
print(f"根日志器级别: {levels['root']['level_name']}")
|
||||
"""
|
||||
return logger_config.get_current_log_levels()
|
||||
|
||||
|
||||
# 便捷的日志级别常量
|
||||
class LogLevel:
|
||||
"""日志级别常量类"""
|
||||
DEBUG = logging.DEBUG
|
||||
INFO = logging.INFO
|
||||
WARNING = logging.WARNING
|
||||
ERROR = logging.ERROR
|
||||
CRITICAL = logging.CRITICAL
|
||||
@@ -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('-', '_')}"
|
||||
|
||||
@@ -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 验证通过"
|
||||
|
||||
@@ -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" },
|
||||
]
|
||||
@@ -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()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,111 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
def main(**kwargs) -> dict:
|
||||
"""
|
||||
函数节点的入口函数
|
||||
|
||||
Args:
|
||||
**kwargs: 从前端配置的参数传入,可通过变量引用获取工作流上下文
|
||||
|
||||
Returns:
|
||||
dict: 返回结果将作为节点输出,可被后续节点引用
|
||||
"""
|
||||
# 从 kwargs 获取参数,如果没有提供则使用默认值
|
||||
url = kwargs.get('url', 'http://192.167.30.2:8088/datasource/sqlExecutionLog/testBatchSqlWithSchema')
|
||||
|
||||
# 请求头(从kwargs获取或使用默认值)
|
||||
authorization_token = kwargs.get('authorization_token', 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjAxNzZiNjEyLTc2YWItNDdhMS1iYTRiLTdjNWU2ZTMxNDlmZCJ9.w7aOfDJDHtA4bwNKIvUVK2cf1yO_2F27d_eYuos-p1-XGrSQOX0D4ny0b8Js36MhXwBnF4GDcy8V1VobEN6zBA')
|
||||
headers = {
|
||||
'Authorization': authorization_token
|
||||
}
|
||||
|
||||
# 请求体参数(从kwargs获取或使用默认值)
|
||||
payload_id = kwargs.get('id', '2006300000000000001')
|
||||
business_name = kwargs.get('businessName', 'OrderDelayWarningAnalysis')
|
||||
business_description = kwargs.get('businessDescription', '订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级')
|
||||
datasource_id = kwargs.get('datasourceId', '57')
|
||||
sql_template = kwargs.get('sqlTemplate', 'WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = \'CLOSED\' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = \'CLOSED\'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN (\'OPEN\', \'STARTED\')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN \'RED\' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN \'YELLOW\' ELSE \'GREEN\' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN \'PRODUCTION_SEVERELY_DELAYED\' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN \'HIGH_LOGISTICS_DELAY_RISK\' WHEN gm.avg_production_days > 15 THEN \'LONG_PRODUCTION_CYCLE\' WHEN gm.defect_rate_pct > 10 THEN \'QUALITY_ISSUES\' ELSE \'NORMAL\' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = \'t\' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id WHERE EXTRACT(YEAR FROM so.order_date_utc) = 2025 ORDER BY delay_probability_pct DESC, so.order_date_utc DESC LIMIT 30')
|
||||
parameters = kwargs.get('parameters', {})
|
||||
|
||||
# 请求体(从kwargs获取或使用默认值)
|
||||
payload = {
|
||||
"id": payload_id,
|
||||
"businessName": business_name,
|
||||
"businessDescription": business_description,
|
||||
"datasourceId": datasource_id,
|
||||
"sqlTemplate": sql_template,
|
||||
"parameters": parameters
|
||||
}
|
||||
|
||||
# 超时时间(从kwargs获取或使用默认值)
|
||||
timeout = kwargs.get('timeout', 30)
|
||||
|
||||
try:
|
||||
# 发送POST请求
|
||||
response = requests.post(
|
||||
url=url,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# 检查HTTP响应状态码
|
||||
response.raise_for_status()
|
||||
|
||||
# 解析响应结果
|
||||
result = response.json()
|
||||
|
||||
# 返回接口调用结果
|
||||
return {
|
||||
"status": "success",
|
||||
"status_code": response.status_code,
|
||||
"result": result
|
||||
}
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_type": "timeout",
|
||||
"message": f"接口调用超时({timeout}秒)",
|
||||
"result": None
|
||||
}
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_type": "connection_error",
|
||||
"message": "无法连接到目标服务器",
|
||||
"result": None
|
||||
}
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_type": "http_error",
|
||||
"status_code": response.status_code if 'response' in locals() else None,
|
||||
"message": f"HTTP请求错误: {str(e)}",
|
||||
"response_text": response.text if 'response' in locals() else "",
|
||||
"result": None
|
||||
}
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_type": "json_decode_error",
|
||||
"message": "接口返回的不是有效的JSON格式",
|
||||
"response_text": response.text if 'response' in locals() else "",
|
||||
"result": None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error_type": "unknown_error",
|
||||
"message": f"接口调用失败: {str(e)}",
|
||||
"result": None
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = main()
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
@@ -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
|
||||
@@ -1,10 +0,0 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
@@ -1 +0,0 @@
|
||||
3.13
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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调用成功
|
||||
@@ -1,373 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
# 支持直接运行和模块导入两种方式
|
||||
try:
|
||||
from .utils import load_json, generate_tool_name, generate_input_schema
|
||||
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
||||
from .utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
|
||||
from .utils.logger_config import logger_config
|
||||
except ImportError:
|
||||
from utils import load_json, generate_tool_name, generate_input_schema
|
||||
from utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
||||
from utils import get_database_id, get_datasource_id, get_skill_id, get_env_config
|
||||
from utils.logger_config import logger_config
|
||||
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.types as types
|
||||
|
||||
# 初始化 MCP 专用日志器
|
||||
mcp_logger = logger_config.setup_mcp_logging()
|
||||
|
||||
# ========== 数据源配置 ==========
|
||||
# 数据源类型常量
|
||||
DATA_SOURCE_API = "api" # 仅使用API数据
|
||||
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
|
||||
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
|
||||
|
||||
# 默认数据源(可修改)
|
||||
DEFAULT_DATA_SOURCE = DATA_SOURCE_LOCAL
|
||||
# ================================
|
||||
|
||||
|
||||
def get_queries():
|
||||
"""
|
||||
获取业务查询配置
|
||||
|
||||
Returns:
|
||||
list: 包含所有业务查询配置的列表
|
||||
"""
|
||||
try:
|
||||
# 获取当前文件所在目录
|
||||
current_dir = Path(__file__).parent
|
||||
|
||||
# 构建 businessQueries.json 的路径
|
||||
json_path = current_dir / "businessQueries.json"
|
||||
|
||||
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
|
||||
|
||||
# 使用 load_json 方法读取 JSON 文件
|
||||
queries = load_json(json_path)
|
||||
|
||||
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
|
||||
|
||||
return queries
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
||||
"""
|
||||
根据查询配置生成 MCP 工具模式
|
||||
|
||||
Args:
|
||||
query: 单个查询配置字典
|
||||
|
||||
Returns:
|
||||
types.Tool: MCP 工具对象
|
||||
"""
|
||||
try:
|
||||
# 获取参数定义并生成 inputSchema
|
||||
parameters = query.get('parameters', {})
|
||||
input_schema = generate_input_schema(parameters)
|
||||
|
||||
# 生成工具名称(格式: tool_拼音_id)
|
||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
||||
tool_name = query['businessName']
|
||||
# 构建工具描述,包含业务名称和业务描述
|
||||
description = f"{query['businessName']}: {query['businessDescription']}"
|
||||
|
||||
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
|
||||
|
||||
return types.Tool(
|
||||
name=tool_name,
|
||||
description=description,
|
||||
inputSchema=input_schema
|
||||
)
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
# 创建 MCP 服务器实例
|
||||
server = Server("lzwcai-mcpskills-mfg-data-agentv2")
|
||||
|
||||
# 缓存查询配置,避免重复加载
|
||||
_queries_cache = None
|
||||
|
||||
|
||||
async def get_queries_cache(source: str = None):
|
||||
"""
|
||||
获取或初始化查询配置缓存
|
||||
|
||||
Args:
|
||||
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE)
|
||||
- "api": 仅使用API数据
|
||||
- "local": 仅使用本地JSON数据
|
||||
- "both": 合并本地和API数据
|
||||
|
||||
Returns:
|
||||
查询配置列表
|
||||
"""
|
||||
global _queries_cache
|
||||
if _queries_cache is None:
|
||||
source = source or DEFAULT_DATA_SOURCE
|
||||
mcp_logger.info(f"初始化查询配置(数据源: {source})...")
|
||||
|
||||
if source == DATA_SOURCE_LOCAL:
|
||||
_queries_cache = get_queries()
|
||||
mcp_logger.info(f"本地配置: {len(_queries_cache)} 条")
|
||||
|
||||
elif source == DATA_SOURCE_API:
|
||||
try:
|
||||
_queries_cache = await call_third_party_api()
|
||||
mcp_logger.info(f"API配置: {len(_queries_cache)} 条")
|
||||
mcp_logger.info(f"API配置数组: {_queries_cache}")
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API获取失败,降级使用本地配置: {e}")
|
||||
_queries_cache = get_queries()
|
||||
|
||||
else: # DATA_SOURCE_BOTH
|
||||
local = get_queries()
|
||||
try:
|
||||
api = await call_third_party_api()
|
||||
except Exception as e:
|
||||
mcp_logger.warning(f"API获取失败: {e}")
|
||||
api = []
|
||||
_queries_cache = local + api
|
||||
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})")
|
||||
|
||||
return _queries_cache
|
||||
|
||||
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""
|
||||
列出所有动态生成的 MCP 工具
|
||||
|
||||
Returns:
|
||||
list[types.Tool]: 所有可用的工具列表
|
||||
"""
|
||||
try:
|
||||
mcp_logger.info("收到列出工具请求")
|
||||
|
||||
queries = await get_queries_cache()
|
||||
tools = []
|
||||
|
||||
for query in queries:
|
||||
tool = generate_tool_schema_from_query(query)
|
||||
tools.append(tool)
|
||||
|
||||
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
|
||||
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
|
||||
|
||||
return tools
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str,
|
||||
arguments: dict[str, Any] | None
|
||||
) -> list[types.TextContent]:
|
||||
"""
|
||||
处理工具调用请求
|
||||
|
||||
Args:
|
||||
name: 工具名称
|
||||
arguments: 工具参数
|
||||
|
||||
Returns:
|
||||
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
|
||||
"""
|
||||
try:
|
||||
mcp_logger.info(f"收到工具调用请求: {name}")
|
||||
mcp_logger.debug(f"工具参数: {arguments}")
|
||||
|
||||
# 获取查询配置缓存
|
||||
queries = await get_queries_cache()
|
||||
|
||||
# 根据工具名称查找对应的 item(接口配置)
|
||||
tool_item = None
|
||||
for query in queries:
|
||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
||||
tool_name = query['businessName']
|
||||
if tool_name == name:
|
||||
tool_item = query
|
||||
break
|
||||
|
||||
# 构建返回结果
|
||||
import json
|
||||
|
||||
if tool_item:
|
||||
request_data = {
|
||||
"datasourceId": get_datasource_id(),
|
||||
"businessName": tool_item.get("businessName"),
|
||||
"businessDescription": tool_item.get("businessDescription"),
|
||||
"sqlTemplate": tool_item.get("sqlTemplate"),
|
||||
"parameters": tool_item.get("parameters"),
|
||||
"testParams": arguments or {}
|
||||
}
|
||||
|
||||
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
|
||||
if arguments and arguments.get("targetDatabaseName"):
|
||||
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
|
||||
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
|
||||
|
||||
# 调用测试SQL API
|
||||
try:
|
||||
mcp_logger.info("正在调用测试SQL API...")
|
||||
api_response = test_sql_with_schema(request_data)
|
||||
mcp_logger.info("测试SQL API调用成功")
|
||||
|
||||
# 返回包含 data 字段的结果
|
||||
result = {
|
||||
"success": True,
|
||||
"data": api_response
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"调用测试SQL API失败: {str(e)}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
result = {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"data": None
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
error_msg = f"未找到工具 {name} 对应的配置"
|
||||
result = {
|
||||
"success": False,
|
||||
"error": error_msg,
|
||||
"data": None
|
||||
}
|
||||
result_text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
mcp_logger.debug(f"工具调用结果: {result_text}")
|
||||
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=result_text
|
||||
)
|
||||
]
|
||||
except Exception as e:
|
||||
error_msg = f"工具调用失败: {name}, 错误: {e}"
|
||||
mcp_logger.error(error_msg, exc_info=True)
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=f"错误: {error_msg}"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def call_third_party_api(skill_id: str = None) -> list:
|
||||
"""
|
||||
调用第三方API获取技能信息并返回处理后的数据
|
||||
|
||||
Args:
|
||||
skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178)
|
||||
|
||||
Returns:
|
||||
处理后的查询配置列表(businessQueries格式)
|
||||
|
||||
Example:
|
||||
queries = await call_third_party_api()
|
||||
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
|
||||
"""
|
||||
try:
|
||||
# 如果没有传入 skill_id,则从环境变量读取
|
||||
if skill_id is None:
|
||||
skill_id = get_skill_id()
|
||||
|
||||
mcp_logger.info(f"调用第三方API,skill_id: {skill_id}")
|
||||
|
||||
# 获取原始数据
|
||||
raw_result = get_skill_by_id(skill_id)
|
||||
|
||||
mcp_logger.info(f"成功{raw_result}")
|
||||
|
||||
# 处理并返回
|
||||
processed_queries = process_skill_response(raw_result)
|
||||
|
||||
mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据")
|
||||
return processed_queries
|
||||
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"API调用失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""MCP 服务器异步主函数"""
|
||||
try:
|
||||
mcp_logger.info("=" * 60)
|
||||
mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder")
|
||||
mcp_logger.info("版本: 0.1.0")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
# 输出环境配置信息
|
||||
env_config = get_env_config()
|
||||
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
|
||||
mcp_logger.info(f"环境配置 - Datasource ID: {env_config['datasource_id']}")
|
||||
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
|
||||
mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}")
|
||||
mcp_logger.info("=" * 60)
|
||||
|
||||
from mcp.server.stdio import stdio_server
|
||||
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
mcp_logger.info("MCP 服务器已启动,等待客户端连接...")
|
||||
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="lzwcai-mcpskills-analyzeOrder",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
mcp_logger.info("MCP 服务器已关闭")
|
||||
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
"""入口点函数(用于 console_scripts)"""
|
||||
try:
|
||||
# 初始化系统日志
|
||||
# MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信
|
||||
logger_config.setup_logging(
|
||||
app_name="lzwcai_mcp_sqlexecutor",
|
||||
log_level=logging.INFO,
|
||||
console_output=False # 禁用控制台输出
|
||||
)
|
||||
|
||||
mcp_logger.info("开始运行 MCP SQL Executor 服务器")
|
||||
asyncio.run(async_main())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
mcp_logger.info("收到中断信号,正在关闭服务器...")
|
||||
except Exception as e:
|
||||
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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
Reference in New Issue
Block a user